mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-03-11 17:45:30 +00:00
Update + Update Version + Eslint
This commit is contained in:
parent
a307b9b1ed
commit
a7c7451b0d
54 changed files with 8581 additions and 9106 deletions
238
@types/crunchyEpisodeList.d.ts
vendored
238
@types/crunchyEpisodeList.d.ts
vendored
|
|
@ -1,119 +1,119 @@
|
|||
export interface CrunchyEpisodeList {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: unknown;
|
||||
__actions__: unknown;
|
||||
total: number;
|
||||
items: Item[];
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
__class__: Class;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Links;
|
||||
__actions__: unknown;
|
||||
id: string;
|
||||
channel_id: ChannelID;
|
||||
series_id: string;
|
||||
series_title: string;
|
||||
series_slug_title: string;
|
||||
season_id: string;
|
||||
season_title: string;
|
||||
season_slug_title: string;
|
||||
season_number: number;
|
||||
episode: string;
|
||||
episode_number: number | null;
|
||||
sequence_number: number;
|
||||
production_episode_id: string;
|
||||
title: string;
|
||||
slug_title: string;
|
||||
description: string;
|
||||
next_episode_id?: string;
|
||||
next_episode_title?: string;
|
||||
hd_flag: boolean;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
episode_air_date: string;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_clip: boolean;
|
||||
seo_title: string;
|
||||
seo_description: string;
|
||||
season_tags: string[];
|
||||
available_offline: boolean;
|
||||
media_type: Class;
|
||||
slug: string;
|
||||
images: Images;
|
||||
duration_ms: number;
|
||||
ad_breaks: AdBreak[];
|
||||
is_premium_only: boolean;
|
||||
listing_id: string;
|
||||
subtitle_locales: SubtitleLocale[];
|
||||
playback?: string;
|
||||
availability_notes: string;
|
||||
available_date?: string;
|
||||
hide_season_title?: boolean;
|
||||
hide_season_number?: boolean;
|
||||
isSelected?: boolean;
|
||||
seq_id: string;
|
||||
}
|
||||
|
||||
export enum Class {
|
||||
Episode = 'episode',
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
ads: Ads;
|
||||
'episode/channel': Ads;
|
||||
'episode/next_episode'?: Ads;
|
||||
'episode/season': Ads;
|
||||
'episode/series': Ads;
|
||||
streams?: Ads;
|
||||
}
|
||||
|
||||
export interface Ads {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
type: AdBreakType;
|
||||
offset_ms: number;
|
||||
}
|
||||
|
||||
export enum AdBreakType {
|
||||
Midroll = 'midroll',
|
||||
Preroll = 'preroll',
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
Crunchyroll = 'crunchyroll',
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
thumbnail: Array<Thumbnail[]>;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
width: number;
|
||||
height: number;
|
||||
type: ThumbnailType;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export enum ThumbnailType {
|
||||
Thumbnail = 'thumbnail',
|
||||
}
|
||||
|
||||
export enum SubtitleLocale {
|
||||
ArSA = 'ar-SA',
|
||||
DeDE = 'de-DE',
|
||||
EnUS = 'en-US',
|
||||
Es419 = 'es-419',
|
||||
EsES = 'es-ES',
|
||||
FrFR = 'fr-FR',
|
||||
ItIT = 'it-IT',
|
||||
PtBR = 'pt-BR',
|
||||
RuRU = 'ru-RU',
|
||||
}
|
||||
export interface CrunchyEpisodeList {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: unknown;
|
||||
__actions__: unknown;
|
||||
total: number;
|
||||
items: Item[];
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
__class__: Class;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Links;
|
||||
__actions__: unknown;
|
||||
id: string;
|
||||
channel_id: ChannelID;
|
||||
series_id: string;
|
||||
series_title: string;
|
||||
series_slug_title: string;
|
||||
season_id: string;
|
||||
season_title: string;
|
||||
season_slug_title: string;
|
||||
season_number: number;
|
||||
episode: string;
|
||||
episode_number: number | null;
|
||||
sequence_number: number;
|
||||
production_episode_id: string;
|
||||
title: string;
|
||||
slug_title: string;
|
||||
description: string;
|
||||
next_episode_id?: string;
|
||||
next_episode_title?: string;
|
||||
hd_flag: boolean;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
episode_air_date: string;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_clip: boolean;
|
||||
seo_title: string;
|
||||
seo_description: string;
|
||||
season_tags: string[];
|
||||
available_offline: boolean;
|
||||
media_type: Class;
|
||||
slug: string;
|
||||
images: Images;
|
||||
duration_ms: number;
|
||||
ad_breaks: AdBreak[];
|
||||
is_premium_only: boolean;
|
||||
listing_id: string;
|
||||
subtitle_locales: SubtitleLocale[];
|
||||
playback?: string;
|
||||
availability_notes: string;
|
||||
available_date?: string;
|
||||
hide_season_title?: boolean;
|
||||
hide_season_number?: boolean;
|
||||
isSelected?: boolean;
|
||||
seq_id: string;
|
||||
}
|
||||
|
||||
export enum Class {
|
||||
Episode = 'episode',
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
ads: Ads;
|
||||
'episode/channel': Ads;
|
||||
'episode/next_episode'?: Ads;
|
||||
'episode/season': Ads;
|
||||
'episode/series': Ads;
|
||||
streams?: Ads;
|
||||
}
|
||||
|
||||
export interface Ads {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
type: AdBreakType;
|
||||
offset_ms: number;
|
||||
}
|
||||
|
||||
export enum AdBreakType {
|
||||
Midroll = 'midroll',
|
||||
Preroll = 'preroll',
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
Crunchyroll = 'crunchyroll',
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
thumbnail: Array<Thumbnail[]>;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
width: number;
|
||||
height: number;
|
||||
type: ThumbnailType;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export enum ThumbnailType {
|
||||
Thumbnail = 'thumbnail',
|
||||
}
|
||||
|
||||
export enum SubtitleLocale {
|
||||
ArSA = 'ar-SA',
|
||||
DeDE = 'de-DE',
|
||||
EnUS = 'en-US',
|
||||
Es419 = 'es-419',
|
||||
EsES = 'es-ES',
|
||||
FrFR = 'fr-FR',
|
||||
ItIT = 'it-IT',
|
||||
PtBR = 'pt-BR',
|
||||
RuRU = 'ru-RU',
|
||||
}
|
||||
|
|
|
|||
328
@types/crunchySearch.d.ts
vendored
328
@types/crunchySearch.d.ts
vendored
|
|
@ -1,165 +1,165 @@
|
|||
// Generated by https://quicktype.io
|
||||
|
||||
export interface CrunchySearch {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: CrunchySearchLinks;
|
||||
__actions__: unknown;
|
||||
total: number;
|
||||
items: CrunchySearchItem[];
|
||||
}
|
||||
|
||||
|
||||
export interface CrunchySearchLinks {
|
||||
continuation?: Continuation;
|
||||
}
|
||||
|
||||
export interface Continuation {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface CrunchySearchItem {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: CrunchySearchLinks;
|
||||
__actions__: unknown;
|
||||
type: string;
|
||||
total: number;
|
||||
items: ItemItem[];
|
||||
}
|
||||
|
||||
export interface ItemItem {
|
||||
__actions__: unknown;
|
||||
__class__: Class;
|
||||
__href__: string;
|
||||
__links__: PurpleLinks;
|
||||
channel_id: ChannelID;
|
||||
description: string;
|
||||
external_id: string;
|
||||
id: string;
|
||||
images: Images;
|
||||
linked_resource_key: string;
|
||||
new: boolean;
|
||||
new_content: boolean;
|
||||
promo_description: string;
|
||||
promo_title: string;
|
||||
search_metadata: SearchMetadata;
|
||||
series_metadata?: SeriesMetadata;
|
||||
slug: string;
|
||||
slug_title: string;
|
||||
title: string;
|
||||
type: ItemType;
|
||||
episode_metadata?: EpisodeMetadata;
|
||||
playback?: string;
|
||||
isSelected?: boolean;
|
||||
season_number?: string;
|
||||
is_premium_only?: boolean;
|
||||
hide_metadata?: boolean;
|
||||
seq_id?: string;
|
||||
f_num?: string;
|
||||
s_num?: string;
|
||||
ep_num?: string;
|
||||
last_public?: string;
|
||||
subtitle_locales?: string[];
|
||||
availability_notes?: string
|
||||
}
|
||||
|
||||
export enum Class {
|
||||
Panel = 'panel',
|
||||
}
|
||||
|
||||
export interface PurpleLinks {
|
||||
resource: Continuation;
|
||||
'resource/channel': Continuation;
|
||||
'episode/season'?: Continuation;
|
||||
'episode/series'?: Continuation;
|
||||
streams?: Continuation;
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
Crunchyroll = 'crunchyroll',
|
||||
}
|
||||
|
||||
export interface EpisodeMetadata {
|
||||
ad_breaks: AdBreak[];
|
||||
availability_notes: string;
|
||||
available_offline: boolean;
|
||||
duration_ms: number;
|
||||
episode: string;
|
||||
episode_air_date: string;
|
||||
episode_number: number;
|
||||
is_clip: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_mature: boolean;
|
||||
is_premium_only: boolean;
|
||||
is_subbed: boolean;
|
||||
mature_blocked: boolean;
|
||||
maturity_ratings: string[];
|
||||
season_id: string;
|
||||
season_number: number;
|
||||
season_slug_title: string;
|
||||
season_title: string;
|
||||
sequence_number: number;
|
||||
series_id: string;
|
||||
series_slug_title: string;
|
||||
series_title: string;
|
||||
subtitle_locales: string[];
|
||||
tenant_categories?: TenantCategory[];
|
||||
available_date?: string;
|
||||
free_available_date?: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
offset_ms: number;
|
||||
type: AdBreakType;
|
||||
}
|
||||
|
||||
export enum AdBreakType {
|
||||
Midroll = 'midroll',
|
||||
Preroll = 'preroll',
|
||||
}
|
||||
|
||||
export enum TenantCategory {
|
||||
Action = 'Action',
|
||||
Drama = 'Drama',
|
||||
SciFi = 'Sci-Fi',
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
poster_tall?: Array<PosterTall[]>;
|
||||
poster_wide?: Array<PosterTall[]>;
|
||||
thumbnail?: Array<PosterTall[]>;
|
||||
}
|
||||
|
||||
export interface PosterTall {
|
||||
height: number;
|
||||
source: string;
|
||||
type: PosterTallType;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export enum PosterTallType {
|
||||
PosterTall = 'poster_tall',
|
||||
PosterWide = 'poster_wide',
|
||||
Thumbnail = 'thumbnail',
|
||||
}
|
||||
|
||||
export interface SearchMetadata {
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface SeriesMetadata {
|
||||
availability_notes: string;
|
||||
episode_count: number;
|
||||
extended_description: string;
|
||||
is_dubbed: boolean;
|
||||
is_mature: boolean;
|
||||
is_simulcast: boolean;
|
||||
is_subbed: boolean;
|
||||
mature_blocked: boolean;
|
||||
maturity_ratings: string[];
|
||||
season_count: number;
|
||||
tenant_categories: TenantCategory[];
|
||||
// Generated by https://quicktype.io
|
||||
|
||||
export interface CrunchySearch {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: CrunchySearchLinks;
|
||||
__actions__: unknown;
|
||||
total: number;
|
||||
items: CrunchySearchItem[];
|
||||
}
|
||||
|
||||
|
||||
export interface CrunchySearchLinks {
|
||||
continuation?: Continuation;
|
||||
}
|
||||
|
||||
export interface Continuation {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface CrunchySearchItem {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: CrunchySearchLinks;
|
||||
__actions__: unknown;
|
||||
type: string;
|
||||
total: number;
|
||||
items: ItemItem[];
|
||||
}
|
||||
|
||||
export interface ItemItem {
|
||||
__actions__: unknown;
|
||||
__class__: Class;
|
||||
__href__: string;
|
||||
__links__: PurpleLinks;
|
||||
channel_id: ChannelID;
|
||||
description: string;
|
||||
external_id: string;
|
||||
id: string;
|
||||
images: Images;
|
||||
linked_resource_key: string;
|
||||
new: boolean;
|
||||
new_content: boolean;
|
||||
promo_description: string;
|
||||
promo_title: string;
|
||||
search_metadata: SearchMetadata;
|
||||
series_metadata?: SeriesMetadata;
|
||||
slug: string;
|
||||
slug_title: string;
|
||||
title: string;
|
||||
type: ItemType;
|
||||
episode_metadata?: EpisodeMetadata;
|
||||
playback?: string;
|
||||
isSelected?: boolean;
|
||||
season_number?: string;
|
||||
is_premium_only?: boolean;
|
||||
hide_metadata?: boolean;
|
||||
seq_id?: string;
|
||||
f_num?: string;
|
||||
s_num?: string;
|
||||
ep_num?: string;
|
||||
last_public?: string;
|
||||
subtitle_locales?: string[];
|
||||
availability_notes?: string
|
||||
}
|
||||
|
||||
export enum Class {
|
||||
Panel = 'panel',
|
||||
}
|
||||
|
||||
export interface PurpleLinks {
|
||||
resource: Continuation;
|
||||
'resource/channel': Continuation;
|
||||
'episode/season'?: Continuation;
|
||||
'episode/series'?: Continuation;
|
||||
streams?: Continuation;
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
Crunchyroll = 'crunchyroll',
|
||||
}
|
||||
|
||||
export interface EpisodeMetadata {
|
||||
ad_breaks: AdBreak[];
|
||||
availability_notes: string;
|
||||
available_offline: boolean;
|
||||
duration_ms: number;
|
||||
episode: string;
|
||||
episode_air_date: string;
|
||||
episode_number: number;
|
||||
is_clip: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_mature: boolean;
|
||||
is_premium_only: boolean;
|
||||
is_subbed: boolean;
|
||||
mature_blocked: boolean;
|
||||
maturity_ratings: string[];
|
||||
season_id: string;
|
||||
season_number: number;
|
||||
season_slug_title: string;
|
||||
season_title: string;
|
||||
sequence_number: number;
|
||||
series_id: string;
|
||||
series_slug_title: string;
|
||||
series_title: string;
|
||||
subtitle_locales: string[];
|
||||
tenant_categories?: TenantCategory[];
|
||||
available_date?: string;
|
||||
free_available_date?: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
offset_ms: number;
|
||||
type: AdBreakType;
|
||||
}
|
||||
|
||||
export enum AdBreakType {
|
||||
Midroll = 'midroll',
|
||||
Preroll = 'preroll',
|
||||
}
|
||||
|
||||
export enum TenantCategory {
|
||||
Action = 'Action',
|
||||
Drama = 'Drama',
|
||||
SciFi = 'Sci-Fi',
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
poster_tall?: Array<PosterTall[]>;
|
||||
poster_wide?: Array<PosterTall[]>;
|
||||
thumbnail?: Array<PosterTall[]>;
|
||||
}
|
||||
|
||||
export interface PosterTall {
|
||||
height: number;
|
||||
source: string;
|
||||
type: PosterTallType;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export enum PosterTallType {
|
||||
PosterTall = 'poster_tall',
|
||||
PosterWide = 'poster_wide',
|
||||
Thumbnail = 'thumbnail',
|
||||
}
|
||||
|
||||
export interface SearchMetadata {
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface SeriesMetadata {
|
||||
availability_notes: string;
|
||||
episode_count: number;
|
||||
extended_description: string;
|
||||
is_dubbed: boolean;
|
||||
is_mature: boolean;
|
||||
is_simulcast: boolean;
|
||||
is_subbed: boolean;
|
||||
mature_blocked: boolean;
|
||||
maturity_ratings: string[];
|
||||
season_count: number;
|
||||
tenant_categories: TenantCategory[];
|
||||
}
|
||||
256
@types/crunchyTypes.d.ts
vendored
256
@types/crunchyTypes.d.ts
vendored
|
|
@ -1,128 +1,128 @@
|
|||
import { HLSCallback } from 'hls-download';
|
||||
import { sxItem } from '../crunchy';
|
||||
import { LanguageItem } from '../modules/module.langsData';
|
||||
import { DownloadInfo } from './messageHandler';
|
||||
|
||||
export type CrunchyDownloadOptions = {
|
||||
hslang: string,
|
||||
kstream: number,
|
||||
novids?: boolean,
|
||||
x: number,
|
||||
q: number,
|
||||
fileName: string,
|
||||
numbers: number,
|
||||
partsize: number,
|
||||
callbackMaker?: (data: DownloadInfo) => HLSCallback,
|
||||
timeout: number,
|
||||
fsRetryTime: number,
|
||||
dlsubs: string[],
|
||||
skipsubs: boolean,
|
||||
mp4: boolean
|
||||
}
|
||||
|
||||
export type CurnchyMultiDownload = {
|
||||
dubLang: string[],
|
||||
all?: boolean,
|
||||
but?: boolean,
|
||||
e?: string
|
||||
}
|
||||
|
||||
export type CrunchyMuxOptions = {
|
||||
output: string,
|
||||
skipSubMux?: boolean
|
||||
novids?: boolean,
|
||||
mp4: boolean,
|
||||
forceMuxer?: 'ffmpeg'|'mkvmerge',
|
||||
nocleanup?: boolean
|
||||
}
|
||||
|
||||
export type CrunchyEpMeta = {
|
||||
data: {
|
||||
mediaId: string,
|
||||
lang?: LanguageItem,
|
||||
playback?: string
|
||||
}[],
|
||||
seasonTitle: string,
|
||||
episodeNumber: string,
|
||||
episodeTitle: string,
|
||||
seasonID: string,
|
||||
season: number,
|
||||
showID: string,
|
||||
e: string,
|
||||
image: string
|
||||
}
|
||||
|
||||
export type DownloadedMedia = {
|
||||
type: 'Video',
|
||||
lang: LanguageItem,
|
||||
path: string
|
||||
} | ({
|
||||
type: 'Subtitle'
|
||||
} & sxItem )
|
||||
|
||||
export type ParseItem = {
|
||||
__class__?: string;
|
||||
isSelected?: boolean,
|
||||
type?: string,
|
||||
id: string,
|
||||
title: string,
|
||||
playback?: string,
|
||||
season_number?: number|string,
|
||||
is_premium_only?: boolean,
|
||||
hide_metadata?: boolean,
|
||||
seq_id?: string,
|
||||
f_num?: string,
|
||||
s_num?: string
|
||||
external_id?: string,
|
||||
ep_num?: string
|
||||
last_public?: string,
|
||||
subtitle_locales?: string[],
|
||||
availability_notes?: string
|
||||
}
|
||||
|
||||
export interface SeriesSearch {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Actions;
|
||||
__actions__: Actions;
|
||||
total: number;
|
||||
items: SeriesSearchItem[];
|
||||
}
|
||||
|
||||
export interface SeriesSearchItem {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Links;
|
||||
__actions__: string[];
|
||||
id: string;
|
||||
channel_id: string;
|
||||
title: string;
|
||||
slug_title: string;
|
||||
series_id: string;
|
||||
season_number: number;
|
||||
is_complete: boolean;
|
||||
description: string;
|
||||
keywords: any[];
|
||||
season_tags: string[];
|
||||
images: Actions;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_simulcast: boolean;
|
||||
seo_title: string;
|
||||
seo_description: string;
|
||||
availability_notes: string;
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
'season/channel': Season;
|
||||
'season/episodes': Season;
|
||||
'season/series': Season;
|
||||
}
|
||||
|
||||
export interface Season {
|
||||
href: string;
|
||||
}
|
||||
import { HLSCallback } from 'hls-download';
|
||||
import { sxItem } from '../crunchy';
|
||||
import { LanguageItem } from '../modules/module.langsData';
|
||||
import { DownloadInfo } from './messageHandler';
|
||||
|
||||
export type CrunchyDownloadOptions = {
|
||||
hslang: string,
|
||||
kstream: number,
|
||||
novids?: boolean,
|
||||
x: number,
|
||||
q: number,
|
||||
fileName: string,
|
||||
numbers: number,
|
||||
partsize: number,
|
||||
callbackMaker?: (data: DownloadInfo) => HLSCallback,
|
||||
timeout: number,
|
||||
fsRetryTime: number,
|
||||
dlsubs: string[],
|
||||
skipsubs: boolean,
|
||||
mp4: boolean
|
||||
}
|
||||
|
||||
export type CurnchyMultiDownload = {
|
||||
dubLang: string[],
|
||||
all?: boolean,
|
||||
but?: boolean,
|
||||
e?: string
|
||||
}
|
||||
|
||||
export type CrunchyMuxOptions = {
|
||||
output: string,
|
||||
skipSubMux?: boolean
|
||||
novids?: boolean,
|
||||
mp4: boolean,
|
||||
forceMuxer?: 'ffmpeg'|'mkvmerge',
|
||||
nocleanup?: boolean
|
||||
}
|
||||
|
||||
export type CrunchyEpMeta = {
|
||||
data: {
|
||||
mediaId: string,
|
||||
lang?: LanguageItem,
|
||||
playback?: string
|
||||
}[],
|
||||
seasonTitle: string,
|
||||
episodeNumber: string,
|
||||
episodeTitle: string,
|
||||
seasonID: string,
|
||||
season: number,
|
||||
showID: string,
|
||||
e: string,
|
||||
image: string
|
||||
}
|
||||
|
||||
export type DownloadedMedia = {
|
||||
type: 'Video',
|
||||
lang: LanguageItem,
|
||||
path: string
|
||||
} | ({
|
||||
type: 'Subtitle'
|
||||
} & sxItem )
|
||||
|
||||
export type ParseItem = {
|
||||
__class__?: string;
|
||||
isSelected?: boolean,
|
||||
type?: string,
|
||||
id: string,
|
||||
title: string,
|
||||
playback?: string,
|
||||
season_number?: number|string,
|
||||
is_premium_only?: boolean,
|
||||
hide_metadata?: boolean,
|
||||
seq_id?: string,
|
||||
f_num?: string,
|
||||
s_num?: string
|
||||
external_id?: string,
|
||||
ep_num?: string
|
||||
last_public?: string,
|
||||
subtitle_locales?: string[],
|
||||
availability_notes?: string
|
||||
}
|
||||
|
||||
export interface SeriesSearch {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Actions;
|
||||
__actions__: Actions;
|
||||
total: number;
|
||||
items: SeriesSearchItem[];
|
||||
}
|
||||
|
||||
export interface SeriesSearchItem {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Links;
|
||||
__actions__: string[];
|
||||
id: string;
|
||||
channel_id: string;
|
||||
title: string;
|
||||
slug_title: string;
|
||||
series_id: string;
|
||||
season_number: number;
|
||||
is_complete: boolean;
|
||||
description: string;
|
||||
keywords: any[];
|
||||
season_tags: string[];
|
||||
images: Actions;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_simulcast: boolean;
|
||||
seo_title: string;
|
||||
seo_description: string;
|
||||
availability_notes: string;
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
'season/channel': Season;
|
||||
'season/episodes': Season;
|
||||
'season/series': Season;
|
||||
}
|
||||
|
||||
export interface Season {
|
||||
href: string;
|
||||
}
|
||||
|
|
|
|||
10
@types/downloadedFile.d.ts
vendored
10
@types/downloadedFile.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
import { LanguageItem } from '../modules/module.langsData';
|
||||
|
||||
export type DownloadedFile = {
|
||||
path: string,
|
||||
lang: LanguageItem
|
||||
import { LanguageItem } from '../modules/module.langsData';
|
||||
|
||||
export type DownloadedFile = {
|
||||
path: string,
|
||||
lang: LanguageItem
|
||||
}
|
||||
782
@types/episode.d.ts
vendored
782
@types/episode.d.ts
vendored
|
|
@ -1,391 +1,391 @@
|
|||
// 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<string[]>;
|
||||
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 type Next = Record<string, unknown>
|
||||
|
||||
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;
|
||||
}
|
||||
// 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<string[]>;
|
||||
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 type Next = Record<string, unknown>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
68
@types/funiSearch.d.ts
vendored
68
@types/funiSearch.d.ts
vendored
|
|
@ -1,34 +1,34 @@
|
|||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
|
|
|||
30
@types/funiTypes.d.ts
vendored
30
@types/funiTypes.d.ts
vendored
|
|
@ -1,16 +1,16 @@
|
|||
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
|
||||
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
|
||||
}
|
||||
212
@types/github.d.ts
vendored
212
@types/github.d.ts
vendored
|
|
@ -1,106 +1,106 @@
|
|||
export type GithubTag = {
|
||||
name: string,
|
||||
zipball_url: string,
|
||||
tarball_url: string,
|
||||
commit: {
|
||||
sha: string,
|
||||
url: string
|
||||
},
|
||||
node_id: string
|
||||
}
|
||||
|
||||
export interface TagCompare {
|
||||
url: string;
|
||||
html_url: string;
|
||||
permalink_url: string;
|
||||
diff_url: string;
|
||||
patch_url: string;
|
||||
base_commit: BaseCommitClass;
|
||||
merge_base_commit: BaseCommitClass;
|
||||
status: string;
|
||||
ahead_by: number;
|
||||
behind_by: number;
|
||||
total_commits: number;
|
||||
commits: BaseCommitClass[];
|
||||
files: File[];
|
||||
}
|
||||
|
||||
export interface BaseCommitClass {
|
||||
sha: string;
|
||||
node_id: string;
|
||||
commit: BaseCommitCommit;
|
||||
url: string;
|
||||
html_url: string;
|
||||
comments_url: string;
|
||||
author: BaseCommitAuthor;
|
||||
committer: BaseCommitAuthor;
|
||||
parents: Parent[];
|
||||
}
|
||||
|
||||
export interface BaseCommitAuthor {
|
||||
login: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: boolean;
|
||||
}
|
||||
|
||||
export interface BaseCommitCommit {
|
||||
author: PurpleAuthor;
|
||||
committer: PurpleAuthor;
|
||||
message: string;
|
||||
tree: Tree;
|
||||
url: string;
|
||||
comment_count: number;
|
||||
verification: Verification;
|
||||
}
|
||||
|
||||
export interface PurpleAuthor {
|
||||
name: string;
|
||||
email: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface Tree {
|
||||
sha: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Verification {
|
||||
verified: boolean;
|
||||
reason: string;
|
||||
signature: string;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface Parent {
|
||||
sha: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
export interface File {
|
||||
sha: string;
|
||||
filename: string;
|
||||
status: string;
|
||||
additions: number;
|
||||
deletions: number;
|
||||
changes: number;
|
||||
blob_url: string;
|
||||
raw_url: string;
|
||||
contents_url: string;
|
||||
patch: string;
|
||||
}
|
||||
export type GithubTag = {
|
||||
name: string,
|
||||
zipball_url: string,
|
||||
tarball_url: string,
|
||||
commit: {
|
||||
sha: string,
|
||||
url: string
|
||||
},
|
||||
node_id: string
|
||||
}
|
||||
|
||||
export interface TagCompare {
|
||||
url: string;
|
||||
html_url: string;
|
||||
permalink_url: string;
|
||||
diff_url: string;
|
||||
patch_url: string;
|
||||
base_commit: BaseCommitClass;
|
||||
merge_base_commit: BaseCommitClass;
|
||||
status: string;
|
||||
ahead_by: number;
|
||||
behind_by: number;
|
||||
total_commits: number;
|
||||
commits: BaseCommitClass[];
|
||||
files: File[];
|
||||
}
|
||||
|
||||
export interface BaseCommitClass {
|
||||
sha: string;
|
||||
node_id: string;
|
||||
commit: BaseCommitCommit;
|
||||
url: string;
|
||||
html_url: string;
|
||||
comments_url: string;
|
||||
author: BaseCommitAuthor;
|
||||
committer: BaseCommitAuthor;
|
||||
parents: Parent[];
|
||||
}
|
||||
|
||||
export interface BaseCommitAuthor {
|
||||
login: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: boolean;
|
||||
}
|
||||
|
||||
export interface BaseCommitCommit {
|
||||
author: PurpleAuthor;
|
||||
committer: PurpleAuthor;
|
||||
message: string;
|
||||
tree: Tree;
|
||||
url: string;
|
||||
comment_count: number;
|
||||
verification: Verification;
|
||||
}
|
||||
|
||||
export interface PurpleAuthor {
|
||||
name: string;
|
||||
email: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface Tree {
|
||||
sha: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Verification {
|
||||
verified: boolean;
|
||||
reason: string;
|
||||
signature: string;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface Parent {
|
||||
sha: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
export interface File {
|
||||
sha: string;
|
||||
filename: string;
|
||||
status: string;
|
||||
additions: number;
|
||||
deletions: number;
|
||||
changes: number;
|
||||
blob_url: string;
|
||||
raw_url: string;
|
||||
contents_url: string;
|
||||
patch: string;
|
||||
}
|
||||
|
|
|
|||
58
@types/hls-download.d.ts
vendored
58
@types/hls-download.d.ts
vendored
|
|
@ -1,30 +1,30 @@
|
|||
declare module 'hls-download' {
|
||||
import type { ProgressData } from './messageHandler';
|
||||
export type HLSCallback = (data: ProgressData) => unknown;
|
||||
export default class hlsDownload {
|
||||
constructor(options: {
|
||||
m3u8json: {
|
||||
segments: Record<string, unknown>[],
|
||||
mediaSequence?: number,
|
||||
},
|
||||
output?: string,
|
||||
threads?: number,
|
||||
retries?: number,
|
||||
offset?: number,
|
||||
baseurl?: string,
|
||||
proxy?: string,
|
||||
skipInit?: boolean,
|
||||
timeout?: number,
|
||||
fsRetryTime?: number,
|
||||
callback?: HLSCallback
|
||||
})
|
||||
async download() : Promise<{
|
||||
ok: boolean,
|
||||
parts: {
|
||||
first: number,
|
||||
total: number,
|
||||
compleated: number
|
||||
}
|
||||
}>
|
||||
}
|
||||
declare module 'hls-download' {
|
||||
import type { ProgressData } from './messageHandler';
|
||||
export type HLSCallback = (data: ProgressData) => unknown;
|
||||
export default class hlsDownload {
|
||||
constructor(options: {
|
||||
m3u8json: {
|
||||
segments: Record<string, unknown>[],
|
||||
mediaSequence?: number,
|
||||
},
|
||||
output?: string,
|
||||
threads?: number,
|
||||
retries?: number,
|
||||
offset?: number,
|
||||
baseurl?: string,
|
||||
proxy?: string,
|
||||
skipInit?: boolean,
|
||||
timeout?: number,
|
||||
fsRetryTime?: number,
|
||||
callback?: HLSCallback
|
||||
})
|
||||
async download() : Promise<{
|
||||
ok: boolean,
|
||||
parts: {
|
||||
first: number,
|
||||
total: number,
|
||||
compleated: number
|
||||
}
|
||||
}>
|
||||
}
|
||||
}
|
||||
16
@types/iso639.d.ts
vendored
16
@types/iso639.d.ts
vendored
|
|
@ -1,9 +1,9 @@
|
|||
declare module 'iso-639' {
|
||||
export type iso639Type = {
|
||||
[key: string]: {
|
||||
'639-1'?: string,
|
||||
'639-2'?: string
|
||||
}
|
||||
}
|
||||
export const iso_639_2: iso639Type;
|
||||
declare module 'iso-639' {
|
||||
export type iso639Type = {
|
||||
[key: string]: {
|
||||
'639-1'?: string,
|
||||
'639-2'?: string
|
||||
}
|
||||
}
|
||||
export const iso_639_2: iso639Type;
|
||||
}
|
||||
336
@types/items.d.ts
vendored
336
@types/items.d.ts
vendored
|
|
@ -1,169 +1,169 @@
|
|||
export interface Item {
|
||||
// Added later
|
||||
id: string,
|
||||
id_split: (number|string)[]
|
||||
// Added from the start
|
||||
mostRecentSvodJpnUs: MostRecentSvodJpnUs;
|
||||
synopsis: string;
|
||||
mediaCategory: ContentType;
|
||||
mostRecentSvodUsEndTimestamp: number;
|
||||
quality: QualityClass;
|
||||
genres: Genre[];
|
||||
titleImages: TitleImages;
|
||||
engAllTerritoryAvail: EngAllTerritoryAvail;
|
||||
thumb: string;
|
||||
mostRecentSvodJpnAllTerrStartTimestamp: number;
|
||||
title: string;
|
||||
starRating: number;
|
||||
primaryAvail: PrimaryAvail;
|
||||
access: Access[];
|
||||
version: Version[];
|
||||
mostRecentSvodJpnAllTerrEndTimestamp: number;
|
||||
itemId: number;
|
||||
versionAudio: VersionAudio;
|
||||
contentType: ContentType;
|
||||
mostRecentSvodUsStartTimestamp: number;
|
||||
poster: string;
|
||||
mostRecentSvodEngAllTerrEndTimestamp: number;
|
||||
mostRecentSvodJpnUsStartTimestamp: number;
|
||||
mostRecentSvodJpnUsEndTimestamp: number;
|
||||
mostRecentSvodStartTimestamp: number;
|
||||
mostRecentSvod: MostRecent;
|
||||
altAvail: AltAvail;
|
||||
ids: IDs;
|
||||
mostRecentSvodUs: MostRecent;
|
||||
item: Item;
|
||||
mostRecentSvodEngAllTerrStartTimestamp: number;
|
||||
audio: string[];
|
||||
mostRecentAvod: MostRecent;
|
||||
}
|
||||
|
||||
export enum ContentType {
|
||||
Episode = 'episode',
|
||||
Ova = 'ova',
|
||||
}
|
||||
|
||||
export interface IDs {
|
||||
externalShowId: ID;
|
||||
externalSeasonId: ExternalSeasonID;
|
||||
externalEpisodeId: string;
|
||||
externalAsianId?: string
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
seasonTitle: string;
|
||||
seasonId: number;
|
||||
episodeOrder: number;
|
||||
episodeSlug: string;
|
||||
created: Date;
|
||||
titleSlug: string;
|
||||
episodeNum: string;
|
||||
episodeId: number;
|
||||
titleId: number;
|
||||
seasonNum: string;
|
||||
ratings: Array<string[]>;
|
||||
showImage: string;
|
||||
titleName: string;
|
||||
runtime: string;
|
||||
episodeName: string;
|
||||
seasonOrder: number;
|
||||
titleExternalId: string;
|
||||
}
|
||||
|
||||
|
||||
export interface MostRecent {
|
||||
image?: string;
|
||||
siblingStartTimestamp?: string;
|
||||
devices?: Device[];
|
||||
availId?: number;
|
||||
distributor?: Distributor;
|
||||
quality?: MostRecentAvodQuality;
|
||||
endTimestamp?: string;
|
||||
mediaCategory?: ContentType;
|
||||
isPromo?: boolean;
|
||||
siblingType?: Purchase;
|
||||
version?: Version;
|
||||
territory?: Territory;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
versionId?: number;
|
||||
tier?: Device | null;
|
||||
purchase?: Purchase;
|
||||
startTimestamp?: string;
|
||||
language?: Audio;
|
||||
itemTitle?: string;
|
||||
ids?: MostRecentAvodIDS;
|
||||
experience?: number;
|
||||
siblingEndTimestamp?: string;
|
||||
item?: Item;
|
||||
subscriptionRequired?: boolean;
|
||||
purchased?: boolean;
|
||||
}
|
||||
|
||||
export interface MostRecentAvodIDS {
|
||||
externalSeasonId: ExternalSeasonID;
|
||||
externalAsianId: null;
|
||||
externalShowId: ID;
|
||||
externalEpisodeId: string;
|
||||
externalEnglishId: string;
|
||||
externalAlphaId: string;
|
||||
}
|
||||
|
||||
export enum Purchase {
|
||||
AVOD = 'A-VOD',
|
||||
Dfov = 'DFOV',
|
||||
Est = 'EST',
|
||||
Svod = 'SVOD',
|
||||
}
|
||||
|
||||
export enum Version {
|
||||
Simulcast = 'Simulcast',
|
||||
Uncut = 'Uncut',
|
||||
}
|
||||
|
||||
export type MostRecentSvodJpnUs = Record<string, any>
|
||||
|
||||
export interface QualityClass {
|
||||
quality: QualityQuality;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export enum QualityQuality {
|
||||
HD = 'HD',
|
||||
SD = 'SD',
|
||||
}
|
||||
|
||||
export interface TitleImages {
|
||||
showThumbnail: string;
|
||||
showBackgroundSite: string;
|
||||
showDetailHeaderDesktop: string;
|
||||
continueWatchingDesktop: string;
|
||||
showDetailHeroSite: string;
|
||||
appleHorizontalBannerShow: string;
|
||||
backgroundImageXbox_360: string;
|
||||
applePosterCover: string;
|
||||
showDetailBoxArtTablet: string;
|
||||
featuredShowBackgroundTablet: string;
|
||||
backgroundImageAppletvfiretv: string;
|
||||
newShowDetailHero: string;
|
||||
showDetailHeroDesktop: string;
|
||||
showKeyart: string;
|
||||
continueWatchingMobile: string;
|
||||
featuredSpotlightShowPhone: string;
|
||||
appleHorizontalBannerMovie: string;
|
||||
featuredSpotlightShowTablet: string;
|
||||
showDetailBoxArtPhone: string;
|
||||
featuredShowBackgroundPhone: string;
|
||||
appleSquareCover: string;
|
||||
backgroundVideo: string;
|
||||
showMasterKeyArt: string;
|
||||
newShowDetailHeroPhone: string;
|
||||
showDetailBoxArtXbox_360: string;
|
||||
showDetailHeaderMobile: string;
|
||||
showLogo: string;
|
||||
}
|
||||
|
||||
export interface VersionAudio {
|
||||
Uncut?: Audio[];
|
||||
Simulcast: Audio[];
|
||||
export interface Item {
|
||||
// Added later
|
||||
id: string,
|
||||
id_split: (number|string)[]
|
||||
// Added from the start
|
||||
mostRecentSvodJpnUs: MostRecentSvodJpnUs;
|
||||
synopsis: string;
|
||||
mediaCategory: ContentType;
|
||||
mostRecentSvodUsEndTimestamp: number;
|
||||
quality: QualityClass;
|
||||
genres: Genre[];
|
||||
titleImages: TitleImages;
|
||||
engAllTerritoryAvail: EngAllTerritoryAvail;
|
||||
thumb: string;
|
||||
mostRecentSvodJpnAllTerrStartTimestamp: number;
|
||||
title: string;
|
||||
starRating: number;
|
||||
primaryAvail: PrimaryAvail;
|
||||
access: Access[];
|
||||
version: Version[];
|
||||
mostRecentSvodJpnAllTerrEndTimestamp: number;
|
||||
itemId: number;
|
||||
versionAudio: VersionAudio;
|
||||
contentType: ContentType;
|
||||
mostRecentSvodUsStartTimestamp: number;
|
||||
poster: string;
|
||||
mostRecentSvodEngAllTerrEndTimestamp: number;
|
||||
mostRecentSvodJpnUsStartTimestamp: number;
|
||||
mostRecentSvodJpnUsEndTimestamp: number;
|
||||
mostRecentSvodStartTimestamp: number;
|
||||
mostRecentSvod: MostRecent;
|
||||
altAvail: AltAvail;
|
||||
ids: IDs;
|
||||
mostRecentSvodUs: MostRecent;
|
||||
item: Item;
|
||||
mostRecentSvodEngAllTerrStartTimestamp: number;
|
||||
audio: string[];
|
||||
mostRecentAvod: MostRecent;
|
||||
}
|
||||
|
||||
export enum ContentType {
|
||||
Episode = 'episode',
|
||||
Ova = 'ova',
|
||||
}
|
||||
|
||||
export interface IDs {
|
||||
externalShowId: ID;
|
||||
externalSeasonId: ExternalSeasonID;
|
||||
externalEpisodeId: string;
|
||||
externalAsianId?: string
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
seasonTitle: string;
|
||||
seasonId: number;
|
||||
episodeOrder: number;
|
||||
episodeSlug: string;
|
||||
created: Date;
|
||||
titleSlug: string;
|
||||
episodeNum: string;
|
||||
episodeId: number;
|
||||
titleId: number;
|
||||
seasonNum: string;
|
||||
ratings: Array<string[]>;
|
||||
showImage: string;
|
||||
titleName: string;
|
||||
runtime: string;
|
||||
episodeName: string;
|
||||
seasonOrder: number;
|
||||
titleExternalId: string;
|
||||
}
|
||||
|
||||
|
||||
export interface MostRecent {
|
||||
image?: string;
|
||||
siblingStartTimestamp?: string;
|
||||
devices?: Device[];
|
||||
availId?: number;
|
||||
distributor?: Distributor;
|
||||
quality?: MostRecentAvodQuality;
|
||||
endTimestamp?: string;
|
||||
mediaCategory?: ContentType;
|
||||
isPromo?: boolean;
|
||||
siblingType?: Purchase;
|
||||
version?: Version;
|
||||
territory?: Territory;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
versionId?: number;
|
||||
tier?: Device | null;
|
||||
purchase?: Purchase;
|
||||
startTimestamp?: string;
|
||||
language?: Audio;
|
||||
itemTitle?: string;
|
||||
ids?: MostRecentAvodIDS;
|
||||
experience?: number;
|
||||
siblingEndTimestamp?: string;
|
||||
item?: Item;
|
||||
subscriptionRequired?: boolean;
|
||||
purchased?: boolean;
|
||||
}
|
||||
|
||||
export interface MostRecentAvodIDS {
|
||||
externalSeasonId: ExternalSeasonID;
|
||||
externalAsianId: null;
|
||||
externalShowId: ID;
|
||||
externalEpisodeId: string;
|
||||
externalEnglishId: string;
|
||||
externalAlphaId: string;
|
||||
}
|
||||
|
||||
export enum Purchase {
|
||||
AVOD = 'A-VOD',
|
||||
Dfov = 'DFOV',
|
||||
Est = 'EST',
|
||||
Svod = 'SVOD',
|
||||
}
|
||||
|
||||
export enum Version {
|
||||
Simulcast = 'Simulcast',
|
||||
Uncut = 'Uncut',
|
||||
}
|
||||
|
||||
export type MostRecentSvodJpnUs = Record<string, any>
|
||||
|
||||
export interface QualityClass {
|
||||
quality: QualityQuality;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export enum QualityQuality {
|
||||
HD = 'HD',
|
||||
SD = 'SD',
|
||||
}
|
||||
|
||||
export interface TitleImages {
|
||||
showThumbnail: string;
|
||||
showBackgroundSite: string;
|
||||
showDetailHeaderDesktop: string;
|
||||
continueWatchingDesktop: string;
|
||||
showDetailHeroSite: string;
|
||||
appleHorizontalBannerShow: string;
|
||||
backgroundImageXbox_360: string;
|
||||
applePosterCover: string;
|
||||
showDetailBoxArtTablet: string;
|
||||
featuredShowBackgroundTablet: string;
|
||||
backgroundImageAppletvfiretv: string;
|
||||
newShowDetailHero: string;
|
||||
showDetailHeroDesktop: string;
|
||||
showKeyart: string;
|
||||
continueWatchingMobile: string;
|
||||
featuredSpotlightShowPhone: string;
|
||||
appleHorizontalBannerMovie: string;
|
||||
featuredSpotlightShowTablet: string;
|
||||
showDetailBoxArtPhone: string;
|
||||
featuredShowBackgroundPhone: string;
|
||||
appleSquareCover: string;
|
||||
backgroundVideo: string;
|
||||
showMasterKeyArt: string;
|
||||
newShowDetailHeroPhone: string;
|
||||
showDetailBoxArtXbox_360: string;
|
||||
showDetailHeaderMobile: string;
|
||||
showLogo: string;
|
||||
}
|
||||
|
||||
export interface VersionAudio {
|
||||
Uncut?: Audio[];
|
||||
Simulcast: Audio[];
|
||||
}
|
||||
96
@types/m3u8-parsed.d.ts
vendored
96
@types/m3u8-parsed.d.ts
vendored
|
|
@ -1,49 +1,49 @@
|
|||
declare module 'm3u8-parsed' {
|
||||
export type M3U8 = {
|
||||
allowCache: boolean,
|
||||
discontinuityStarts: [],
|
||||
segments: {
|
||||
duration: number,
|
||||
byterange?: {
|
||||
length: number,
|
||||
offset: number
|
||||
},
|
||||
uri: string,
|
||||
key: {
|
||||
method: string,
|
||||
uri: string,
|
||||
},
|
||||
timeline: number
|
||||
}[],
|
||||
version: number,
|
||||
mediaGroups: {
|
||||
[type: string]: {
|
||||
[index: string]: {
|
||||
[language: string]: {
|
||||
default: boolean,
|
||||
autoselect: boolean,
|
||||
language: string,
|
||||
uri: string
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
playlists: {
|
||||
uri: string,
|
||||
timeline: number,
|
||||
attributes: {
|
||||
'CLOSED-CAPTIONS': string,
|
||||
'AUDIO': string,
|
||||
'FRAME-RATE': number,
|
||||
'RESOLUTION': {
|
||||
width: number,
|
||||
height: number
|
||||
},
|
||||
'CODECS': string,
|
||||
'AVERAGE-BANDWIDTH': string,
|
||||
'BANDWIDTH': number
|
||||
}
|
||||
}[],
|
||||
}
|
||||
export default function (data: string): M3U8;
|
||||
declare module 'm3u8-parsed' {
|
||||
export type M3U8 = {
|
||||
allowCache: boolean,
|
||||
discontinuityStarts: [],
|
||||
segments: {
|
||||
duration: number,
|
||||
byterange?: {
|
||||
length: number,
|
||||
offset: number
|
||||
},
|
||||
uri: string,
|
||||
key: {
|
||||
method: string,
|
||||
uri: string,
|
||||
},
|
||||
timeline: number
|
||||
}[],
|
||||
version: number,
|
||||
mediaGroups: {
|
||||
[type: string]: {
|
||||
[index: string]: {
|
||||
[language: string]: {
|
||||
default: boolean,
|
||||
autoselect: boolean,
|
||||
language: string,
|
||||
uri: string
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
playlists: {
|
||||
uri: string,
|
||||
timeline: number,
|
||||
attributes: {
|
||||
'CLOSED-CAPTIONS': string,
|
||||
'AUDIO': string,
|
||||
'FRAME-RATE': number,
|
||||
'RESOLUTION': {
|
||||
width: number,
|
||||
height: number
|
||||
},
|
||||
'CODECS': string,
|
||||
'AVERAGE-BANDWIDTH': string,
|
||||
'BANDWIDTH': number
|
||||
}
|
||||
}[],
|
||||
}
|
||||
export default function (data: string): M3U8;
|
||||
}
|
||||
242
@types/messageHandler.d.ts
vendored
242
@types/messageHandler.d.ts
vendored
|
|
@ -1,122 +1,122 @@
|
|||
import { HLSCallback } from 'hls-download';
|
||||
import type { FunimationSearch } from './funiSearch';
|
||||
import type { AvailableMuxer } from '../modules/module.args';
|
||||
|
||||
export interface MessageHandler {
|
||||
auth: (data: AuthData) => Promise<AuthResponse>;
|
||||
checkToken: () => Promise<CheckTokenResponse>;
|
||||
search: (data: SearchData) => Promise<SearchResponse>,
|
||||
availableDubCodes: () => Promise<string[]>,
|
||||
handleDefault: (name: string) => Promise<any>,
|
||||
resolveItems: (data: ResolveItemsData) => Promise<ResponseBase<QueueItem[]>>,
|
||||
listEpisodes: (id: string) => Promise<EpisodeListResponse>,
|
||||
downloadItem: (data) => void,
|
||||
isDownloading: () => boolean,
|
||||
writeToClipboard: (text: string) => void,
|
||||
openFolder: (path: string[]) => void
|
||||
}
|
||||
|
||||
export type QueueItem = {
|
||||
title: string,
|
||||
episode: string,
|
||||
ids: string[],
|
||||
fileName: string,
|
||||
parent: {
|
||||
title: string,
|
||||
season: string
|
||||
},
|
||||
q: number,
|
||||
dubLang: string[],
|
||||
}
|
||||
|
||||
export type ResolveItemsData = {
|
||||
id: string,
|
||||
dubLang: string[],
|
||||
all: boolean,
|
||||
but: boolean,
|
||||
e: string,
|
||||
fileName: string,
|
||||
q: number
|
||||
}
|
||||
|
||||
export type SearchResponseItem = {
|
||||
image: string,
|
||||
name: string,
|
||||
desc?: string,
|
||||
id: string,
|
||||
lang?: string[],
|
||||
rating: number
|
||||
};
|
||||
|
||||
export type Episode = {
|
||||
e: string,
|
||||
lang: string[],
|
||||
name: string,
|
||||
season: string,
|
||||
seasonTitle: string,
|
||||
episode: string,
|
||||
id: string,
|
||||
img: string,
|
||||
description: string,
|
||||
time: string
|
||||
}
|
||||
|
||||
export type SearchResponse = ResponseBase<SearchResponseItem[]>
|
||||
export type EpisodeListResponse = ResponseBase<Episode[]>
|
||||
|
||||
export type FuniEpisodeData = {
|
||||
title: string,
|
||||
episode: string,
|
||||
episodeID: string,
|
||||
seasonTitle: string,
|
||||
seasonNumber: string,
|
||||
};
|
||||
|
||||
export type AuthData = { username: string, password: string };
|
||||
export type SearchData = { search: string, page?: number, 'search-type'?: string, 'search-locale'?: string };
|
||||
export type FuniGetShowData = { id: number, e?: string, but: boolean, all: boolean };
|
||||
export type FuniGetEpisodeData = { subs: FuniSubsData, fnSlug: FuniEpisodeData, simul?: boolean; dubLang: string[], s: string }
|
||||
export type FuniStreamData = { callbackMaker?: (data: DownloadInfo) => HLSCallback, q: number, x: number, fileName: string, numbers: number, novids?: boolean,
|
||||
timeout: number, partsize: number, fsRetryTime: number, noaudio?: boolean, mp4: boolean, ass: boolean, fontSize: number, fontName?: string, skipmux?: boolean,
|
||||
forceMuxer: AvailableMuxer | undefined, simul: boolean, skipSubMux: boolean, nocleanup: boolean }
|
||||
export type FuniSubsData = { nosubs?: boolean, sub: boolean, dlsubs: string[] }
|
||||
export type DownloadData = { id: string, e: string, dubLang: string[], fileName: string, q: number }
|
||||
|
||||
export type AuthResponse = ResponseBase<undefined>;
|
||||
export type FuniSearchReponse = ResponseBase<FunimationSearch>;
|
||||
export type FuniShowResponse = ResponseBase<FuniEpisodeData[]>;
|
||||
export type FuniGetEpisodeResponse = ResponseBase<undefined>;
|
||||
export type CheckTokenResponse = ResponseBase<undefined>;
|
||||
|
||||
|
||||
export type ResponseBase<T> = ({
|
||||
isOk: true,
|
||||
value: T
|
||||
} | {
|
||||
isOk: false,
|
||||
reason: Error
|
||||
});
|
||||
|
||||
export type ProgressData = {
|
||||
total: number,
|
||||
cur: number,
|
||||
percent: number|string,
|
||||
time: number,
|
||||
downloadSpeed: number
|
||||
};
|
||||
|
||||
export type PossibleMessanges = keyof ServiceHandler;
|
||||
|
||||
export type DownloadInfo = {
|
||||
image: string,
|
||||
parent: {
|
||||
title: string
|
||||
},
|
||||
title: string,
|
||||
fileName: string
|
||||
}
|
||||
|
||||
export type ExtendedProgress = {
|
||||
progress: ProgressData,
|
||||
downloadInfo: DownloadInfo
|
||||
import { HLSCallback } from 'hls-download';
|
||||
import type { FunimationSearch } from './funiSearch';
|
||||
import type { AvailableMuxer } from '../modules/module.args';
|
||||
|
||||
export interface MessageHandler {
|
||||
auth: (data: AuthData) => Promise<AuthResponse>;
|
||||
checkToken: () => Promise<CheckTokenResponse>;
|
||||
search: (data: SearchData) => Promise<SearchResponse>,
|
||||
availableDubCodes: () => Promise<string[]>,
|
||||
handleDefault: (name: string) => Promise<any>,
|
||||
resolveItems: (data: ResolveItemsData) => Promise<ResponseBase<QueueItem[]>>,
|
||||
listEpisodes: (id: string) => Promise<EpisodeListResponse>,
|
||||
downloadItem: (data) => void,
|
||||
isDownloading: () => boolean,
|
||||
writeToClipboard: (text: string) => void,
|
||||
openFolder: (path: string[]) => void
|
||||
}
|
||||
|
||||
export type QueueItem = {
|
||||
title: string,
|
||||
episode: string,
|
||||
ids: string[],
|
||||
fileName: string,
|
||||
parent: {
|
||||
title: string,
|
||||
season: string
|
||||
},
|
||||
q: number,
|
||||
dubLang: string[],
|
||||
}
|
||||
|
||||
export type ResolveItemsData = {
|
||||
id: string,
|
||||
dubLang: string[],
|
||||
all: boolean,
|
||||
but: boolean,
|
||||
e: string,
|
||||
fileName: string,
|
||||
q: number
|
||||
}
|
||||
|
||||
export type SearchResponseItem = {
|
||||
image: string,
|
||||
name: string,
|
||||
desc?: string,
|
||||
id: string,
|
||||
lang?: string[],
|
||||
rating: number
|
||||
};
|
||||
|
||||
export type Episode = {
|
||||
e: string,
|
||||
lang: string[],
|
||||
name: string,
|
||||
season: string,
|
||||
seasonTitle: string,
|
||||
episode: string,
|
||||
id: string,
|
||||
img: string,
|
||||
description: string,
|
||||
time: string
|
||||
}
|
||||
|
||||
export type SearchResponse = ResponseBase<SearchResponseItem[]>
|
||||
export type EpisodeListResponse = ResponseBase<Episode[]>
|
||||
|
||||
export type FuniEpisodeData = {
|
||||
title: string,
|
||||
episode: string,
|
||||
episodeID: string,
|
||||
seasonTitle: string,
|
||||
seasonNumber: string,
|
||||
};
|
||||
|
||||
export type AuthData = { username: string, password: string };
|
||||
export type SearchData = { search: string, page?: number, 'search-type'?: string, 'search-locale'?: string };
|
||||
export type FuniGetShowData = { id: number, e?: string, but: boolean, all: boolean };
|
||||
export type FuniGetEpisodeData = { subs: FuniSubsData, fnSlug: FuniEpisodeData, simul?: boolean; dubLang: string[], s: string }
|
||||
export type FuniStreamData = { callbackMaker?: (data: DownloadInfo) => HLSCallback, q: number, x: number, fileName: string, numbers: number, novids?: boolean,
|
||||
timeout: number, partsize: number, fsRetryTime: number, noaudio?: boolean, mp4: boolean, ass: boolean, fontSize: number, fontName?: string, skipmux?: boolean,
|
||||
forceMuxer: AvailableMuxer | undefined, simul: boolean, skipSubMux: boolean, nocleanup: boolean }
|
||||
export type FuniSubsData = { nosubs?: boolean, sub: boolean, dlsubs: string[] }
|
||||
export type DownloadData = { id: string, e: string, dubLang: string[], fileName: string, q: number }
|
||||
|
||||
export type AuthResponse = ResponseBase<undefined>;
|
||||
export type FuniSearchReponse = ResponseBase<FunimationSearch>;
|
||||
export type FuniShowResponse = ResponseBase<FuniEpisodeData[]>;
|
||||
export type FuniGetEpisodeResponse = ResponseBase<undefined>;
|
||||
export type CheckTokenResponse = ResponseBase<undefined>;
|
||||
|
||||
|
||||
export type ResponseBase<T> = ({
|
||||
isOk: true,
|
||||
value: T
|
||||
} | {
|
||||
isOk: false,
|
||||
reason: Error
|
||||
});
|
||||
|
||||
export type ProgressData = {
|
||||
total: number,
|
||||
cur: number,
|
||||
percent: number|string,
|
||||
time: number,
|
||||
downloadSpeed: number
|
||||
};
|
||||
|
||||
export type PossibleMessanges = keyof ServiceHandler;
|
||||
|
||||
export type DownloadInfo = {
|
||||
image: string,
|
||||
parent: {
|
||||
title: string
|
||||
},
|
||||
title: string,
|
||||
fileName: string
|
||||
}
|
||||
|
||||
export type ExtendedProgress = {
|
||||
progress: ProgressData,
|
||||
downloadInfo: DownloadInfo
|
||||
}
|
||||
186
@types/objectInfo.d.ts
vendored
186
@types/objectInfo.d.ts
vendored
|
|
@ -1,93 +1,93 @@
|
|||
// Generated by https://quicktype.io
|
||||
|
||||
export interface ObjectInfo {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: unknown;
|
||||
__actions__: unknown;
|
||||
total: number;
|
||||
items: Item[];
|
||||
}
|
||||
export interface Item {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__links__: Links;
|
||||
__actions__: unknown;
|
||||
id: string;
|
||||
external_id: string;
|
||||
channel_id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
promo_title: string;
|
||||
promo_description: string;
|
||||
type: string;
|
||||
slug: string;
|
||||
slug_title: string;
|
||||
images: Images;
|
||||
episode_metadata: EpisodeMetadata;
|
||||
playback: string;
|
||||
linked_resource_key: string;
|
||||
type: string;
|
||||
s_num?: string;
|
||||
f_num?: string;
|
||||
movie_metadata?: {
|
||||
movie_listing_id: string;
|
||||
movie_listing_title: string
|
||||
};
|
||||
isSelected?: boolean
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
'episode/season': EpisodeSeason;
|
||||
'episode/series': EpisodeSeason;
|
||||
resource: EpisodeSeason;
|
||||
'resource/channel': EpisodeSeason;
|
||||
streams: EpisodeSeason;
|
||||
}
|
||||
|
||||
export interface EpisodeSeason {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface EpisodeMetadata {
|
||||
series_id: string;
|
||||
series_title: string;
|
||||
series_slug_title: string;
|
||||
season_id: string;
|
||||
season_title: string;
|
||||
season_slug_title: string;
|
||||
season_number: number;
|
||||
episode_number: number;
|
||||
episode: string;
|
||||
sequence_number: number;
|
||||
duration_ms: number;
|
||||
ad_breaks: AdBreak[];
|
||||
episode_air_date: string;
|
||||
is_premium_only: boolean;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_clip: boolean;
|
||||
available_offline: boolean;
|
||||
maturity_ratings: string[];
|
||||
subtitle_locales: string[];
|
||||
availability_notes: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
type: string;
|
||||
offset_ms: number;
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
thumbnail: Array<Thumbnail[]>;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
width: number;
|
||||
height: number;
|
||||
type: string;
|
||||
source: string;
|
||||
}
|
||||
// Generated by https://quicktype.io
|
||||
|
||||
export interface ObjectInfo {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: unknown;
|
||||
__actions__: unknown;
|
||||
total: number;
|
||||
items: Item[];
|
||||
}
|
||||
export interface Item {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__links__: Links;
|
||||
__actions__: unknown;
|
||||
id: string;
|
||||
external_id: string;
|
||||
channel_id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
promo_title: string;
|
||||
promo_description: string;
|
||||
type: string;
|
||||
slug: string;
|
||||
slug_title: string;
|
||||
images: Images;
|
||||
episode_metadata: EpisodeMetadata;
|
||||
playback: string;
|
||||
linked_resource_key: string;
|
||||
type: string;
|
||||
s_num?: string;
|
||||
f_num?: string;
|
||||
movie_metadata?: {
|
||||
movie_listing_id: string;
|
||||
movie_listing_title: string
|
||||
};
|
||||
isSelected?: boolean
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
'episode/season': EpisodeSeason;
|
||||
'episode/series': EpisodeSeason;
|
||||
resource: EpisodeSeason;
|
||||
'resource/channel': EpisodeSeason;
|
||||
streams: EpisodeSeason;
|
||||
}
|
||||
|
||||
export interface EpisodeSeason {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface EpisodeMetadata {
|
||||
series_id: string;
|
||||
series_title: string;
|
||||
series_slug_title: string;
|
||||
season_id: string;
|
||||
season_title: string;
|
||||
season_slug_title: string;
|
||||
season_number: number;
|
||||
episode_number: number;
|
||||
episode: string;
|
||||
sequence_number: number;
|
||||
duration_ms: number;
|
||||
ad_breaks: AdBreak[];
|
||||
episode_air_date: string;
|
||||
is_premium_only: boolean;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_clip: boolean;
|
||||
available_offline: boolean;
|
||||
maturity_ratings: string[];
|
||||
subtitle_locales: string[];
|
||||
availability_notes: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
type: string;
|
||||
offset_ms: number;
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
thumbnail: Array<Thumbnail[]>;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
width: number;
|
||||
height: number;
|
||||
type: string;
|
||||
source: string;
|
||||
}
|
||||
|
|
|
|||
4
@types/pkg.d.ts
vendored
4
@types/pkg.d.ts
vendored
|
|
@ -1,3 +1,3 @@
|
|||
declare module 'pkg' {
|
||||
export async function exec(config: string[]);
|
||||
declare module 'pkg' {
|
||||
export async function exec(config: string[]);
|
||||
}
|
||||
68
@types/playbackData.d.ts
vendored
68
@types/playbackData.d.ts
vendored
|
|
@ -1,34 +1,34 @@
|
|||
// Generated by https://quicktype.io
|
||||
|
||||
export interface PlaybackData {
|
||||
audio_locale: string;
|
||||
subtitles: { [key: string]: Subtitle };
|
||||
streams: { [key: string]: { [key: string]: Stream } };
|
||||
QoS: QoS;
|
||||
}
|
||||
|
||||
export interface QoS {
|
||||
region: string;
|
||||
cloudFrontRequestId: string;
|
||||
lambdaRunTime: number;
|
||||
}
|
||||
|
||||
export interface Stream {
|
||||
hardsub_locale: string;
|
||||
url: string;
|
||||
vcodec: Vcodec;
|
||||
hardsub_lang?: string;
|
||||
audio_lang?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
|
||||
export enum Vcodec {
|
||||
H264 = 'h264',
|
||||
}
|
||||
|
||||
export interface Subtitle {
|
||||
locale: Locale;
|
||||
url: string;
|
||||
format: string;
|
||||
}
|
||||
// Generated by https://quicktype.io
|
||||
|
||||
export interface PlaybackData {
|
||||
audio_locale: string;
|
||||
subtitles: { [key: string]: Subtitle };
|
||||
streams: { [key: string]: { [key: string]: Stream } };
|
||||
QoS: QoS;
|
||||
}
|
||||
|
||||
export interface QoS {
|
||||
region: string;
|
||||
cloudFrontRequestId: string;
|
||||
lambdaRunTime: number;
|
||||
}
|
||||
|
||||
export interface Stream {
|
||||
hardsub_locale: string;
|
||||
url: string;
|
||||
vcodec: Vcodec;
|
||||
hardsub_lang?: string;
|
||||
audio_lang?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
|
||||
export enum Vcodec {
|
||||
H264 = 'h264',
|
||||
}
|
||||
|
||||
export interface Subtitle {
|
||||
locale: Locale;
|
||||
url: string;
|
||||
format: string;
|
||||
}
|
||||
|
|
|
|||
24
@types/randomEvents.d.ts
vendored
24
@types/randomEvents.d.ts
vendored
|
|
@ -1,13 +1,13 @@
|
|||
import { ExtendedProgress } from "./messageHandler";
|
||||
|
||||
export type RandomEvents = {
|
||||
progress: ExtendedProgress,
|
||||
finish: undefined
|
||||
}
|
||||
|
||||
export interface RandomEvent<T extends keyof RandomEvents> {
|
||||
name: T,
|
||||
data: RandomEvents[T]
|
||||
}
|
||||
|
||||
import { ExtendedProgress } from './messageHandler';
|
||||
|
||||
export type RandomEvents = {
|
||||
progress: ExtendedProgress,
|
||||
finish: undefined
|
||||
}
|
||||
|
||||
export interface RandomEvent<T extends keyof RandomEvents> {
|
||||
name: T,
|
||||
data: RandomEvents[T]
|
||||
}
|
||||
|
||||
export type Handler<T extends keyof RandomEvents> = (data: RandomEvent<T>) => unknown;
|
||||
4
@types/removeNPMAbsolutePaths.d.ts
vendored
4
@types/removeNPMAbsolutePaths.d.ts
vendored
|
|
@ -1,3 +1,3 @@
|
|||
declare module 'removeNPMAbsolutePaths' {
|
||||
export default async function modulesCleanup(path: string);
|
||||
declare module 'removeNPMAbsolutePaths' {
|
||||
export default async function modulesCleanup(path: string);
|
||||
}
|
||||
28
@types/sei-helper.d.ts
vendored
28
@types/sei-helper.d.ts
vendored
|
|
@ -1,15 +1,15 @@
|
|||
declare module 'sei-helper' {
|
||||
export async function question(qStr: string): Promise<string>;
|
||||
export function cleanupFilename(str: string): string;
|
||||
export function exec(str: string, str1: string, str2: string);
|
||||
export const cookie: {
|
||||
parse: (data: Record<string, string>) => Record<string, {
|
||||
value: string;
|
||||
expires: Date;
|
||||
path: string;
|
||||
domain: string;
|
||||
secure: boolean;
|
||||
}>
|
||||
};
|
||||
export function formatTime(time: number): string
|
||||
declare module 'sei-helper' {
|
||||
export async function question(qStr: string): Promise<string>;
|
||||
export function cleanupFilename(str: string): string;
|
||||
export function exec(str: string, str1: string, str2: string);
|
||||
export const cookie: {
|
||||
parse: (data: Record<string, string>) => Record<string, {
|
||||
value: string;
|
||||
expires: Date;
|
||||
path: string;
|
||||
domain: string;
|
||||
secure: boolean;
|
||||
}>
|
||||
};
|
||||
export function formatTime(time: number): string
|
||||
}
|
||||
4
@types/serviceClassInterface.d.ts
vendored
4
@types/serviceClassInterface.d.ts
vendored
|
|
@ -1,3 +1,3 @@
|
|||
export interface ServiceClass {
|
||||
cli: () => Promise<boolean|undefined|void>
|
||||
export interface ServiceClass {
|
||||
cli: () => Promise<boolean|undefined|void>
|
||||
}
|
||||
56
@types/streamData.d.ts
vendored
56
@types/streamData.d.ts
vendored
|
|
@ -1,28 +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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
|
|
|||
6
@types/updateFile.d.ts
vendored
6
@types/updateFile.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
export type UpdateFile = {
|
||||
lastCheck: number,
|
||||
nextCheck: number
|
||||
export type UpdateFile = {
|
||||
lastCheck: number,
|
||||
nextCheck: number
|
||||
}
|
||||
3092
crunchy.ts
3092
crunchy.ts
File diff suppressed because it is too large
Load diff
|
|
@ -1,159 +1,160 @@
|
|||
import { app, BrowserWindow, dialog, } from 'electron';
|
||||
import path from 'path/posix';
|
||||
import registerMessageHandler from './messageHandler';
|
||||
import fs from 'fs';
|
||||
import dotenv from 'dotenv';
|
||||
import express from "express";
|
||||
import { Console } from 'console';
|
||||
import json from '../../../package.json';
|
||||
|
||||
process.on('uncaughtException', (er, or) => {
|
||||
console.error(er, or);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (er, pr) => {
|
||||
console.log(er, pr);
|
||||
});
|
||||
|
||||
const getDataDirectory = () => {
|
||||
switch (process.platform) {
|
||||
case "darwin": {
|
||||
if (!process.env.HOME) {
|
||||
console.error('Unknown home directory');
|
||||
process.exit(1);
|
||||
}
|
||||
return path.join(process.env.HOME, "Library", "Application Support", json.name);
|
||||
}
|
||||
case "win32": {
|
||||
if (!process.env.APPDATA) {
|
||||
console.error('Unknown home directory');
|
||||
process.exit(1);
|
||||
}
|
||||
return path.join(process.env.APPDATA, json.name);
|
||||
}
|
||||
case "linux": {
|
||||
if (!process.env.HOME) {
|
||||
console.error('Unknown home directory');
|
||||
process.exit(1);
|
||||
}
|
||||
return path.join(process.env.HOME, `.${json.name}`);
|
||||
}
|
||||
default: {
|
||||
console.error("Unsupported platform!");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(getDataDirectory()))
|
||||
fs.mkdirSync(getDataDirectory());
|
||||
|
||||
export { getDataDirectory };
|
||||
|
||||
import './menu';
|
||||
|
||||
|
||||
if (fs.existsSync(path.join(__dirname, '.env')))
|
||||
dotenv.config({ path: path.join(__dirname, '.env'), debug: true });
|
||||
|
||||
if (require('electron-squirrel-startup')) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
export const isWindows = process.platform === 'win32';
|
||||
|
||||
let mainWindow: BrowserWindow|undefined = undefined;
|
||||
export { mainWindow };
|
||||
|
||||
const icon = path.join(__dirname, 'images', `Logo_Inverted.${isWindows ? 'ico' : 'png'}`);
|
||||
|
||||
if (!process.env.TEST) {
|
||||
console = (() => {
|
||||
const logFolder = path.join(getDataDirectory(), 'logs');
|
||||
if (!fs.existsSync(logFolder))
|
||||
fs.mkdirSync(logFolder);
|
||||
return new Console(fs.createWriteStream(path.join(logFolder, `${Date.now()}.log`)));
|
||||
})();
|
||||
}
|
||||
|
||||
const createWindow = async () => {
|
||||
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 800,
|
||||
title: 'AniDL GUI BETA',
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
},
|
||||
icon,
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('crashed', (e) => console.log(e));
|
||||
|
||||
if (!process.env.TEST) {
|
||||
const response = dialog.showMessageBoxSync(mainWindow, {
|
||||
title: 'Test Version Information',
|
||||
message: 'I understand that this is a test version that is subject to changes and most certainly contains errors.'
|
||||
+ '\nI understand that I am using this tool at my own risk.'
|
||||
+ '\nI know that bugs or suggestions should be made to Izuco on Discord or under github@izuco.dev'
|
||||
+ '\nI understand that I should thank Darekon for the art works and the concept art if I see him',
|
||||
buttons: [
|
||||
'Cancel',
|
||||
'I understand'
|
||||
],
|
||||
type: 'info'
|
||||
});
|
||||
console.log(`[INFO] Popup response: ${response}`);
|
||||
if (response !== 1 && response !== -1)
|
||||
app.quit();
|
||||
}
|
||||
|
||||
registerMessageHandler(mainWindow);
|
||||
|
||||
if (!process.env.USE_BROWSER) {
|
||||
const app = express();
|
||||
|
||||
// Path.sep seems to return / on windows with electron
|
||||
// \\ in Filename on Linux is possible but I don't see another way rn
|
||||
const sep = isWindows ? '\\' : '/';
|
||||
|
||||
const p = __dirname.split(sep);
|
||||
p.pop();
|
||||
p.push('build');
|
||||
|
||||
console.log(p.join(sep));
|
||||
|
||||
app.use(express.static(p.join(sep)));
|
||||
|
||||
await new Promise((resolve) => {
|
||||
app.listen(3000, () => {
|
||||
console.log('Express started');
|
||||
resolve(undefined);
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
mainWindow.loadURL('http://localhost:3000');
|
||||
if (process.env.TEST)
|
||||
mainWindow.webContents.openDevTools();
|
||||
};
|
||||
|
||||
app.on('ready', createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('quit', () => {
|
||||
process.exit(0);
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
import { app, BrowserWindow, dialog, } from 'electron';
|
||||
import path from 'path/posix';
|
||||
import registerMessageHandler from './messageHandler';
|
||||
import fs from 'fs';
|
||||
import dotenv from 'dotenv';
|
||||
import express from 'express';
|
||||
import { Console } from 'console';
|
||||
import json from '../../../package.json';
|
||||
|
||||
process.on('uncaughtException', (er, or) => {
|
||||
console.error(er, or);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (er, pr) => {
|
||||
console.log(er, pr);
|
||||
});
|
||||
|
||||
const getDataDirectory = () => {
|
||||
switch (process.platform) {
|
||||
case 'darwin': {
|
||||
if (!process.env.HOME) {
|
||||
console.error('Unknown home directory');
|
||||
process.exit(1);
|
||||
}
|
||||
return path.join(process.env.HOME, 'Library', 'Application Support', json.name);
|
||||
}
|
||||
case 'win32': {
|
||||
if (!process.env.APPDATA) {
|
||||
console.error('Unknown home directory');
|
||||
process.exit(1);
|
||||
}
|
||||
return path.join(process.env.APPDATA, json.name);
|
||||
}
|
||||
case 'linux': {
|
||||
if (!process.env.HOME) {
|
||||
console.error('Unknown home directory');
|
||||
process.exit(1);
|
||||
}
|
||||
return path.join(process.env.HOME, `.${json.name}`);
|
||||
}
|
||||
default: {
|
||||
console.error('Unsupported platform!');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!fs.existsSync(getDataDirectory()))
|
||||
fs.mkdirSync(getDataDirectory());
|
||||
|
||||
export { getDataDirectory };
|
||||
|
||||
import './menu';
|
||||
|
||||
|
||||
if (fs.existsSync(path.join(__dirname, '.env')))
|
||||
dotenv.config({ path: path.join(__dirname, '.env'), debug: true });
|
||||
|
||||
if (require('electron-squirrel-startup')) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
export const isWindows = process.platform === 'win32';
|
||||
|
||||
let mainWindow: BrowserWindow|undefined = undefined;
|
||||
export { mainWindow };
|
||||
|
||||
const icon = path.join(__dirname, 'images', `Logo_Inverted.${isWindows ? 'ico' : 'png'}`);
|
||||
|
||||
if (!process.env.TEST) {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
console = (() => {
|
||||
const logFolder = path.join(getDataDirectory(), 'logs');
|
||||
if (!fs.existsSync(logFolder))
|
||||
fs.mkdirSync(logFolder);
|
||||
return new Console(fs.createWriteStream(path.join(logFolder, `${Date.now()}.log`)));
|
||||
})();
|
||||
}
|
||||
|
||||
const createWindow = async () => {
|
||||
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 800,
|
||||
title: 'AniDL GUI BETA',
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
},
|
||||
icon,
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('crashed', (e) => console.log(e));
|
||||
|
||||
if (!process.env.TEST) {
|
||||
const response = dialog.showMessageBoxSync(mainWindow, {
|
||||
title: 'Test Version Information',
|
||||
message: 'I understand that this is a test version that is subject to changes and most certainly contains errors.'
|
||||
+ '\nI understand that I am using this tool at my own risk.'
|
||||
+ '\nI know that bugs or suggestions should be made to Izuco on Discord or under github@izuco.dev'
|
||||
+ '\nI understand that I should thank Darekon for the art works and the concept art if I see him',
|
||||
buttons: [
|
||||
'Cancel',
|
||||
'I understand'
|
||||
],
|
||||
type: 'info'
|
||||
});
|
||||
console.log(`[INFO] Popup response: ${response}`);
|
||||
if (response !== 1 && response !== -1)
|
||||
app.quit();
|
||||
}
|
||||
|
||||
registerMessageHandler(mainWindow);
|
||||
|
||||
if (!process.env.USE_BROWSER) {
|
||||
const app = express();
|
||||
|
||||
// Path.sep seems to return / on windows with electron
|
||||
// \\ in Filename on Linux is possible but I don't see another way rn
|
||||
const sep = isWindows ? '\\' : '/';
|
||||
|
||||
const p = __dirname.split(sep);
|
||||
p.pop();
|
||||
p.push('build');
|
||||
|
||||
console.log(p.join(sep));
|
||||
|
||||
app.use(express.static(p.join(sep)));
|
||||
|
||||
await new Promise((resolve) => {
|
||||
app.listen(3000, () => {
|
||||
console.log('Express started');
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
mainWindow.loadURL('http://localhost:3000');
|
||||
if (process.env.TEST)
|
||||
mainWindow.webContents.openDevTools();
|
||||
};
|
||||
|
||||
app.on('ready', createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('quit', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,85 +1,85 @@
|
|||
import { Menu, MenuItem, MenuItemConstructorOptions, shell } from "electron";
|
||||
import path from 'path';
|
||||
import { getDataDirectory } from ".";
|
||||
import json from '../../../package.json';
|
||||
|
||||
const template: (MenuItemConstructorOptions | MenuItem)[] = [
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
role: 'undo'
|
||||
},
|
||||
{
|
||||
role: 'redo'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
role: 'paste'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Debug',
|
||||
submenu: [
|
||||
{
|
||||
role: 'toggleDevTools'
|
||||
},
|
||||
{
|
||||
label: 'Open log folder',
|
||||
click: () => {
|
||||
shell.openPath(path.join(getDataDirectory(), 'logs'))
|
||||
}
|
||||
},
|
||||
{
|
||||
role: 'forceReload'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Version',
|
||||
sublabel: json.version
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/anidl/multi-downloader-nx')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Report a Bug',
|
||||
click: () => {
|
||||
shell.openExternal(`https://github.com/anidl/multi-downloader-nx/issues/new?assignees=izu-co&labels=bug&template=bug.yml&title=BUG&version=${json.version}`)
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/anidl/multi-downloader-nx/graphs/contributors')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
click: () => {
|
||||
shell.openExternal('https://discord.gg/qEpbWen5vq')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
import { Menu, MenuItem, MenuItemConstructorOptions, shell } from 'electron';
|
||||
import path from 'path';
|
||||
import { getDataDirectory } from '.';
|
||||
import json from '../../../package.json';
|
||||
|
||||
const template: (MenuItemConstructorOptions | MenuItem)[] = [
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
role: 'undo'
|
||||
},
|
||||
{
|
||||
role: 'redo'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
role: 'paste'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Debug',
|
||||
submenu: [
|
||||
{
|
||||
role: 'toggleDevTools'
|
||||
},
|
||||
{
|
||||
label: 'Open log folder',
|
||||
click: () => {
|
||||
shell.openPath(path.join(getDataDirectory(), 'logs'));
|
||||
}
|
||||
},
|
||||
{
|
||||
role: 'forceReload'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Version',
|
||||
sublabel: json.version
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/anidl/multi-downloader-nx');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Report a Bug',
|
||||
click: () => {
|
||||
shell.openExternal(`https://github.com/anidl/multi-downloader-nx/issues/new?assignees=izu-co&labels=bug&template=bug.yml&title=BUG&version=${json.version}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/anidl/multi-downloader-nx/graphs/contributors');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
click: () => {
|
||||
shell.openExternal('https://discord.gg/qEpbWen5vq');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
||||
|
|
@ -1,28 +1,28 @@
|
|||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { MessageHandler } from '../../../@types/messageHandler';
|
||||
import Crunchy from './serviceHandler/crunchyroll';
|
||||
import Funimation from './serviceHandler/funimation';
|
||||
|
||||
export default (window: BrowserWindow) => {
|
||||
let handler: MessageHandler|undefined;
|
||||
ipcMain.handle('setup', (_, data) => {
|
||||
if (data === 'funi') {
|
||||
handler = new Funimation(window);
|
||||
} else if (data === 'crunchy') {
|
||||
handler = new Crunchy(window);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('type', async () => handler === undefined ? undefined : handler instanceof Funimation ? 'funi' : 'crunchy');
|
||||
ipcMain.handle('auth', async (_, data) => handler?.auth(data));
|
||||
ipcMain.handle('checkToken', async () => handler?.checkToken());
|
||||
ipcMain.handle('search', async (_, data) => handler?.search(data));
|
||||
ipcMain.handle('default', async (_, data) => handler?.handleDefault(data));
|
||||
ipcMain.handle('availableDubCodes', async () => handler?.availableDubCodes());
|
||||
ipcMain.handle('resolveItems', async (_, data) => handler?.resolveItems(data));
|
||||
ipcMain.handle('listEpisodes', async (_, data) => handler?.listEpisodes(data));
|
||||
ipcMain.handle('downloadItem', async (_, data) => handler?.downloadItem(data));
|
||||
ipcMain.handle('writeToClipboard', async (_, data) => handler?.writeToClipboard(data));
|
||||
ipcMain.handle('openFolder', async (_, data) => handler?.openFolder(data));
|
||||
ipcMain.on('isDownloading', (ev) => ev.returnValue = handler?.isDownloading());
|
||||
};
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { MessageHandler } from '../../../@types/messageHandler';
|
||||
import Crunchy from './serviceHandler/crunchyroll';
|
||||
import Funimation from './serviceHandler/funimation';
|
||||
|
||||
export default (window: BrowserWindow) => {
|
||||
let handler: MessageHandler|undefined;
|
||||
ipcMain.handle('setup', (_, data) => {
|
||||
if (data === 'funi') {
|
||||
handler = new Funimation(window);
|
||||
} else if (data === 'crunchy') {
|
||||
handler = new Crunchy(window);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('type', async () => handler === undefined ? undefined : handler instanceof Funimation ? 'funi' : 'crunchy');
|
||||
ipcMain.handle('auth', async (_, data) => handler?.auth(data));
|
||||
ipcMain.handle('checkToken', async () => handler?.checkToken());
|
||||
ipcMain.handle('search', async (_, data) => handler?.search(data));
|
||||
ipcMain.handle('default', async (_, data) => handler?.handleDefault(data));
|
||||
ipcMain.handle('availableDubCodes', async () => handler?.availableDubCodes());
|
||||
ipcMain.handle('resolveItems', async (_, data) => handler?.resolveItems(data));
|
||||
ipcMain.handle('listEpisodes', async (_, data) => handler?.listEpisodes(data));
|
||||
ipcMain.handle('downloadItem', async (_, data) => handler?.downloadItem(data));
|
||||
ipcMain.handle('writeToClipboard', async (_, data) => handler?.writeToClipboard(data));
|
||||
ipcMain.handle('openFolder', async (_, data) => handler?.openFolder(data));
|
||||
ipcMain.on('isDownloading', (ev) => ev.returnValue = handler?.isDownloading());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
contextBridge.exposeInMainWorld('Electron', {
|
||||
ipcRenderer: {
|
||||
...ipcRenderer,
|
||||
on: (name: string, handler: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
|
||||
ipcRenderer.on(name, handler);
|
||||
return ipcRenderer;
|
||||
},
|
||||
removeListener: (name: string, handler: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
|
||||
ipcRenderer.removeListener(name, handler);
|
||||
}
|
||||
}
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
contextBridge.exposeInMainWorld('Electron', {
|
||||
ipcRenderer: {
|
||||
...ipcRenderer,
|
||||
on: (name: string, handler: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
|
||||
ipcRenderer.on(name, handler);
|
||||
return ipcRenderer;
|
||||
},
|
||||
removeListener: (name: string, handler: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
|
||||
ipcRenderer.removeListener(name, handler);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,66 +1,66 @@
|
|||
import { BrowserWindow, clipboard, dialog, shell } from "electron";
|
||||
import { DownloadInfo, ProgressData } from "../../../../@types/messageHandler";
|
||||
import { RandomEvent, RandomEvents } from "../../../../@types/randomEvents";
|
||||
import { isWindows } from "..";
|
||||
|
||||
export default class Base {
|
||||
|
||||
constructor(private window: BrowserWindow) {}
|
||||
|
||||
private downloading = false;
|
||||
|
||||
setDownloading(downloading: boolean) {
|
||||
this.downloading = downloading;
|
||||
}
|
||||
|
||||
getDownloading() {
|
||||
return this.downloading;
|
||||
}
|
||||
|
||||
alertError(error: Error) {
|
||||
dialog.showMessageBoxSync(this.window, {
|
||||
message: `${error.name ?? 'An error occured'}\n${error.message}`,
|
||||
detail: error.stack,
|
||||
title: `Error`,
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
makeProgressHandler(videoInfo: DownloadInfo) {
|
||||
return ((data: ProgressData) => {
|
||||
this.sendMessage({
|
||||
name: 'progress',
|
||||
data: {
|
||||
downloadInfo: videoInfo,
|
||||
progress: data
|
||||
}
|
||||
})
|
||||
}).bind(this);
|
||||
}
|
||||
|
||||
getWindow() {
|
||||
return this.window;
|
||||
}
|
||||
|
||||
sendMessage<T extends keyof RandomEvents>(data: RandomEvent<T>) {
|
||||
this.window.webContents.send('randomEvent', data);
|
||||
}
|
||||
|
||||
isDownloading() {
|
||||
return this.downloading;
|
||||
}
|
||||
|
||||
async writeToClipboard(text: string) {
|
||||
clipboard.writeText(text, 'clipboard');
|
||||
return true;
|
||||
}
|
||||
|
||||
async openFolder(subPath: string[]) {
|
||||
const sep = isWindows ? '\\' : '/';
|
||||
|
||||
const p = __dirname.split(sep).slice(0, -4); // gui/electron/src/serviceHandler
|
||||
p.push(...subPath);
|
||||
shell.openPath(p.join(sep));
|
||||
}
|
||||
|
||||
import { BrowserWindow, clipboard, dialog, shell } from 'electron';
|
||||
import { DownloadInfo, ProgressData } from '../../../../@types/messageHandler';
|
||||
import { RandomEvent, RandomEvents } from '../../../../@types/randomEvents';
|
||||
import { isWindows } from '..';
|
||||
|
||||
export default class Base {
|
||||
|
||||
constructor(private window: BrowserWindow) {}
|
||||
|
||||
private downloading = false;
|
||||
|
||||
setDownloading(downloading: boolean) {
|
||||
this.downloading = downloading;
|
||||
}
|
||||
|
||||
getDownloading() {
|
||||
return this.downloading;
|
||||
}
|
||||
|
||||
alertError(error: Error) {
|
||||
dialog.showMessageBoxSync(this.window, {
|
||||
message: `${error.name ?? 'An error occured'}\n${error.message}`,
|
||||
detail: error.stack,
|
||||
title: 'Error',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
makeProgressHandler(videoInfo: DownloadInfo) {
|
||||
return ((data: ProgressData) => {
|
||||
this.sendMessage({
|
||||
name: 'progress',
|
||||
data: {
|
||||
downloadInfo: videoInfo,
|
||||
progress: data
|
||||
}
|
||||
});
|
||||
}).bind(this);
|
||||
}
|
||||
|
||||
getWindow() {
|
||||
return this.window;
|
||||
}
|
||||
|
||||
sendMessage<T extends keyof RandomEvents>(data: RandomEvent<T>) {
|
||||
this.window.webContents.send('randomEvent', data);
|
||||
}
|
||||
|
||||
isDownloading() {
|
||||
return this.downloading;
|
||||
}
|
||||
|
||||
async writeToClipboard(text: string) {
|
||||
clipboard.writeText(text, 'clipboard');
|
||||
return true;
|
||||
}
|
||||
|
||||
async openFolder(subPath: string[]) {
|
||||
const sep = isWindows ? '\\' : '/';
|
||||
|
||||
const p = __dirname.split(sep).slice(0, -4); // gui/electron/src/serviceHandler
|
||||
p.push(...subPath);
|
||||
shell.openPath(p.join(sep));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,93 +1,93 @@
|
|||
import { BrowserWindow } from 'electron';
|
||||
import { CrunchyDownloadOptions } from '../../../../@types/crunchyTypes';
|
||||
import { AuthData, CheckTokenResponse, DownloadData, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import Crunchy from '../../../../crunchy';
|
||||
import Funimation from '../../../../funi';
|
||||
import { ArgvType } from '../../../../modules/module.app-args';
|
||||
import { buildDefault, getDefault } from '../../../../modules/module.args';
|
||||
import { dubLanguageCodes } from '../../../../modules/module.langsData';
|
||||
import Base from './base';
|
||||
|
||||
class CrunchyHandler extends Base implements MessageHandler {
|
||||
private crunchy: Crunchy;
|
||||
constructor(window: BrowserWindow) {
|
||||
super(window);
|
||||
this.crunchy = new Crunchy();
|
||||
}
|
||||
|
||||
public async listEpisodes (id: string): Promise<EpisodeListResponse> {
|
||||
return { isOk: true, value: (await this.crunchy.listSeriesID(id)).list };
|
||||
}
|
||||
|
||||
public async handleDefault(name: string) {
|
||||
return getDefault(name, this.crunchy.cfg.cli);
|
||||
}
|
||||
|
||||
public async availableDubCodes(): Promise<string[]> {
|
||||
return dubLanguageCodes;
|
||||
}
|
||||
|
||||
public async resolveItems(data: ResolveItemsData): Promise<ResponseBase<QueueItem[]>> {
|
||||
const res = await this.crunchy.downloadFromSeriesID(data.id, data);
|
||||
if (!res.isOk)
|
||||
return res;
|
||||
return { isOk: true, value: res.value.map(a => {
|
||||
return {
|
||||
...data,
|
||||
ids: a.data.map(a => a.mediaId),
|
||||
title: a.episodeTitle,
|
||||
parent: {
|
||||
title: a.seasonTitle,
|
||||
season: a.season.toString()
|
||||
},
|
||||
e: a.e,
|
||||
episode: a.episodeNumber
|
||||
};
|
||||
}) };
|
||||
}
|
||||
|
||||
public async search(data: SearchData): Promise<SearchResponse> {
|
||||
this.crunchy.refreshToken();
|
||||
const crunchySearch = await this.crunchy.doSearch(data);
|
||||
if (!crunchySearch.isOk)
|
||||
return crunchySearch;
|
||||
return { isOk: true, value: crunchySearch.value };
|
||||
}
|
||||
|
||||
public async checkToken(): Promise<CheckTokenResponse> {
|
||||
if (await this.crunchy.getProfile()) {
|
||||
return { isOk: true, value: undefined };
|
||||
} else {
|
||||
return { isOk: false, reason: new Error('') };
|
||||
}
|
||||
}
|
||||
|
||||
public auth(data: AuthData) {
|
||||
return this.crunchy.doAuth(data);
|
||||
}
|
||||
|
||||
public async downloadItem(data: DownloadData) {
|
||||
this.setDownloading(true);
|
||||
const _default = buildDefault() as ArgvType;
|
||||
await this.crunchy.refreshToken();
|
||||
const res = await this.crunchy.downloadFromSeriesID(data.id, {
|
||||
dubLang: data.dubLang,
|
||||
e: data.e
|
||||
});
|
||||
if (res.isOk) {
|
||||
for (const select of res.value) {
|
||||
if (!(await this.crunchy.downloadEpisode(select, {..._default, skipsubs: false, callbackMaker: this.makeProgressHandler.bind(this), q: data.q, fileName: data.fileName }))) {
|
||||
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`);
|
||||
er.name = 'Download error';
|
||||
this.alertError(er);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.alertError(res.reason);
|
||||
}
|
||||
this.sendMessage({ name: 'finish', data: undefined })
|
||||
this.setDownloading(false);
|
||||
}
|
||||
}
|
||||
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { CrunchyDownloadOptions } from '../../../../@types/crunchyTypes';
|
||||
import { AuthData, CheckTokenResponse, DownloadData, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import Crunchy from '../../../../crunchy';
|
||||
import Funimation from '../../../../funi';
|
||||
import { ArgvType } from '../../../../modules/module.app-args';
|
||||
import { buildDefault, getDefault } from '../../../../modules/module.args';
|
||||
import { dubLanguageCodes } from '../../../../modules/module.langsData';
|
||||
import Base from './base';
|
||||
|
||||
class CrunchyHandler extends Base implements MessageHandler {
|
||||
private crunchy: Crunchy;
|
||||
constructor(window: BrowserWindow) {
|
||||
super(window);
|
||||
this.crunchy = new Crunchy();
|
||||
}
|
||||
|
||||
public async listEpisodes (id: string): Promise<EpisodeListResponse> {
|
||||
return { isOk: true, value: (await this.crunchy.listSeriesID(id)).list };
|
||||
}
|
||||
|
||||
public async handleDefault(name: string) {
|
||||
return getDefault(name, this.crunchy.cfg.cli);
|
||||
}
|
||||
|
||||
public async availableDubCodes(): Promise<string[]> {
|
||||
return dubLanguageCodes;
|
||||
}
|
||||
|
||||
public async resolveItems(data: ResolveItemsData): Promise<ResponseBase<QueueItem[]>> {
|
||||
const res = await this.crunchy.downloadFromSeriesID(data.id, data);
|
||||
if (!res.isOk)
|
||||
return res;
|
||||
return { isOk: true, value: res.value.map(a => {
|
||||
return {
|
||||
...data,
|
||||
ids: a.data.map(a => a.mediaId),
|
||||
title: a.episodeTitle,
|
||||
parent: {
|
||||
title: a.seasonTitle,
|
||||
season: a.season.toString()
|
||||
},
|
||||
e: a.e,
|
||||
episode: a.episodeNumber
|
||||
};
|
||||
}) };
|
||||
}
|
||||
|
||||
public async search(data: SearchData): Promise<SearchResponse> {
|
||||
this.crunchy.refreshToken();
|
||||
const crunchySearch = await this.crunchy.doSearch(data);
|
||||
if (!crunchySearch.isOk)
|
||||
return crunchySearch;
|
||||
return { isOk: true, value: crunchySearch.value };
|
||||
}
|
||||
|
||||
public async checkToken(): Promise<CheckTokenResponse> {
|
||||
if (await this.crunchy.getProfile()) {
|
||||
return { isOk: true, value: undefined };
|
||||
} else {
|
||||
return { isOk: false, reason: new Error('') };
|
||||
}
|
||||
}
|
||||
|
||||
public auth(data: AuthData) {
|
||||
return this.crunchy.doAuth(data);
|
||||
}
|
||||
|
||||
public async downloadItem(data: DownloadData) {
|
||||
this.setDownloading(true);
|
||||
const _default = buildDefault() as ArgvType;
|
||||
await this.crunchy.refreshToken();
|
||||
const res = await this.crunchy.downloadFromSeriesID(data.id, {
|
||||
dubLang: data.dubLang,
|
||||
e: data.e
|
||||
});
|
||||
if (res.isOk) {
|
||||
for (const select of res.value) {
|
||||
if (!(await this.crunchy.downloadEpisode(select, {..._default, skipsubs: false, callbackMaker: this.makeProgressHandler.bind(this), q: data.q, fileName: data.fileName }))) {
|
||||
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`);
|
||||
er.name = 'Download error';
|
||||
this.alertError(er);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.alertError(res.reason);
|
||||
}
|
||||
this.sendMessage({ name: 'finish', data: undefined });
|
||||
this.setDownloading(false);
|
||||
}
|
||||
}
|
||||
|
||||
export default CrunchyHandler;
|
||||
|
|
@ -1,101 +1,101 @@
|
|||
import { BrowserWindow } from 'electron';
|
||||
import { AuthData, CheckTokenResponse, DownloadData, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import Funimation from '../../../../funi';
|
||||
import { ArgvType } from '../../../../modules/module.app-args';
|
||||
import { buildDefault, getDefault } from '../../../../modules/module.args';
|
||||
import { dubLanguageCodes } from '../../../../modules/module.langsData';
|
||||
import Base from './base';
|
||||
|
||||
class FunimationHandler extends Base implements MessageHandler {
|
||||
private funi: Funimation;
|
||||
constructor(window: BrowserWindow) {
|
||||
super(window);
|
||||
this.funi = new Funimation();
|
||||
}
|
||||
|
||||
public async listEpisodes (id: string) : Promise<EpisodeListResponse> {
|
||||
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<string[]> {
|
||||
return dubLanguageCodes;
|
||||
}
|
||||
|
||||
public async resolveItems(data: ResolveItemsData): Promise<ResponseBase<QueueItem[]>> {
|
||||
const res = await this.funi.getShow(false, { ...data, id: parseInt(data.id) });
|
||||
if (!res.isOk)
|
||||
return res;
|
||||
return { isOk: true, value: res.value.map(a => {
|
||||
return {
|
||||
...data,
|
||||
ids: [a.episodeID],
|
||||
title: a.title,
|
||||
parent: {
|
||||
title: a.seasonTitle,
|
||||
season: a.seasonNumber
|
||||
},
|
||||
e: a.episodeID,
|
||||
episode: a.seasonNumber
|
||||
};
|
||||
}) };
|
||||
}
|
||||
|
||||
public async search(data: SearchData): Promise<SearchResponse> {
|
||||
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<CheckTokenResponse> {
|
||||
return this.funi.checkToken();
|
||||
}
|
||||
|
||||
public auth(data: AuthData) {
|
||||
return this.funi.auth(data);
|
||||
}
|
||||
|
||||
public async downloadItem(data: DownloadData) {
|
||||
this.setDownloading(true);
|
||||
const res = await this.funi.getShow(false, { all: false, but: false, id: parseInt(data.id), e: data.e });
|
||||
const _default = buildDefault() as ArgvType;
|
||||
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: ['all'], sub: false } }, { ..._default, callbackMaker: this.makeProgressHandler.bind(this), ass: true, fileName: data.fileName, q: data.q })
|
||||
}
|
||||
this.sendMessage({ name: 'finish', data: undefined })
|
||||
this.setDownloading(false);
|
||||
};
|
||||
}
|
||||
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { AuthData, CheckTokenResponse, DownloadData, EpisodeListResponse, MessageHandler, QueueItem, ResolveItemsData, ResponseBase, SearchData, SearchResponse } from '../../../../@types/messageHandler';
|
||||
import Funimation from '../../../../funi';
|
||||
import { ArgvType } from '../../../../modules/module.app-args';
|
||||
import { buildDefault, getDefault } from '../../../../modules/module.args';
|
||||
import { dubLanguageCodes } from '../../../../modules/module.langsData';
|
||||
import Base from './base';
|
||||
|
||||
class FunimationHandler extends Base implements MessageHandler {
|
||||
private funi: Funimation;
|
||||
constructor(window: BrowserWindow) {
|
||||
super(window);
|
||||
this.funi = new Funimation();
|
||||
}
|
||||
|
||||
public async listEpisodes (id: string) : Promise<EpisodeListResponse> {
|
||||
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<string[]> {
|
||||
return dubLanguageCodes;
|
||||
}
|
||||
|
||||
public async resolveItems(data: ResolveItemsData): Promise<ResponseBase<QueueItem[]>> {
|
||||
const res = await this.funi.getShow(false, { ...data, id: parseInt(data.id) });
|
||||
if (!res.isOk)
|
||||
return res;
|
||||
return { isOk: true, value: res.value.map(a => {
|
||||
return {
|
||||
...data,
|
||||
ids: [a.episodeID],
|
||||
title: a.title,
|
||||
parent: {
|
||||
title: a.seasonTitle,
|
||||
season: a.seasonNumber
|
||||
},
|
||||
e: a.episodeID,
|
||||
episode: a.seasonNumber
|
||||
};
|
||||
}) };
|
||||
}
|
||||
|
||||
public async search(data: SearchData): Promise<SearchResponse> {
|
||||
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<CheckTokenResponse> {
|
||||
return this.funi.checkToken();
|
||||
}
|
||||
|
||||
public auth(data: AuthData) {
|
||||
return this.funi.auth(data);
|
||||
}
|
||||
|
||||
public async downloadItem(data: DownloadData) {
|
||||
this.setDownloading(true);
|
||||
const res = await this.funi.getShow(false, { all: false, but: false, id: parseInt(data.id), e: data.e });
|
||||
const _default = buildDefault() as ArgvType;
|
||||
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: ['all'], sub: false } }, { ..._default, callbackMaker: this.makeProgressHandler.bind(this), ass: true, fileName: data.fileName, q: data.q });
|
||||
}
|
||||
this.sendMessage({ name: 'finish', data: undefined });
|
||||
this.setDownloading(false);
|
||||
}
|
||||
}
|
||||
|
||||
export default FunimationHandler;
|
||||
|
|
@ -57,7 +57,7 @@ const MultiSelect: React.FC<MultiSelectProps> = (props) => {
|
|||
)}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{props.values.concat(props.allOption ? 'all' : []). map((name) => (
|
||||
{props.values.concat(props.allOption ? 'all' : []).map((name) => (
|
||||
<MenuItem
|
||||
key={name}
|
||||
value={name}
|
||||
|
|
|
|||
120
index.ts
120
index.ts
|
|
@ -1,61 +1,61 @@
|
|||
import { ServiceClass } from './@types/serviceClassInterface';
|
||||
import { appArgv, overrideArguments } from './modules/module.app-args';
|
||||
import * as yamlCfg from './modules/module.cfg-loader';
|
||||
import { makeCommand, addToArchive } from './modules/module.downloadArchive';
|
||||
|
||||
import update from './modules/module.updater';
|
||||
|
||||
(async () => {
|
||||
const cfg = yamlCfg.loadCfg();
|
||||
|
||||
const argv = appArgv(cfg.cli);
|
||||
|
||||
await update(argv.update);
|
||||
|
||||
if (argv.all && argv.but) {
|
||||
console.log('[ERROR] --all and --but exclude each other!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv.addArchive) {
|
||||
if (argv.service === 'funi') {
|
||||
if (argv.s === undefined)
|
||||
return console.log('[ERROR] `-s` not found');
|
||||
addToArchive({
|
||||
service: 'funi',
|
||||
type: 's'
|
||||
}, argv.s);
|
||||
console.log('[INFO] Added %s to the downloadArchive list', argv.s);
|
||||
} else if (argv.service === 'crunchy') {
|
||||
if (argv.s === undefined && argv.series === undefined)
|
||||
return console.log('[ERROR] `-s` or `--srz` not found');
|
||||
if (argv.s && argv.series)
|
||||
return console.log('[ERROR] Both `-s` and `--srz` found');
|
||||
addToArchive({
|
||||
service: 'crunchy',
|
||||
type: argv.s === undefined ? 'srz' : 's'
|
||||
}, (argv.s === undefined ? argv.series : argv.s) as string);
|
||||
console.log('[INFO] Added %s to the downloadArchive list', (argv.s === undefined ? argv.series : argv.s));
|
||||
}
|
||||
} else if (argv.downloadArchive) {
|
||||
const ids = makeCommand(argv.service);
|
||||
for (const id of ids) {
|
||||
overrideArguments(cfg.cli, id);
|
||||
/* Reimport module to override appArgv */
|
||||
Object.keys(require.cache).forEach(key => {
|
||||
if (key.endsWith('crunchy.js') || key.endsWith('funi.js'))
|
||||
delete require.cache[key];
|
||||
});
|
||||
const service = new (argv.service === 'funi' ? (await import('./funi')).default : (await import('./crunchy')).default) as ServiceClass;
|
||||
await service.cli();
|
||||
}
|
||||
} else {
|
||||
if (argv.service === 'funi') {
|
||||
const funi = new (await import('./funi')).default();
|
||||
await funi.cli();
|
||||
} else if (argv.service === 'crunchy') {
|
||||
const crunchy = new (await import('./crunchy')).default();
|
||||
await crunchy.cli();
|
||||
}
|
||||
}
|
||||
import { ServiceClass } from './@types/serviceClassInterface';
|
||||
import { appArgv, overrideArguments } from './modules/module.app-args';
|
||||
import * as yamlCfg from './modules/module.cfg-loader';
|
||||
import { makeCommand, addToArchive } from './modules/module.downloadArchive';
|
||||
|
||||
import update from './modules/module.updater';
|
||||
|
||||
(async () => {
|
||||
const cfg = yamlCfg.loadCfg();
|
||||
|
||||
const argv = appArgv(cfg.cli);
|
||||
|
||||
await update(argv.update);
|
||||
|
||||
if (argv.all && argv.but) {
|
||||
console.log('[ERROR] --all and --but exclude each other!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv.addArchive) {
|
||||
if (argv.service === 'funi') {
|
||||
if (argv.s === undefined)
|
||||
return console.log('[ERROR] `-s` not found');
|
||||
addToArchive({
|
||||
service: 'funi',
|
||||
type: 's'
|
||||
}, argv.s);
|
||||
console.log('[INFO] Added %s to the downloadArchive list', argv.s);
|
||||
} else if (argv.service === 'crunchy') {
|
||||
if (argv.s === undefined && argv.series === undefined)
|
||||
return console.log('[ERROR] `-s` or `--srz` not found');
|
||||
if (argv.s && argv.series)
|
||||
return console.log('[ERROR] Both `-s` and `--srz` found');
|
||||
addToArchive({
|
||||
service: 'crunchy',
|
||||
type: argv.s === undefined ? 'srz' : 's'
|
||||
}, (argv.s === undefined ? argv.series : argv.s) as string);
|
||||
console.log('[INFO] Added %s to the downloadArchive list', (argv.s === undefined ? argv.series : argv.s));
|
||||
}
|
||||
} else if (argv.downloadArchive) {
|
||||
const ids = makeCommand(argv.service);
|
||||
for (const id of ids) {
|
||||
overrideArguments(cfg.cli, id);
|
||||
/* Reimport module to override appArgv */
|
||||
Object.keys(require.cache).forEach(key => {
|
||||
if (key.endsWith('crunchy.js') || key.endsWith('funi.js'))
|
||||
delete require.cache[key];
|
||||
});
|
||||
const service = new (argv.service === 'funi' ? (await import('./funi')).default : (await import('./crunchy')).default) as ServiceClass;
|
||||
await service.cli();
|
||||
}
|
||||
} else {
|
||||
if (argv.service === 'funi') {
|
||||
const funi = new (await import('./funi')).default();
|
||||
await funi.cli();
|
||||
} else if (argv.service === 'crunchy') {
|
||||
const crunchy = new (await import('./crunchy')).default();
|
||||
await crunchy.cli();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,63 +1,63 @@
|
|||
import packageJSON from '../package.json';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { args, groups } from './module.args';
|
||||
|
||||
const transformService = (str: 'funi'|'crunchy'|'both') => {
|
||||
switch (str) {
|
||||
case 'both':
|
||||
return 'Both';
|
||||
case 'crunchy':
|
||||
return 'Crunchyroll';
|
||||
case 'funi':
|
||||
return 'Funimation';
|
||||
}
|
||||
};
|
||||
|
||||
let docs = `# ${packageJSON.name} (${packageJSON.version}v)
|
||||
|
||||
If you find any bugs in this documentation or in the programm itself please report it [over on GitHub](${packageJSON.bugs.url}).
|
||||
|
||||
## Legal Warning
|
||||
|
||||
This application is not endorsed by or affiliated with *Funimation* or *Crunchyroll*.
|
||||
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.
|
||||
|
||||
## CLI Options
|
||||
### Legend
|
||||
- \`\${someText}\` shows that you should replace this text with your own
|
||||
- e.g. \`--username \${someText}\` -> \`--username Izuco\`
|
||||
`;
|
||||
|
||||
Object.entries(groups).forEach(([key, value]) => {
|
||||
docs += `\n### ${value.slice(0, -1)}\n`;
|
||||
|
||||
docs += args.filter(a => a.group === key).map(argument => {
|
||||
return [`#### \`${argument.name.length > 1 ? '--' : '-'}${argument.name}\``,
|
||||
`| **Service** | **Usage** | **Type** | **Required** | **Alias** | ${argument.choices ? '**Choices** |' : ''} ${argument.default ? '**Default** |' : ''}**cli-default Entry**`,
|
||||
`| --- | --- | --- | --- | --- | ${argument.choices ? '--- | ' : ''}${argument.default ? '--- | ' : ''}---| `,
|
||||
`| ${transformService(argument.service)} | \`${argument.name.length > 1 ? '--' : '-'}${argument.name} ${argument.usage}\` | \`${argument.type}\` | \`${argument.demandOption ? 'Yes' : 'No'}\`|`
|
||||
+ ` \`${(argument.alias ? `${argument.alias.length > 1 ? '--' : '-'}${argument.alias}` : undefined) ?? 'NaN'}\` |`
|
||||
+ `${argument.choices ? ` [${argument.choices.map(a => `\`${a || '\'\''}\``).join(', ')}] |` : ''}`
|
||||
+ `${argument.default ? ` \`${
|
||||
typeof argument.default === 'object'
|
||||
? Array.isArray(argument.default)
|
||||
? JSON.stringify(argument.default)
|
||||
: argument.default.default
|
||||
: argument.default
|
||||
}\`|` : ''}`
|
||||
+ ` ${typeof argument.default === 'object' && !Array.isArray(argument.default)
|
||||
? `\`${argument.default.name || argument.name}: \``
|
||||
: '`NaN`'
|
||||
} |`,
|
||||
'',
|
||||
argument.docDescribe === true ? argument.describe : argument.docDescribe
|
||||
].join('\n');
|
||||
}).join('\n');
|
||||
});
|
||||
|
||||
|
||||
|
||||
import packageJSON from '../package.json';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { args, groups } from './module.args';
|
||||
|
||||
const transformService = (str: 'funi'|'crunchy'|'both') => {
|
||||
switch (str) {
|
||||
case 'both':
|
||||
return 'Both';
|
||||
case 'crunchy':
|
||||
return 'Crunchyroll';
|
||||
case 'funi':
|
||||
return 'Funimation';
|
||||
}
|
||||
};
|
||||
|
||||
let docs = `# ${packageJSON.name} (${packageJSON.version}v)
|
||||
|
||||
If you find any bugs in this documentation or in the programm itself please report it [over on GitHub](${packageJSON.bugs.url}).
|
||||
|
||||
## Legal Warning
|
||||
|
||||
This application is not endorsed by or affiliated with *Funimation* or *Crunchyroll*.
|
||||
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.
|
||||
|
||||
## CLI Options
|
||||
### Legend
|
||||
- \`\${someText}\` shows that you should replace this text with your own
|
||||
- e.g. \`--username \${someText}\` -> \`--username Izuco\`
|
||||
`;
|
||||
|
||||
Object.entries(groups).forEach(([key, value]) => {
|
||||
docs += `\n### ${value.slice(0, -1)}\n`;
|
||||
|
||||
docs += args.filter(a => a.group === key).map(argument => {
|
||||
return [`#### \`${argument.name.length > 1 ? '--' : '-'}${argument.name}\``,
|
||||
`| **Service** | **Usage** | **Type** | **Required** | **Alias** | ${argument.choices ? '**Choices** |' : ''} ${argument.default ? '**Default** |' : ''}**cli-default Entry**`,
|
||||
`| --- | --- | --- | --- | --- | ${argument.choices ? '--- | ' : ''}${argument.default ? '--- | ' : ''}---| `,
|
||||
`| ${transformService(argument.service)} | \`${argument.name.length > 1 ? '--' : '-'}${argument.name} ${argument.usage}\` | \`${argument.type}\` | \`${argument.demandOption ? 'Yes' : 'No'}\`|`
|
||||
+ ` \`${(argument.alias ? `${argument.alias.length > 1 ? '--' : '-'}${argument.alias}` : undefined) ?? 'NaN'}\` |`
|
||||
+ `${argument.choices ? ` [${argument.choices.map(a => `\`${a || '\'\''}\``).join(', ')}] |` : ''}`
|
||||
+ `${argument.default ? ` \`${
|
||||
typeof argument.default === 'object'
|
||||
? Array.isArray(argument.default)
|
||||
? JSON.stringify(argument.default)
|
||||
: argument.default.default
|
||||
: argument.default
|
||||
}\`|` : ''}`
|
||||
+ ` ${typeof argument.default === 'object' && !Array.isArray(argument.default)
|
||||
? `\`${argument.default.name || argument.name}: \``
|
||||
: '`NaN`'
|
||||
} |`,
|
||||
'',
|
||||
argument.docDescribe === true ? argument.describe : argument.docDescribe
|
||||
].join('\n');
|
||||
}).join('\n');
|
||||
});
|
||||
|
||||
|
||||
|
||||
fs.writeFileSync(path.resolve(__dirname, '..', 'docs', 'DOCUMENTATION.md'), docs);
|
||||
234
modules/build.ts
234
modules/build.ts
|
|
@ -1,117 +1,117 @@
|
|||
// build requirements
|
||||
import fs from 'fs-extra';
|
||||
import pkg from '../package.json';
|
||||
import modulesCleanup from 'removeNPMAbsolutePaths';
|
||||
import { exec } from 'pkg';
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
const buildsDir = './_builds';
|
||||
const nodeVer = 'node16-';
|
||||
|
||||
type BuildTypes = `${'ubuntu'|'windows'|'macos'}64`
|
||||
|
||||
(async () => {
|
||||
const buildType = process.argv[2] as BuildTypes;
|
||||
const isGUI = process.argv[3] === 'true';
|
||||
|
||||
if (isGUI) {
|
||||
buildGUI(buildType);
|
||||
} else {
|
||||
buildBinary(buildType);
|
||||
}
|
||||
})();
|
||||
|
||||
async function buildGUI(buildType: BuildTypes) {
|
||||
execSync(`npx electron-builder build ${getCommand(buildType)}`, { stdio: [0,1,2] });
|
||||
execSync(`7z a -t7z "../${buildsDir}/multi-downloader-nx-${buildType}-gui.7z" "${getOutputFileName(buildType)}"`,{
|
||||
stdio:[0,1,2],
|
||||
cwd: path.join('dist')
|
||||
});
|
||||
}
|
||||
|
||||
function getCommand(buildType: BuildTypes) {
|
||||
switch (buildType) {
|
||||
case 'ubuntu64':
|
||||
return `--linux --arm64`
|
||||
case 'windows64':
|
||||
return '--win';
|
||||
case 'macos64':
|
||||
return '--mac dmg';
|
||||
default:
|
||||
return '--error'
|
||||
}
|
||||
}
|
||||
|
||||
function getOutputFileName(buildType: BuildTypes) {
|
||||
switch (buildType) {
|
||||
case 'ubuntu64':
|
||||
return `${pkg.name}_${pkg.version}_arm64.deb`;
|
||||
case 'windows64':
|
||||
return `${pkg.name} Setup ${pkg.version}.exe`;
|
||||
case 'macos64':
|
||||
return `${pkg.name}-${pkg.version}.dmg`;
|
||||
default:
|
||||
throw new Error(`Unknown build type ${buildType}`);
|
||||
}
|
||||
}
|
||||
|
||||
// main
|
||||
async function buildBinary(buildType: BuildTypes) {
|
||||
const buildStr = `multi-downloader-nx`;
|
||||
const acceptableBuilds = ['windows64','ubuntu64','macos64'];
|
||||
if(!acceptableBuilds.includes(buildType)){
|
||||
console.error('[ERROR] unknown build type!');
|
||||
process.exit(1);
|
||||
}
|
||||
await modulesCleanup('.');
|
||||
if(!fs.existsSync(buildsDir)){
|
||||
fs.mkdirSync(buildsDir);
|
||||
}
|
||||
const buildFull = `${buildStr}-${buildType}-cli`;
|
||||
const buildDir = `${buildsDir}/${buildFull}`;
|
||||
if(fs.existsSync(buildDir)){
|
||||
fs.removeSync(buildDir);
|
||||
}
|
||||
fs.mkdirSync(buildDir);
|
||||
const buildConfig = [
|
||||
pkg.main,
|
||||
'--target', nodeVer + getTarget(buildType),
|
||||
'--output', `${buildDir}/${pkg.short_name}`,
|
||||
];
|
||||
console.log(`[Build] Build configuration: ${buildFull}`);
|
||||
try {
|
||||
await exec(buildConfig);
|
||||
}
|
||||
catch(e){
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
}
|
||||
fs.mkdirSync(`${buildDir}/config`);
|
||||
fs.mkdirSync(`${buildDir}/videos`);
|
||||
fs.copySync('./config/bin-path.yml', `${buildDir}/config/bin-path.yml`);
|
||||
fs.copySync('./config/cli-defaults.yml', `${buildDir}/config/cli-defaults.yml`);
|
||||
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`)){
|
||||
fs.removeSync(`${buildsDir}/${buildFull}.7z`);
|
||||
}
|
||||
execSync(`7z a -t7z "${buildsDir}/${buildFull}.7z" "${buildDir}"`,{stdio:[0,1,2]});
|
||||
};
|
||||
|
||||
function getTarget(bt: string) : string {
|
||||
switch(bt){
|
||||
case 'windows64':
|
||||
return 'windows-x64';
|
||||
case 'ubuntu64':
|
||||
return 'linux-x64';
|
||||
case 'macos64':
|
||||
return 'macos-x64';
|
||||
default:
|
||||
return 'windows-x64';
|
||||
}
|
||||
}
|
||||
// build requirements
|
||||
import fs from 'fs-extra';
|
||||
import pkg from '../package.json';
|
||||
import modulesCleanup from 'removeNPMAbsolutePaths';
|
||||
import { exec } from 'pkg';
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
const buildsDir = './_builds';
|
||||
const nodeVer = 'node16-';
|
||||
|
||||
type BuildTypes = `${'ubuntu'|'windows'|'macos'}64`
|
||||
|
||||
(async () => {
|
||||
const buildType = process.argv[2] as BuildTypes;
|
||||
const isGUI = process.argv[3] === 'true';
|
||||
|
||||
if (isGUI) {
|
||||
buildGUI(buildType);
|
||||
} else {
|
||||
buildBinary(buildType);
|
||||
}
|
||||
})();
|
||||
|
||||
async function buildGUI(buildType: BuildTypes) {
|
||||
execSync(`npx electron-builder build ${getCommand(buildType)}`, { stdio: [0,1,2] });
|
||||
execSync(`7z a -t7z "../${buildsDir}/multi-downloader-nx-${buildType}-gui.7z" "${getOutputFileName(buildType)}"`,{
|
||||
stdio:[0,1,2],
|
||||
cwd: path.join('dist')
|
||||
});
|
||||
}
|
||||
|
||||
function getCommand(buildType: BuildTypes) {
|
||||
switch (buildType) {
|
||||
case 'ubuntu64':
|
||||
return '--linux --arm64';
|
||||
case 'windows64':
|
||||
return '--win';
|
||||
case 'macos64':
|
||||
return '--mac dmg';
|
||||
default:
|
||||
return '--error';
|
||||
}
|
||||
}
|
||||
|
||||
function getOutputFileName(buildType: BuildTypes) {
|
||||
switch (buildType) {
|
||||
case 'ubuntu64':
|
||||
return `${pkg.name}_${pkg.version}_arm64.deb`;
|
||||
case 'windows64':
|
||||
return `${pkg.name} Setup ${pkg.version}.exe`;
|
||||
case 'macos64':
|
||||
return `${pkg.name}-${pkg.version}.dmg`;
|
||||
default:
|
||||
throw new Error(`Unknown build type ${buildType}`);
|
||||
}
|
||||
}
|
||||
|
||||
// main
|
||||
async function buildBinary(buildType: BuildTypes) {
|
||||
const buildStr = 'multi-downloader-nx';
|
||||
const acceptableBuilds = ['windows64','ubuntu64','macos64'];
|
||||
if(!acceptableBuilds.includes(buildType)){
|
||||
console.error('[ERROR] unknown build type!');
|
||||
process.exit(1);
|
||||
}
|
||||
await modulesCleanup('.');
|
||||
if(!fs.existsSync(buildsDir)){
|
||||
fs.mkdirSync(buildsDir);
|
||||
}
|
||||
const buildFull = `${buildStr}-${buildType}-cli`;
|
||||
const buildDir = `${buildsDir}/${buildFull}`;
|
||||
if(fs.existsSync(buildDir)){
|
||||
fs.removeSync(buildDir);
|
||||
}
|
||||
fs.mkdirSync(buildDir);
|
||||
const buildConfig = [
|
||||
pkg.main,
|
||||
'--target', nodeVer + getTarget(buildType),
|
||||
'--output', `${buildDir}/${pkg.short_name}`,
|
||||
];
|
||||
console.log(`[Build] Build configuration: ${buildFull}`);
|
||||
try {
|
||||
await exec(buildConfig);
|
||||
}
|
||||
catch(e){
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
}
|
||||
fs.mkdirSync(`${buildDir}/config`);
|
||||
fs.mkdirSync(`${buildDir}/videos`);
|
||||
fs.copySync('./config/bin-path.yml', `${buildDir}/config/bin-path.yml`);
|
||||
fs.copySync('./config/cli-defaults.yml', `${buildDir}/config/cli-defaults.yml`);
|
||||
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`)){
|
||||
fs.removeSync(`${buildsDir}/${buildFull}.7z`);
|
||||
}
|
||||
execSync(`7z a -t7z "${buildsDir}/${buildFull}.7z" "${buildDir}"`,{stdio:[0,1,2]});
|
||||
}
|
||||
|
||||
function getTarget(bt: string) : string {
|
||||
switch(bt){
|
||||
case 'windows64':
|
||||
return 'windows-x64';
|
||||
case 'ubuntu64':
|
||||
return 'linux-x64';
|
||||
case 'macos64':
|
||||
return 'macos-x64';
|
||||
default:
|
||||
return 'windows-x64';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,75 +1,75 @@
|
|||
import { Headers } from 'got/dist/source';
|
||||
|
||||
// api domains
|
||||
const domain = {
|
||||
www: 'https://www.crunchyroll.com',
|
||||
api: 'https://api.crunchyroll.com',
|
||||
www_beta: 'https://beta.crunchyroll.com',
|
||||
api_beta: 'https://beta-api.crunchyroll.com',
|
||||
};
|
||||
|
||||
export type APIType = {
|
||||
newani: string,
|
||||
search1: string,
|
||||
search2: string,
|
||||
rss_cid: string,
|
||||
rss_gid: string
|
||||
media_page: string
|
||||
series_page: string
|
||||
auth: string
|
||||
// mobile api
|
||||
search3: string
|
||||
session: string
|
||||
collections: string
|
||||
// beta api
|
||||
beta_auth: string
|
||||
beta_authBasic: string
|
||||
beta_authBasicMob: string
|
||||
beta_profile: string
|
||||
beta_cmsToken: string
|
||||
beta_search: string
|
||||
beta_browse: string
|
||||
beta_cms: string,
|
||||
beta_authHeader: Headers,
|
||||
beta_authHeaderMob: Headers
|
||||
}
|
||||
|
||||
// api urls
|
||||
const api: APIType = {
|
||||
// web
|
||||
newani: `${domain.www}/rss/anime`,
|
||||
search1: `${domain.www}/ajax/?req=RpcApiSearch_GetSearchCandidates`,
|
||||
search2: `${domain.www}/search_page`,
|
||||
rss_cid: `${domain.www}/syndication/feed?type=episodes&id=`, // &lang=enUS
|
||||
rss_gid: `${domain.www}/syndication/feed?type=episodes&group_id=`, // &lang=enUS
|
||||
media_page: `${domain.www}/media-`,
|
||||
series_page: `${domain.www}/series-`,
|
||||
auth: `${domain.www}/login`,
|
||||
// mobile api
|
||||
search3: `${domain.api}/autocomplete.0.json`,
|
||||
session: `${domain.api}/start_session.0.json`,
|
||||
collections: `${domain.api}/list_collections.0.json`,
|
||||
// beta api
|
||||
beta_auth: `${domain.api_beta}/auth/v1/token`,
|
||||
beta_authBasic: 'Basic bm9haWhkZXZtXzZpeWcwYThsMHE6',
|
||||
beta_authBasicMob: 'Basic YTZ5eGxvYW04c2VqaThsZDhldnc6aFQ3d2FjWHhNaURJcDhSNE9kekJybWVoQUtLTEVKUEE=',
|
||||
beta_profile: `${domain.api_beta}/accounts/v1/me/profile`,
|
||||
beta_cmsToken: `${domain.api_beta}/index/v2`,
|
||||
beta_search: `${domain.api_beta}/content/v1/search`,
|
||||
beta_browse: `${domain.api_beta}/content/v1/browse`,
|
||||
beta_cms: `${domain.api_beta}/cms/v2`,
|
||||
beta_authHeader: {},
|
||||
beta_authHeaderMob: {}
|
||||
};
|
||||
|
||||
// set header
|
||||
api.beta_authHeader = {
|
||||
Authorization: api.beta_authBasic,
|
||||
};
|
||||
api.beta_authHeaderMob = {
|
||||
Authorization: api.beta_authBasicMob,
|
||||
};
|
||||
|
||||
export {
|
||||
domain, api
|
||||
};
|
||||
import { Headers } from 'got/dist/source';
|
||||
|
||||
// api domains
|
||||
const domain = {
|
||||
www: 'https://www.crunchyroll.com',
|
||||
api: 'https://api.crunchyroll.com',
|
||||
www_beta: 'https://beta.crunchyroll.com',
|
||||
api_beta: 'https://beta-api.crunchyroll.com',
|
||||
};
|
||||
|
||||
export type APIType = {
|
||||
newani: string,
|
||||
search1: string,
|
||||
search2: string,
|
||||
rss_cid: string,
|
||||
rss_gid: string
|
||||
media_page: string
|
||||
series_page: string
|
||||
auth: string
|
||||
// mobile api
|
||||
search3: string
|
||||
session: string
|
||||
collections: string
|
||||
// beta api
|
||||
beta_auth: string
|
||||
beta_authBasic: string
|
||||
beta_authBasicMob: string
|
||||
beta_profile: string
|
||||
beta_cmsToken: string
|
||||
beta_search: string
|
||||
beta_browse: string
|
||||
beta_cms: string,
|
||||
beta_authHeader: Headers,
|
||||
beta_authHeaderMob: Headers
|
||||
}
|
||||
|
||||
// api urls
|
||||
const api: APIType = {
|
||||
// web
|
||||
newani: `${domain.www}/rss/anime`,
|
||||
search1: `${domain.www}/ajax/?req=RpcApiSearch_GetSearchCandidates`,
|
||||
search2: `${domain.www}/search_page`,
|
||||
rss_cid: `${domain.www}/syndication/feed?type=episodes&id=`, // &lang=enUS
|
||||
rss_gid: `${domain.www}/syndication/feed?type=episodes&group_id=`, // &lang=enUS
|
||||
media_page: `${domain.www}/media-`,
|
||||
series_page: `${domain.www}/series-`,
|
||||
auth: `${domain.www}/login`,
|
||||
// mobile api
|
||||
search3: `${domain.api}/autocomplete.0.json`,
|
||||
session: `${domain.api}/start_session.0.json`,
|
||||
collections: `${domain.api}/list_collections.0.json`,
|
||||
// beta api
|
||||
beta_auth: `${domain.api_beta}/auth/v1/token`,
|
||||
beta_authBasic: 'Basic bm9haWhkZXZtXzZpeWcwYThsMHE6',
|
||||
beta_authBasicMob: 'Basic YTZ5eGxvYW04c2VqaThsZDhldnc6aFQ3d2FjWHhNaURJcDhSNE9kekJybWVoQUtLTEVKUEE=',
|
||||
beta_profile: `${domain.api_beta}/accounts/v1/me/profile`,
|
||||
beta_cmsToken: `${domain.api_beta}/index/v2`,
|
||||
beta_search: `${domain.api_beta}/content/v1/search`,
|
||||
beta_browse: `${domain.api_beta}/content/v1/browse`,
|
||||
beta_cms: `${domain.api_beta}/cms/v2`,
|
||||
beta_authHeader: {},
|
||||
beta_authHeaderMob: {}
|
||||
};
|
||||
|
||||
// set header
|
||||
api.beta_authHeader = {
|
||||
Authorization: api.beta_authBasic,
|
||||
};
|
||||
api.beta_authHeaderMob = {
|
||||
Authorization: api.beta_authBasicMob,
|
||||
};
|
||||
|
||||
export {
|
||||
domain, api
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,67 +1,67 @@
|
|||
import yargs, { Choices } from 'yargs';
|
||||
import { args, AvailableMuxer, groups } from './module.args';
|
||||
|
||||
let argvC: { [x: string]: unknown; fsRetryTime: number, forceMuxer: AvailableMuxer|undefined; username: string|undefined, password: string|undefined, silentAuth: boolean, skipSubMux: boolean, downloadArchive: boolean, addArchive: boolean, but: boolean, auth: boolean | undefined; dlFonts: boolean | undefined; search: string | undefined; 'search-type': string; page: number | undefined; 'search-locale': string; new: boolean | undefined; 'movie-listing': string | undefined; series: string | undefined; s: string | undefined; e: string | undefined; q: number; x: number; kstream: number; partsize: number; hslang: string; dlsubs: string[]; novids: boolean | undefined; noaudio: boolean | undefined; nosubs: boolean | undefined; dubLang: string[]; all: boolean; fontSize: number; allDubs: boolean; timeout: number; simul: boolean; mp4: boolean; skipmux: boolean | undefined; fileName: string; numbers: number; nosess: string; debug: boolean | undefined; nocleanup: boolean; help: boolean | undefined; service: 'funi' | 'crunchy'; update: boolean; fontName: string | undefined; _: (string | number)[]; $0: string; };
|
||||
|
||||
export type ArgvType = typeof argvC;
|
||||
|
||||
const appArgv = (cfg: {
|
||||
[key: string]: unknown
|
||||
}) => {
|
||||
if (argvC)
|
||||
return argvC;
|
||||
yargs(process.argv.slice(2));
|
||||
const argv = getArgv(cfg)
|
||||
.parseSync();
|
||||
argvC = argv;
|
||||
return argv;
|
||||
};
|
||||
|
||||
|
||||
const overrideArguments = (cfg: { [key:string]: unknown }, override: Partial<typeof argvC>) => {
|
||||
const argv = getArgv(cfg).middleware((ar) => {
|
||||
for (const key of Object.keys(override)) {
|
||||
ar[key] = override[key];
|
||||
}
|
||||
return ar;
|
||||
}).parseSync();
|
||||
argvC = argv;
|
||||
};
|
||||
|
||||
export {
|
||||
appArgv,
|
||||
overrideArguments
|
||||
};
|
||||
|
||||
const getArgv = (cfg: { [key:string]: unknown }) => {
|
||||
const parseDefault = <T = unknown>(key: string, _default: T) : T=> {
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, key)) {
|
||||
return cfg[key] as T;
|
||||
} else
|
||||
return _default;
|
||||
};
|
||||
|
||||
const argv = yargs.parserConfiguration({
|
||||
'duplicate-arguments-array': false,
|
||||
'camel-case-expansion': false,
|
||||
})
|
||||
.wrap(yargs.terminalWidth())
|
||||
.usage('Usage: $0 [options]')
|
||||
.help(true).version(false);
|
||||
const data = args.map(a => {
|
||||
return {
|
||||
...a,
|
||||
group: groups[a.group],
|
||||
default: typeof a.default === 'object' && !Array.isArray(a.default) ?
|
||||
parseDefault(a.default.name || a.name, a.default.default) : a.default
|
||||
};
|
||||
});
|
||||
for (const item of data)
|
||||
argv.option(item.name, {
|
||||
...item,
|
||||
choices: item.choices as unknown as Choices
|
||||
});
|
||||
return argv as unknown as yargs.Argv<typeof argvC>;
|
||||
};
|
||||
|
||||
import yargs, { Choices } from 'yargs';
|
||||
import { args, AvailableMuxer, groups } from './module.args';
|
||||
|
||||
let argvC: { [x: string]: unknown; fsRetryTime: number, forceMuxer: AvailableMuxer|undefined; username: string|undefined, password: string|undefined, silentAuth: boolean, skipSubMux: boolean, downloadArchive: boolean, addArchive: boolean, but: boolean, auth: boolean | undefined; dlFonts: boolean | undefined; search: string | undefined; 'search-type': string; page: number | undefined; 'search-locale': string; new: boolean | undefined; 'movie-listing': string | undefined; series: string | undefined; s: string | undefined; e: string | undefined; q: number; x: number; kstream: number; partsize: number; hslang: string; dlsubs: string[]; novids: boolean | undefined; noaudio: boolean | undefined; nosubs: boolean | undefined; dubLang: string[]; all: boolean; fontSize: number; allDubs: boolean; timeout: number; simul: boolean; mp4: boolean; skipmux: boolean | undefined; fileName: string; numbers: number; nosess: string; debug: boolean | undefined; nocleanup: boolean; help: boolean | undefined; service: 'funi' | 'crunchy'; update: boolean; fontName: string | undefined; _: (string | number)[]; $0: string; };
|
||||
|
||||
export type ArgvType = typeof argvC;
|
||||
|
||||
const appArgv = (cfg: {
|
||||
[key: string]: unknown
|
||||
}) => {
|
||||
if (argvC)
|
||||
return argvC;
|
||||
yargs(process.argv.slice(2));
|
||||
const argv = getArgv(cfg)
|
||||
.parseSync();
|
||||
argvC = argv;
|
||||
return argv;
|
||||
};
|
||||
|
||||
|
||||
const overrideArguments = (cfg: { [key:string]: unknown }, override: Partial<typeof argvC>) => {
|
||||
const argv = getArgv(cfg).middleware((ar) => {
|
||||
for (const key of Object.keys(override)) {
|
||||
ar[key] = override[key];
|
||||
}
|
||||
return ar;
|
||||
}).parseSync();
|
||||
argvC = argv;
|
||||
};
|
||||
|
||||
export {
|
||||
appArgv,
|
||||
overrideArguments
|
||||
};
|
||||
|
||||
const getArgv = (cfg: { [key:string]: unknown }) => {
|
||||
const parseDefault = <T = unknown>(key: string, _default: T) : T=> {
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, key)) {
|
||||
return cfg[key] as T;
|
||||
} else
|
||||
return _default;
|
||||
};
|
||||
|
||||
const argv = yargs.parserConfiguration({
|
||||
'duplicate-arguments-array': false,
|
||||
'camel-case-expansion': false,
|
||||
})
|
||||
.wrap(yargs.terminalWidth())
|
||||
.usage('Usage: $0 [options]')
|
||||
.help(true).version(false);
|
||||
const data = args.map(a => {
|
||||
return {
|
||||
...a,
|
||||
group: groups[a.group],
|
||||
default: typeof a.default === 'object' && !Array.isArray(a.default) ?
|
||||
parseDefault(a.default.name || a.name, a.default.default) : a.default
|
||||
};
|
||||
});
|
||||
for (const item of data)
|
||||
argv.option(item.name, {
|
||||
...item,
|
||||
choices: item.choices as unknown as Choices
|
||||
});
|
||||
return argv as unknown as yargs.Argv<typeof argvC>;
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,208 +1,208 @@
|
|||
import path from 'path';
|
||||
import yaml from 'yaml';
|
||||
import fs from 'fs-extra';
|
||||
import { lookpath } from 'lookpath';
|
||||
|
||||
// new-cfg
|
||||
const workingDir = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? path.dirname(process.execPath) : path.join(__dirname, '/..');
|
||||
const binCfgFile = path.join(workingDir, 'config', 'bin-path');
|
||||
const dirCfgFile = path.join(workingDir, 'config', 'dir-path');
|
||||
const cliCfgFile = path.join(workingDir, 'config', 'cli-defaults');
|
||||
const sessCfgFile = path.join(workingDir, 'config', 'session');
|
||||
const tokenFile = {
|
||||
funi: path.join(workingDir, 'config', 'funi_token'),
|
||||
cr: path.join(workingDir, 'config', 'cr_token')
|
||||
};
|
||||
|
||||
const loadYamlCfgFile = <T extends Record<string, any>>(file: string, isSess?: boolean): T => {
|
||||
if(fs.existsSync(`${file}.user.yml`) && !isSess){
|
||||
file += '.user';
|
||||
}
|
||||
file += '.yml';
|
||||
if(fs.existsSync(file)){
|
||||
try{
|
||||
return yaml.parse(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR]', e);
|
||||
return {} as T;
|
||||
}
|
||||
}
|
||||
return {} as T;
|
||||
};
|
||||
|
||||
export type ConfigObject = {
|
||||
dir: {
|
||||
content: string,
|
||||
trash: string,
|
||||
fonts: string;
|
||||
},
|
||||
bin: {
|
||||
ffmpeg?: string,
|
||||
mkvmerge?: string
|
||||
},
|
||||
cli: {
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
const loadCfg = () : ConfigObject => {
|
||||
// load cfgs
|
||||
const defaultCfg: ConfigObject = {
|
||||
bin: {},
|
||||
dir: loadYamlCfgFile<{
|
||||
content: string,
|
||||
trash: string,
|
||||
fonts: string
|
||||
}>(dirCfgFile),
|
||||
cli: loadYamlCfgFile<{
|
||||
[key: string]: any
|
||||
}>(cliCfgFile),
|
||||
};
|
||||
const defaultDirs = {
|
||||
fonts: '${wdir}/fonts/',
|
||||
content: '${wdir}/videos/',
|
||||
trash: '${wdir}/videos/_trash/',
|
||||
};
|
||||
if (typeof defaultCfg.dir !== 'object' || defaultCfg.dir === null || Array.isArray(defaultCfg.dir)) {
|
||||
defaultCfg.dir = defaultDirs;
|
||||
}
|
||||
|
||||
const keys = Object.keys(defaultDirs) as (keyof typeof defaultDirs)[];
|
||||
for (const key of keys) {
|
||||
if (!Object.prototype.hasOwnProperty.call(defaultCfg.dir, key) || typeof defaultCfg.dir[key] !== 'string') {
|
||||
defaultCfg.dir[key] = defaultDirs[key];
|
||||
}
|
||||
if (!path.isAbsolute(defaultCfg.dir[key])) {
|
||||
defaultCfg.dir[key] = path.join(workingDir, defaultCfg.dir[key].replace(/^\${wdir}/, ''));
|
||||
}
|
||||
}
|
||||
|
||||
if(!fs.existsSync(defaultCfg.dir.content)){
|
||||
try{
|
||||
fs.ensureDirSync(defaultCfg.dir.content);
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR] Content directory not accessible!');
|
||||
return defaultCfg;
|
||||
}
|
||||
}
|
||||
if(!fs.existsSync(defaultCfg.dir.trash)){
|
||||
defaultCfg.dir.trash = defaultCfg.dir.content;
|
||||
}
|
||||
// output
|
||||
return defaultCfg;
|
||||
};
|
||||
|
||||
const loadBinCfg = async () => {
|
||||
const binCfg = loadYamlCfgFile<ConfigObject['bin']>(binCfgFile);
|
||||
// binaries
|
||||
const defaultBin = {
|
||||
ffmpeg: '${wdir}/bin/ffmpeg/ffmpeg',
|
||||
mkvmerge: '${wdir}/bin/mkvtoolnix/mkvmerge',
|
||||
};
|
||||
const keys = Object.keys(defaultBin) as (keyof typeof defaultBin)[];
|
||||
for(const dir of keys){
|
||||
if(!Object.prototype.hasOwnProperty.call(binCfg, dir) || typeof binCfg[dir] != 'string'){
|
||||
binCfg[dir] = defaultBin[dir];
|
||||
}
|
||||
if (!path.isAbsolute(binCfg[dir] as string) && (binCfg[dir] as string).match(/^\${wdir}/)){
|
||||
binCfg[dir] = (binCfg[dir] as string).replace(/^\${wdir}/, '');
|
||||
binCfg[dir] = path.join(workingDir, binCfg[dir] as string);
|
||||
}
|
||||
binCfg[dir] = await lookpath(binCfg[dir] as string);
|
||||
binCfg[dir] = binCfg[dir] ? binCfg[dir] : undefined;
|
||||
if(!binCfg[dir]){
|
||||
const binFile = await lookpath(path.basename(defaultBin[dir]));
|
||||
binCfg[dir] = binFile ? binFile : binCfg[dir];
|
||||
}
|
||||
}
|
||||
return binCfg;
|
||||
};
|
||||
|
||||
const loadCRSession = () => {
|
||||
let session = loadYamlCfgFile(sessCfgFile, true);
|
||||
if(typeof session !== 'object' || session === null || Array.isArray(session)){
|
||||
session = {};
|
||||
}
|
||||
for(const cv of Object.keys(session)){
|
||||
if(typeof session[cv] !== 'object' || session[cv] === null || Array.isArray(session[cv])){
|
||||
session[cv] = {};
|
||||
}
|
||||
}
|
||||
return session;
|
||||
};
|
||||
|
||||
const saveCRSession = (data: Record<string, unknown>) => {
|
||||
const cfgFolder = path.dirname(sessCfgFile);
|
||||
try{
|
||||
fs.ensureDirSync(cfgFolder);
|
||||
fs.writeFileSync(`${sessCfgFile}.yml`, yaml.stringify(data));
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR] Can\'t save session file to disk!');
|
||||
}
|
||||
};
|
||||
|
||||
const loadCRToken = () => {
|
||||
let token = loadYamlCfgFile(tokenFile.cr, true);
|
||||
if(typeof token !== 'object' || token === null || Array.isArray(token)){
|
||||
token = {};
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
const saveCRToken = (data: Record<string, unknown>) => {
|
||||
const cfgFolder = path.dirname(tokenFile.cr);
|
||||
try{
|
||||
fs.ensureDirSync(cfgFolder);
|
||||
fs.writeFileSync(`${tokenFile.cr}.yml`, yaml.stringify(data));
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR] Can\'t save token file to disk!');
|
||||
}
|
||||
};
|
||||
|
||||
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.log('[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.log('[ERROR] Can\'t save token file to disk!');
|
||||
}
|
||||
};
|
||||
|
||||
const cfgDir = path.join(workingDir, 'config');
|
||||
|
||||
export {
|
||||
loadBinCfg,
|
||||
loadCfg,
|
||||
loadFuniToken,
|
||||
saveFuniToken,
|
||||
saveCRSession,
|
||||
saveCRToken,
|
||||
loadCRToken,
|
||||
loadCRSession,
|
||||
sessCfgFile,
|
||||
cfgDir
|
||||
import path from 'path';
|
||||
import yaml from 'yaml';
|
||||
import fs from 'fs-extra';
|
||||
import { lookpath } from 'lookpath';
|
||||
|
||||
// new-cfg
|
||||
const workingDir = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? path.dirname(process.execPath) : path.join(__dirname, '/..');
|
||||
const binCfgFile = path.join(workingDir, 'config', 'bin-path');
|
||||
const dirCfgFile = path.join(workingDir, 'config', 'dir-path');
|
||||
const cliCfgFile = path.join(workingDir, 'config', 'cli-defaults');
|
||||
const sessCfgFile = path.join(workingDir, 'config', 'session');
|
||||
const tokenFile = {
|
||||
funi: path.join(workingDir, 'config', 'funi_token'),
|
||||
cr: path.join(workingDir, 'config', 'cr_token')
|
||||
};
|
||||
|
||||
const loadYamlCfgFile = <T extends Record<string, any>>(file: string, isSess?: boolean): T => {
|
||||
if(fs.existsSync(`${file}.user.yml`) && !isSess){
|
||||
file += '.user';
|
||||
}
|
||||
file += '.yml';
|
||||
if(fs.existsSync(file)){
|
||||
try{
|
||||
return yaml.parse(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR]', e);
|
||||
return {} as T;
|
||||
}
|
||||
}
|
||||
return {} as T;
|
||||
};
|
||||
|
||||
export type ConfigObject = {
|
||||
dir: {
|
||||
content: string,
|
||||
trash: string,
|
||||
fonts: string;
|
||||
},
|
||||
bin: {
|
||||
ffmpeg?: string,
|
||||
mkvmerge?: string
|
||||
},
|
||||
cli: {
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
const loadCfg = () : ConfigObject => {
|
||||
// load cfgs
|
||||
const defaultCfg: ConfigObject = {
|
||||
bin: {},
|
||||
dir: loadYamlCfgFile<{
|
||||
content: string,
|
||||
trash: string,
|
||||
fonts: string
|
||||
}>(dirCfgFile),
|
||||
cli: loadYamlCfgFile<{
|
||||
[key: string]: any
|
||||
}>(cliCfgFile),
|
||||
};
|
||||
const defaultDirs = {
|
||||
fonts: '${wdir}/fonts/',
|
||||
content: '${wdir}/videos/',
|
||||
trash: '${wdir}/videos/_trash/',
|
||||
};
|
||||
if (typeof defaultCfg.dir !== 'object' || defaultCfg.dir === null || Array.isArray(defaultCfg.dir)) {
|
||||
defaultCfg.dir = defaultDirs;
|
||||
}
|
||||
|
||||
const keys = Object.keys(defaultDirs) as (keyof typeof defaultDirs)[];
|
||||
for (const key of keys) {
|
||||
if (!Object.prototype.hasOwnProperty.call(defaultCfg.dir, key) || typeof defaultCfg.dir[key] !== 'string') {
|
||||
defaultCfg.dir[key] = defaultDirs[key];
|
||||
}
|
||||
if (!path.isAbsolute(defaultCfg.dir[key])) {
|
||||
defaultCfg.dir[key] = path.join(workingDir, defaultCfg.dir[key].replace(/^\${wdir}/, ''));
|
||||
}
|
||||
}
|
||||
|
||||
if(!fs.existsSync(defaultCfg.dir.content)){
|
||||
try{
|
||||
fs.ensureDirSync(defaultCfg.dir.content);
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR] Content directory not accessible!');
|
||||
return defaultCfg;
|
||||
}
|
||||
}
|
||||
if(!fs.existsSync(defaultCfg.dir.trash)){
|
||||
defaultCfg.dir.trash = defaultCfg.dir.content;
|
||||
}
|
||||
// output
|
||||
return defaultCfg;
|
||||
};
|
||||
|
||||
const loadBinCfg = async () => {
|
||||
const binCfg = loadYamlCfgFile<ConfigObject['bin']>(binCfgFile);
|
||||
// binaries
|
||||
const defaultBin = {
|
||||
ffmpeg: '${wdir}/bin/ffmpeg/ffmpeg',
|
||||
mkvmerge: '${wdir}/bin/mkvtoolnix/mkvmerge',
|
||||
};
|
||||
const keys = Object.keys(defaultBin) as (keyof typeof defaultBin)[];
|
||||
for(const dir of keys){
|
||||
if(!Object.prototype.hasOwnProperty.call(binCfg, dir) || typeof binCfg[dir] != 'string'){
|
||||
binCfg[dir] = defaultBin[dir];
|
||||
}
|
||||
if (!path.isAbsolute(binCfg[dir] as string) && (binCfg[dir] as string).match(/^\${wdir}/)){
|
||||
binCfg[dir] = (binCfg[dir] as string).replace(/^\${wdir}/, '');
|
||||
binCfg[dir] = path.join(workingDir, binCfg[dir] as string);
|
||||
}
|
||||
binCfg[dir] = await lookpath(binCfg[dir] as string);
|
||||
binCfg[dir] = binCfg[dir] ? binCfg[dir] : undefined;
|
||||
if(!binCfg[dir]){
|
||||
const binFile = await lookpath(path.basename(defaultBin[dir]));
|
||||
binCfg[dir] = binFile ? binFile : binCfg[dir];
|
||||
}
|
||||
}
|
||||
return binCfg;
|
||||
};
|
||||
|
||||
const loadCRSession = () => {
|
||||
let session = loadYamlCfgFile(sessCfgFile, true);
|
||||
if(typeof session !== 'object' || session === null || Array.isArray(session)){
|
||||
session = {};
|
||||
}
|
||||
for(const cv of Object.keys(session)){
|
||||
if(typeof session[cv] !== 'object' || session[cv] === null || Array.isArray(session[cv])){
|
||||
session[cv] = {};
|
||||
}
|
||||
}
|
||||
return session;
|
||||
};
|
||||
|
||||
const saveCRSession = (data: Record<string, unknown>) => {
|
||||
const cfgFolder = path.dirname(sessCfgFile);
|
||||
try{
|
||||
fs.ensureDirSync(cfgFolder);
|
||||
fs.writeFileSync(`${sessCfgFile}.yml`, yaml.stringify(data));
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR] Can\'t save session file to disk!');
|
||||
}
|
||||
};
|
||||
|
||||
const loadCRToken = () => {
|
||||
let token = loadYamlCfgFile(tokenFile.cr, true);
|
||||
if(typeof token !== 'object' || token === null || Array.isArray(token)){
|
||||
token = {};
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
const saveCRToken = (data: Record<string, unknown>) => {
|
||||
const cfgFolder = path.dirname(tokenFile.cr);
|
||||
try{
|
||||
fs.ensureDirSync(cfgFolder);
|
||||
fs.writeFileSync(`${tokenFile.cr}.yml`, yaml.stringify(data));
|
||||
}
|
||||
catch(e){
|
||||
console.log('[ERROR] Can\'t save token file to disk!');
|
||||
}
|
||||
};
|
||||
|
||||
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.log('[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.log('[ERROR] Can\'t save token file to disk!');
|
||||
}
|
||||
};
|
||||
|
||||
const cfgDir = path.join(workingDir, 'config');
|
||||
|
||||
export {
|
||||
loadBinCfg,
|
||||
loadCfg,
|
||||
loadFuniToken,
|
||||
saveFuniToken,
|
||||
saveCRSession,
|
||||
saveCRToken,
|
||||
loadCRToken,
|
||||
loadCRSession,
|
||||
sessCfgFile,
|
||||
cfgDir
|
||||
};
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
const parse = (data: string) => {
|
||||
const res: Record<string, {
|
||||
value: string,
|
||||
expires: Date,
|
||||
path: string,
|
||||
domain: string,
|
||||
secure: boolean
|
||||
}> = {};
|
||||
const split = data.replace(/\r/g,'').split('\n');
|
||||
for (const line of split) {
|
||||
const c = line.split('\t');
|
||||
if(c.length < 7){
|
||||
continue;
|
||||
}
|
||||
res[c[5]] = {
|
||||
value: c[6],
|
||||
expires: new Date(parseInt(c[4])*1000),
|
||||
path: c[2],
|
||||
domain: c[0].replace(/^\./,''),
|
||||
secure: c[3] == 'TRUE' ? true : false
|
||||
};
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
export default parse;
|
||||
const parse = (data: string) => {
|
||||
const res: Record<string, {
|
||||
value: string,
|
||||
expires: Date,
|
||||
path: string,
|
||||
domain: string,
|
||||
secure: boolean
|
||||
}> = {};
|
||||
const split = data.replace(/\r/g,'').split('\n');
|
||||
for (const line of split) {
|
||||
const c = line.split('\t');
|
||||
if(c.length < 7){
|
||||
continue;
|
||||
}
|
||||
res[c[5]] = {
|
||||
value: c[6],
|
||||
expires: new Date(parseInt(c[4])*1000),
|
||||
path: c[2],
|
||||
domain: c[0].replace(/^\./,''),
|
||||
secure: c[3] == 'TRUE' ? true : false
|
||||
};
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
export default parse;
|
||||
|
|
|
|||
|
|
@ -1,162 +1,162 @@
|
|||
// build-in
|
||||
import child_process from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import { Headers } from 'got';
|
||||
import path from 'path';
|
||||
|
||||
export type CurlOptions = {
|
||||
headers?: Headers,
|
||||
curlProxy?: boolean,
|
||||
curlProxyAuth?: string,
|
||||
minVersion?: string,
|
||||
http2?: boolean,
|
||||
body?: unknown,
|
||||
curlDebug?: boolean
|
||||
} | undefined;
|
||||
|
||||
export type Res = {
|
||||
httpVersion: string,
|
||||
statusCode: string,
|
||||
statusMessage: string,
|
||||
rawHeaders: string,
|
||||
headers: Record<string, string[]|string>,
|
||||
rawBody: Buffer,
|
||||
body: string,
|
||||
}
|
||||
|
||||
// req
|
||||
const curlReq = async (curlBin: string, url: string, options: CurlOptions, cache: string) => {
|
||||
|
||||
const curlOpt = [
|
||||
`"${curlBin}"`,
|
||||
`"${url}"`,
|
||||
];
|
||||
|
||||
options = options || {};
|
||||
|
||||
if(options.headers && Object.keys(options.headers).length > 0){
|
||||
for(const h of Object.keys(options.headers)){
|
||||
const hC = options.headers[h];
|
||||
curlOpt.push('-H', `"${h}: ${hC}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if(options.curlProxy){
|
||||
curlOpt.push('--proxy-insecure', '-x', `"${options.curlProxy}"`);
|
||||
if(options.curlProxyAuth && typeof options.curlProxyAuth == 'string' && options.curlProxyAuth.match(':')){
|
||||
curlOpt.push('-U', `"${options.curlProxyAuth}"`);
|
||||
}
|
||||
}
|
||||
|
||||
const reqId = uuidv4();
|
||||
const headFile = path.join(cache, `/res-headers-${reqId}`);
|
||||
const bodyFile = path.join(cache, `/res-body-${reqId}`);
|
||||
const errFile = path.join(cache, `/res-err-${reqId}`);
|
||||
|
||||
curlOpt.push('-D', `"${headFile}"`);
|
||||
curlOpt.push('-o', `"${bodyFile}"`);
|
||||
curlOpt.push('--stderr', `"${errFile}"`);
|
||||
curlOpt.push('-L', '-s', '-S');
|
||||
|
||||
if(options.minVersion == 'TLSv1.3'){
|
||||
curlOpt.push('--tlsv1.3');
|
||||
}
|
||||
if(options.http2){
|
||||
curlOpt.push('--http2');
|
||||
}
|
||||
|
||||
if(options.body){
|
||||
curlOpt.push('--data-urlencode', `"${options.body}"`);
|
||||
}
|
||||
|
||||
const curlComm = curlOpt.join(' ');
|
||||
|
||||
try{
|
||||
if(options.curlDebug){
|
||||
console.log(curlComm, '\n');
|
||||
}
|
||||
child_process.execSync(curlComm, { stdio: 'inherit', windowsHide: true });
|
||||
}
|
||||
catch(next){
|
||||
const errData = { name: 'RequestError', message: 'EACCES' };
|
||||
try{
|
||||
fs.unlinkSync(headFile);
|
||||
}
|
||||
catch(e){
|
||||
// ignore it...
|
||||
}
|
||||
try{
|
||||
errData.message =
|
||||
fs.readFileSync(errFile, 'utf8')
|
||||
.replace(/^curl: /, '');
|
||||
fs.unlinkSync(errFile);
|
||||
}
|
||||
catch(e){
|
||||
// ignore it...
|
||||
}
|
||||
throw errData;
|
||||
}
|
||||
|
||||
const rawHeaders = fs.readFileSync(headFile, 'utf8');
|
||||
const rawBody = fs.readFileSync(bodyFile);
|
||||
fs.unlinkSync(headFile);
|
||||
fs.unlinkSync(bodyFile);
|
||||
fs.unlinkSync(errFile);
|
||||
|
||||
const res: Res = {
|
||||
httpVersion: '',
|
||||
statusCode: '',
|
||||
statusMessage: '',
|
||||
rawHeaders: rawHeaders,
|
||||
headers: {},
|
||||
rawBody: rawBody,
|
||||
body: rawBody.toString(),
|
||||
};
|
||||
|
||||
const headersCont = rawHeaders.replace(/\r/g, '').split('\n');
|
||||
|
||||
for(const h of headersCont){
|
||||
if( h == '' ){ continue; }
|
||||
if(!h.match(':')){
|
||||
const statusRes = h.split(' ');
|
||||
res.httpVersion = statusRes[0].split('/')[1];
|
||||
res.statusCode = statusRes[1];
|
||||
res.statusMessage = statusRes.slice(2).join(' ');
|
||||
}
|
||||
else{
|
||||
const resHeader = h.split(': ');
|
||||
const resHeadName = resHeader[0].toLowerCase();
|
||||
const resHeadCont = resHeader.slice(1).join(': ');
|
||||
if(resHeadName == 'set-cookie'){
|
||||
if(!Object.prototype.hasOwnProperty.call(res.headers, resHeadName)){
|
||||
res.headers[resHeadName] = [];
|
||||
}
|
||||
(res.headers[resHeadName] as string[]).push(resHeadCont);
|
||||
}
|
||||
else{
|
||||
res.headers[resHeadName] = resHeadCont;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!res.statusCode.match(/^(2|3)\d\d$/)){
|
||||
const httpStatusMessage = res.statusMessage ? ` (${res.statusMessage})` : '';
|
||||
throw {
|
||||
name: 'HTTPError',
|
||||
message: `Response code ${res.statusCode}${httpStatusMessage}`,
|
||||
response: res
|
||||
};
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
};
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
export default curlReq;
|
||||
// build-in
|
||||
import child_process from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import { Headers } from 'got';
|
||||
import path from 'path';
|
||||
|
||||
export type CurlOptions = {
|
||||
headers?: Headers,
|
||||
curlProxy?: boolean,
|
||||
curlProxyAuth?: string,
|
||||
minVersion?: string,
|
||||
http2?: boolean,
|
||||
body?: unknown,
|
||||
curlDebug?: boolean
|
||||
} | undefined;
|
||||
|
||||
export type Res = {
|
||||
httpVersion: string,
|
||||
statusCode: string,
|
||||
statusMessage: string,
|
||||
rawHeaders: string,
|
||||
headers: Record<string, string[]|string>,
|
||||
rawBody: Buffer,
|
||||
body: string,
|
||||
}
|
||||
|
||||
// req
|
||||
const curlReq = async (curlBin: string, url: string, options: CurlOptions, cache: string) => {
|
||||
|
||||
const curlOpt = [
|
||||
`"${curlBin}"`,
|
||||
`"${url}"`,
|
||||
];
|
||||
|
||||
options = options || {};
|
||||
|
||||
if(options.headers && Object.keys(options.headers).length > 0){
|
||||
for(const h of Object.keys(options.headers)){
|
||||
const hC = options.headers[h];
|
||||
curlOpt.push('-H', `"${h}: ${hC}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if(options.curlProxy){
|
||||
curlOpt.push('--proxy-insecure', '-x', `"${options.curlProxy}"`);
|
||||
if(options.curlProxyAuth && typeof options.curlProxyAuth == 'string' && options.curlProxyAuth.match(':')){
|
||||
curlOpt.push('-U', `"${options.curlProxyAuth}"`);
|
||||
}
|
||||
}
|
||||
|
||||
const reqId = uuidv4();
|
||||
const headFile = path.join(cache, `/res-headers-${reqId}`);
|
||||
const bodyFile = path.join(cache, `/res-body-${reqId}`);
|
||||
const errFile = path.join(cache, `/res-err-${reqId}`);
|
||||
|
||||
curlOpt.push('-D', `"${headFile}"`);
|
||||
curlOpt.push('-o', `"${bodyFile}"`);
|
||||
curlOpt.push('--stderr', `"${errFile}"`);
|
||||
curlOpt.push('-L', '-s', '-S');
|
||||
|
||||
if(options.minVersion == 'TLSv1.3'){
|
||||
curlOpt.push('--tlsv1.3');
|
||||
}
|
||||
if(options.http2){
|
||||
curlOpt.push('--http2');
|
||||
}
|
||||
|
||||
if(options.body){
|
||||
curlOpt.push('--data-urlencode', `"${options.body}"`);
|
||||
}
|
||||
|
||||
const curlComm = curlOpt.join(' ');
|
||||
|
||||
try{
|
||||
if(options.curlDebug){
|
||||
console.log(curlComm, '\n');
|
||||
}
|
||||
child_process.execSync(curlComm, { stdio: 'inherit', windowsHide: true });
|
||||
}
|
||||
catch(next){
|
||||
const errData = { name: 'RequestError', message: 'EACCES' };
|
||||
try{
|
||||
fs.unlinkSync(headFile);
|
||||
}
|
||||
catch(e){
|
||||
// ignore it...
|
||||
}
|
||||
try{
|
||||
errData.message =
|
||||
fs.readFileSync(errFile, 'utf8')
|
||||
.replace(/^curl: /, '');
|
||||
fs.unlinkSync(errFile);
|
||||
}
|
||||
catch(e){
|
||||
// ignore it...
|
||||
}
|
||||
throw errData;
|
||||
}
|
||||
|
||||
const rawHeaders = fs.readFileSync(headFile, 'utf8');
|
||||
const rawBody = fs.readFileSync(bodyFile);
|
||||
fs.unlinkSync(headFile);
|
||||
fs.unlinkSync(bodyFile);
|
||||
fs.unlinkSync(errFile);
|
||||
|
||||
const res: Res = {
|
||||
httpVersion: '',
|
||||
statusCode: '',
|
||||
statusMessage: '',
|
||||
rawHeaders: rawHeaders,
|
||||
headers: {},
|
||||
rawBody: rawBody,
|
||||
body: rawBody.toString(),
|
||||
};
|
||||
|
||||
const headersCont = rawHeaders.replace(/\r/g, '').split('\n');
|
||||
|
||||
for(const h of headersCont){
|
||||
if( h == '' ){ continue; }
|
||||
if(!h.match(':')){
|
||||
const statusRes = h.split(' ');
|
||||
res.httpVersion = statusRes[0].split('/')[1];
|
||||
res.statusCode = statusRes[1];
|
||||
res.statusMessage = statusRes.slice(2).join(' ');
|
||||
}
|
||||
else{
|
||||
const resHeader = h.split(': ');
|
||||
const resHeadName = resHeader[0].toLowerCase();
|
||||
const resHeadCont = resHeader.slice(1).join(': ');
|
||||
if(resHeadName == 'set-cookie'){
|
||||
if(!Object.prototype.hasOwnProperty.call(res.headers, resHeadName)){
|
||||
res.headers[resHeadName] = [];
|
||||
}
|
||||
(res.headers[resHeadName] as string[]).push(resHeadCont);
|
||||
}
|
||||
else{
|
||||
res.headers[resHeadName] = resHeadCont;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!res.statusCode.match(/^(2|3)\d\d$/)){
|
||||
const httpStatusMessage = res.statusMessage ? ` (${res.statusMessage})` : '';
|
||||
throw {
|
||||
name: 'HTTPError',
|
||||
message: `Response code ${res.statusCode}${httpStatusMessage}`,
|
||||
response: res
|
||||
};
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
};
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
export default curlReq;
|
||||
|
|
|
|||
|
|
@ -1,113 +1,113 @@
|
|||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { ArgvType } from './module.app-args';
|
||||
|
||||
const workingDir = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? path.dirname(process.execPath) : path.join(__dirname, '/..');
|
||||
export const archiveFile = path.join(workingDir, 'config', 'archive.json');
|
||||
|
||||
export type ItemType = {
|
||||
id: string,
|
||||
already: string[]
|
||||
}[]
|
||||
|
||||
export type DataType = {
|
||||
funi: {
|
||||
s: ItemType
|
||||
},
|
||||
crunchy: {
|
||||
srz: ItemType,
|
||||
s: ItemType
|
||||
}
|
||||
}
|
||||
|
||||
const addToArchive = (kind: {
|
||||
service: 'funi',
|
||||
type: 's'
|
||||
} | {
|
||||
service: 'crunchy',
|
||||
type: 's'|'srz'
|
||||
}, ID: string) => {
|
||||
const data = loadData();
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(data, kind.service)) {
|
||||
const items = kind.service === 'crunchy' ? data[kind.service][kind.type] : data[kind.service][kind.type];
|
||||
if (items.findIndex(a => a.id === ID) < 0) // Prevent duplicate
|
||||
return;
|
||||
items.push({
|
||||
id: ID,
|
||||
already: []
|
||||
});
|
||||
} else {
|
||||
if (kind.service === 'funi') {
|
||||
data['funi'] = {
|
||||
s: [
|
||||
{
|
||||
id: ID,
|
||||
already: []
|
||||
}
|
||||
]
|
||||
};
|
||||
} else {
|
||||
data['crunchy'] = {
|
||||
s: ([] as ItemType).concat(kind.type === 's' ? {
|
||||
id: ID,
|
||||
already: [] as string[]
|
||||
} : []),
|
||||
srz: ([] as ItemType).concat(kind.type === 'srz' ? {
|
||||
id: ID,
|
||||
already: [] as string[]
|
||||
} : []),
|
||||
};
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(archiveFile, JSON.stringify(data, null, 4));
|
||||
};
|
||||
|
||||
const downloaded = (kind: {
|
||||
service: 'funi',
|
||||
type: 's'
|
||||
} | {
|
||||
service: 'crunchy',
|
||||
type: 's'|'srz'
|
||||
}, ID: string, episode: string[]) => {
|
||||
let data = loadData();
|
||||
if (!Object.prototype.hasOwnProperty.call(data, kind.service)) {
|
||||
addToArchive(kind, ID);
|
||||
data = loadData(); // Load updated version
|
||||
}
|
||||
(kind.service == 'crunchy' ? data[kind.service][kind.type] : data[kind.service][kind.type]).find(a => a.id === ID)?.already.push(...episode);
|
||||
fs.writeFileSync(archiveFile, JSON.stringify(data, null, 4));
|
||||
};
|
||||
|
||||
const makeCommand = (service: 'funi'|'crunchy') : Partial<ArgvType>[] => {
|
||||
const data = loadData();
|
||||
const ret: Partial<ArgvType>[] = [];
|
||||
const kind = data[service];
|
||||
for (const type of Object.keys(kind)) {
|
||||
const item = kind[type as 's']; // 'srz' is also possible but will be ignored for the compiler
|
||||
item.forEach(i => ret.push({
|
||||
but: true,
|
||||
all: false,
|
||||
service,
|
||||
e: i.already.join(','),
|
||||
...(type === 's' ? {
|
||||
s: i.id,
|
||||
series: undefined
|
||||
} : {
|
||||
series: i.id,
|
||||
s: undefined
|
||||
})
|
||||
}));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const loadData = () : DataType => {
|
||||
if (fs.existsSync(archiveFile))
|
||||
return JSON.parse(fs.readFileSync(archiveFile).toString()) as DataType;
|
||||
return {} as DataType;
|
||||
};
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { ArgvType } from './module.app-args';
|
||||
|
||||
const workingDir = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? path.dirname(process.execPath) : path.join(__dirname, '/..');
|
||||
export const archiveFile = path.join(workingDir, 'config', 'archive.json');
|
||||
|
||||
export type ItemType = {
|
||||
id: string,
|
||||
already: string[]
|
||||
}[]
|
||||
|
||||
export type DataType = {
|
||||
funi: {
|
||||
s: ItemType
|
||||
},
|
||||
crunchy: {
|
||||
srz: ItemType,
|
||||
s: ItemType
|
||||
}
|
||||
}
|
||||
|
||||
const addToArchive = (kind: {
|
||||
service: 'funi',
|
||||
type: 's'
|
||||
} | {
|
||||
service: 'crunchy',
|
||||
type: 's'|'srz'
|
||||
}, ID: string) => {
|
||||
const data = loadData();
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(data, kind.service)) {
|
||||
const items = kind.service === 'crunchy' ? data[kind.service][kind.type] : data[kind.service][kind.type];
|
||||
if (items.findIndex(a => a.id === ID) < 0) // Prevent duplicate
|
||||
return;
|
||||
items.push({
|
||||
id: ID,
|
||||
already: []
|
||||
});
|
||||
} else {
|
||||
if (kind.service === 'funi') {
|
||||
data['funi'] = {
|
||||
s: [
|
||||
{
|
||||
id: ID,
|
||||
already: []
|
||||
}
|
||||
]
|
||||
};
|
||||
} else {
|
||||
data['crunchy'] = {
|
||||
s: ([] as ItemType).concat(kind.type === 's' ? {
|
||||
id: ID,
|
||||
already: [] as string[]
|
||||
} : []),
|
||||
srz: ([] as ItemType).concat(kind.type === 'srz' ? {
|
||||
id: ID,
|
||||
already: [] as string[]
|
||||
} : []),
|
||||
};
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(archiveFile, JSON.stringify(data, null, 4));
|
||||
};
|
||||
|
||||
const downloaded = (kind: {
|
||||
service: 'funi',
|
||||
type: 's'
|
||||
} | {
|
||||
service: 'crunchy',
|
||||
type: 's'|'srz'
|
||||
}, ID: string, episode: string[]) => {
|
||||
let data = loadData();
|
||||
if (!Object.prototype.hasOwnProperty.call(data, kind.service)) {
|
||||
addToArchive(kind, ID);
|
||||
data = loadData(); // Load updated version
|
||||
}
|
||||
(kind.service == 'crunchy' ? data[kind.service][kind.type] : data[kind.service][kind.type]).find(a => a.id === ID)?.already.push(...episode);
|
||||
fs.writeFileSync(archiveFile, JSON.stringify(data, null, 4));
|
||||
};
|
||||
|
||||
const makeCommand = (service: 'funi'|'crunchy') : Partial<ArgvType>[] => {
|
||||
const data = loadData();
|
||||
const ret: Partial<ArgvType>[] = [];
|
||||
const kind = data[service];
|
||||
for (const type of Object.keys(kind)) {
|
||||
const item = kind[type as 's']; // 'srz' is also possible but will be ignored for the compiler
|
||||
item.forEach(i => ret.push({
|
||||
but: true,
|
||||
all: false,
|
||||
service,
|
||||
e: i.already.join(','),
|
||||
...(type === 's' ? {
|
||||
s: i.id,
|
||||
series: undefined
|
||||
} : {
|
||||
series: i.id,
|
||||
s: undefined
|
||||
})
|
||||
}));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const loadData = () : DataType => {
|
||||
if (fs.existsSync(archiveFile))
|
||||
return JSON.parse(fs.readFileSync(archiveFile).toString()) as DataType;
|
||||
return {} as DataType;
|
||||
};
|
||||
|
||||
export { addToArchive, downloaded, makeCommand };
|
||||
|
|
@ -1,41 +1,41 @@
|
|||
import * as shlp from 'sei-helper';
|
||||
import path from 'path';
|
||||
import { AvailableFilenameVars } from './module.args';
|
||||
|
||||
|
||||
export type Variable = ({
|
||||
type: 'number',
|
||||
replaceWith: number
|
||||
} | {
|
||||
type: 'string',
|
||||
replaceWith: string
|
||||
}) & {
|
||||
name: AvailableFilenameVars
|
||||
}
|
||||
|
||||
const parseFileName = (input: string, variables: Variable[], numbers: 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];
|
||||
const varName = type.slice(2, -1);
|
||||
const use = variables.find(a => a.name === varName);
|
||||
if (use === undefined) {
|
||||
console.log(`[ERROR] Found variable '${type}' in fileName but no values was internally found!`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (use.type === 'number') {
|
||||
const len = use.replaceWith.toFixed(0).length;
|
||||
const replaceStr = len < numbers ? '0'.repeat(numbers - len) + use.replaceWith : use.replaceWith.toFixed(0);
|
||||
input = input.replace(type, replaceStr);
|
||||
} else {
|
||||
input = input.replace(type, use.replaceWith);
|
||||
}
|
||||
}
|
||||
return input.split(path.sep).map(a => shlp.cleanupFilename(a));
|
||||
};
|
||||
|
||||
import * as shlp from 'sei-helper';
|
||||
import path from 'path';
|
||||
import { AvailableFilenameVars } from './module.args';
|
||||
|
||||
|
||||
export type Variable = ({
|
||||
type: 'number',
|
||||
replaceWith: number
|
||||
} | {
|
||||
type: 'string',
|
||||
replaceWith: string
|
||||
}) & {
|
||||
name: AvailableFilenameVars
|
||||
}
|
||||
|
||||
const parseFileName = (input: string, variables: Variable[], numbers: 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];
|
||||
const varName = type.slice(2, -1);
|
||||
const use = variables.find(a => a.name === varName);
|
||||
if (use === undefined) {
|
||||
console.log(`[ERROR] Found variable '${type}' in fileName but no values was internally found!`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (use.type === 'number') {
|
||||
const len = use.replaceWith.toFixed(0).length;
|
||||
const replaceStr = len < numbers ? '0'.repeat(numbers - len) + use.replaceWith : use.replaceWith.toFixed(0);
|
||||
input = input.replace(type, replaceStr);
|
||||
} else {
|
||||
input = input.replace(type, use.replaceWith);
|
||||
}
|
||||
}
|
||||
return input.split(path.sep).map(a => shlp.cleanupFilename(a));
|
||||
};
|
||||
|
||||
export default parseFileName;
|
||||
|
|
@ -1,55 +1,55 @@
|
|||
// fonts src
|
||||
const root = 'https://static.crunchyroll.com/vilos-v2/web/vilos/assets/libass-fonts/';
|
||||
|
||||
// file list
|
||||
const fontFamilies = {
|
||||
'Adobe Arabic': [ 'AdobeArabic-Bold.otf', ],
|
||||
'Andale Mono': [ 'andalemo.ttf', ],
|
||||
'Arial': [ 'arial.ttf', 'arialbd.ttf', 'arialbi.ttf', 'ariali.ttf', ],
|
||||
'Arial Unicode MS': [ 'arialuni.ttf', ],
|
||||
'Arial Black': [ 'ariblk.ttf', ],
|
||||
'Comic Sans MS': [ 'comic.ttf', 'comicbd.ttf', ],
|
||||
'Courier New': [ 'cour.ttf', 'courbd.ttf', 'courbi.ttf', 'couri.ttf', ],
|
||||
'DejaVu LGC Sans Mono': [ 'DejaVuLGCSansMono-Bold.ttf', 'DejaVuLGCSansMono-BoldOblique.ttf', 'DejaVuLGCSansMono-Oblique.ttf', 'DejaVuLGCSansMono.ttf', ],
|
||||
'DejaVu Sans': [ 'DejaVuSans-Bold.ttf', 'DejaVuSans-BoldOblique.ttf', 'DejaVuSans-ExtraLight.ttf', 'DejaVuSans-Oblique.ttf', 'DejaVuSans.ttf', ],
|
||||
'DejaVu Sans Condensed': [ 'DejaVuSansCondensed-Bold.ttf', 'DejaVuSansCondensed-BoldOblique.ttf', 'DejaVuSansCondensed-Oblique.ttf', 'DejaVuSansCondensed.ttf', ],
|
||||
'DejaVu Sans Mono': [ 'DejaVuSansMono-Bold.ttf', 'DejaVuSansMono-BoldOblique.ttf', 'DejaVuSansMono-Oblique.ttf', 'DejaVuSansMono.ttf', ],
|
||||
'Georgia': [ 'georgia.ttf', 'georgiab.ttf', 'georgiai.ttf', 'georgiaz.ttf', ],
|
||||
'Impact': [ 'impact.ttf', ],
|
||||
'Rubik Black': [ 'Rubik-Black.ttf', 'Rubik-BlackItalic.ttf', ],
|
||||
'Rubik': [ 'Rubik-Bold.ttf', 'Rubik-BoldItalic.ttf', 'Rubik-Italic.ttf', 'Rubik-Light.ttf', 'Rubik-LightItalic.ttf', 'Rubik-Medium.ttf', 'Rubik-MediumItalic.ttf', 'Rubik-Regular.ttf', ],
|
||||
'Tahoma': [ 'tahoma.ttf', ],
|
||||
'Times New Roman': [ 'times.ttf', 'timesbd.ttf', 'timesbi.ttf', 'timesi.ttf', ],
|
||||
'Trebuchet MS': [ 'trebuc.ttf', 'trebucbd.ttf', 'trebucbi.ttf', 'trebucit.ttf', ],
|
||||
'Verdana': [ 'verdana.ttf', 'verdanab.ttf', 'verdanai.ttf', 'verdanaz.ttf', ],
|
||||
'Webdings': [ 'webdings.ttf', ],
|
||||
};
|
||||
|
||||
// collect styles from ass string
|
||||
function assFonts(ass: string){
|
||||
const strings = ass.replace(/\r/g,'').split('\n');
|
||||
const styles = [];
|
||||
for(const s of strings){
|
||||
if(s.match(/^Style: /)){
|
||||
const addStyle = s.split(',');
|
||||
styles.push(addStyle[1]);
|
||||
}
|
||||
}
|
||||
return [...new Set(styles)];
|
||||
}
|
||||
|
||||
// font mime type
|
||||
function fontMime(fontFile: string){
|
||||
if(fontFile.match(/\.otf$/)){
|
||||
return 'application/vnd.ms-opentype';
|
||||
}
|
||||
if(fontFile.match(/\.ttf$/)){
|
||||
return 'application/x-truetype-font';
|
||||
}
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
export type AvailableFonts = keyof typeof fontFamilies;
|
||||
|
||||
// output
|
||||
export { root, fontFamilies, assFonts, fontMime };
|
||||
// fonts src
|
||||
const root = 'https://static.crunchyroll.com/vilos-v2/web/vilos/assets/libass-fonts/';
|
||||
|
||||
// file list
|
||||
const fontFamilies = {
|
||||
'Adobe Arabic': [ 'AdobeArabic-Bold.otf', ],
|
||||
'Andale Mono': [ 'andalemo.ttf', ],
|
||||
'Arial': [ 'arial.ttf', 'arialbd.ttf', 'arialbi.ttf', 'ariali.ttf', ],
|
||||
'Arial Unicode MS': [ 'arialuni.ttf', ],
|
||||
'Arial Black': [ 'ariblk.ttf', ],
|
||||
'Comic Sans MS': [ 'comic.ttf', 'comicbd.ttf', ],
|
||||
'Courier New': [ 'cour.ttf', 'courbd.ttf', 'courbi.ttf', 'couri.ttf', ],
|
||||
'DejaVu LGC Sans Mono': [ 'DejaVuLGCSansMono-Bold.ttf', 'DejaVuLGCSansMono-BoldOblique.ttf', 'DejaVuLGCSansMono-Oblique.ttf', 'DejaVuLGCSansMono.ttf', ],
|
||||
'DejaVu Sans': [ 'DejaVuSans-Bold.ttf', 'DejaVuSans-BoldOblique.ttf', 'DejaVuSans-ExtraLight.ttf', 'DejaVuSans-Oblique.ttf', 'DejaVuSans.ttf', ],
|
||||
'DejaVu Sans Condensed': [ 'DejaVuSansCondensed-Bold.ttf', 'DejaVuSansCondensed-BoldOblique.ttf', 'DejaVuSansCondensed-Oblique.ttf', 'DejaVuSansCondensed.ttf', ],
|
||||
'DejaVu Sans Mono': [ 'DejaVuSansMono-Bold.ttf', 'DejaVuSansMono-BoldOblique.ttf', 'DejaVuSansMono-Oblique.ttf', 'DejaVuSansMono.ttf', ],
|
||||
'Georgia': [ 'georgia.ttf', 'georgiab.ttf', 'georgiai.ttf', 'georgiaz.ttf', ],
|
||||
'Impact': [ 'impact.ttf', ],
|
||||
'Rubik Black': [ 'Rubik-Black.ttf', 'Rubik-BlackItalic.ttf', ],
|
||||
'Rubik': [ 'Rubik-Bold.ttf', 'Rubik-BoldItalic.ttf', 'Rubik-Italic.ttf', 'Rubik-Light.ttf', 'Rubik-LightItalic.ttf', 'Rubik-Medium.ttf', 'Rubik-MediumItalic.ttf', 'Rubik-Regular.ttf', ],
|
||||
'Tahoma': [ 'tahoma.ttf', ],
|
||||
'Times New Roman': [ 'times.ttf', 'timesbd.ttf', 'timesbi.ttf', 'timesi.ttf', ],
|
||||
'Trebuchet MS': [ 'trebuc.ttf', 'trebucbd.ttf', 'trebucbi.ttf', 'trebucit.ttf', ],
|
||||
'Verdana': [ 'verdana.ttf', 'verdanab.ttf', 'verdanai.ttf', 'verdanaz.ttf', ],
|
||||
'Webdings': [ 'webdings.ttf', ],
|
||||
};
|
||||
|
||||
// collect styles from ass string
|
||||
function assFonts(ass: string){
|
||||
const strings = ass.replace(/\r/g,'').split('\n');
|
||||
const styles = [];
|
||||
for(const s of strings){
|
||||
if(s.match(/^Style: /)){
|
||||
const addStyle = s.split(',');
|
||||
styles.push(addStyle[1]);
|
||||
}
|
||||
}
|
||||
return [...new Set(styles)];
|
||||
}
|
||||
|
||||
// font mime type
|
||||
function fontMime(fontFile: string){
|
||||
if(fontFile.match(/\.otf$/)){
|
||||
return 'application/vnd.ms-opentype';
|
||||
}
|
||||
if(fontFile.match(/\.ttf$/)){
|
||||
return 'application/x-truetype-font';
|
||||
}
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
export type AvailableFonts = keyof typeof fontFamilies;
|
||||
|
||||
// output
|
||||
export { root, fontFamilies, assFonts, fontMime };
|
||||
|
|
|
|||
|
|
@ -1,130 +1,130 @@
|
|||
import got, { OptionsOfUnknownResponseBody, ReadError, Response, ResponseType } from 'got';
|
||||
|
||||
// Used for future updates
|
||||
// const argv = require('../funi').argv;
|
||||
//
|
||||
// const lang = {
|
||||
// 'ptBR': {
|
||||
// langCode: 'pt-BR',
|
||||
// regionCode: 'BR'
|
||||
// },
|
||||
// 'esLA': {
|
||||
// langCode: 'es-LA',
|
||||
// regionCode: 'MX'
|
||||
// }
|
||||
// };
|
||||
|
||||
|
||||
export type Options = {
|
||||
url: string,
|
||||
responseType?: ResponseType,
|
||||
baseUrl?: string,
|
||||
querystring?: Record<string, any>,
|
||||
auth?: {
|
||||
user: string,
|
||||
pass: string
|
||||
},
|
||||
useToken?: boolean,
|
||||
token?: string|boolean,
|
||||
dinstid?: boolean|string,
|
||||
debug?: boolean
|
||||
}
|
||||
// TODO convert to class
|
||||
const getData = async <T = string>(options: Options) => {
|
||||
const regionHeaders = {};
|
||||
|
||||
|
||||
const gOptions = {
|
||||
url: options.url,
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0',
|
||||
'Accept-Encoding': 'gzip',
|
||||
...regionHeaders
|
||||
}
|
||||
} as OptionsOfUnknownResponseBody;
|
||||
if(options.responseType) {
|
||||
gOptions.responseType = options.responseType;
|
||||
}
|
||||
if(options.baseUrl){
|
||||
gOptions.prefixUrl = options.baseUrl;
|
||||
gOptions.url = gOptions.url?.toString().replace(/^\//,'');
|
||||
}
|
||||
if(options.querystring){
|
||||
gOptions.url += `?${new URLSearchParams(options.querystring).toString()}`;
|
||||
}
|
||||
if(options.auth){
|
||||
gOptions.method = 'POST';
|
||||
const newHeaders = {
|
||||
...gOptions.headers,
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Origin': 'https://ww.funimation.com',
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0'
|
||||
};
|
||||
gOptions.headers = newHeaders;
|
||||
gOptions.body = `username=${encodeURIComponent(options.auth.user)}&password=${encodeURIComponent(options.auth.pass)}`;
|
||||
}
|
||||
if(options.useToken && options.token){
|
||||
gOptions.headers = {
|
||||
...gOptions.headers,
|
||||
Authorization: `Token ${options.token}`
|
||||
};
|
||||
}
|
||||
if(options.dinstid){
|
||||
gOptions.headers = {
|
||||
...gOptions.headers,
|
||||
devicetype: 'Android Phone'
|
||||
};
|
||||
}
|
||||
// debug
|
||||
gOptions.hooks = {
|
||||
beforeRequest: [
|
||||
(gotOpts) => {
|
||||
if(options.debug){
|
||||
console.log('[DEBUG] GOT OPTIONS:');
|
||||
console.log(gotOpts);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
try {
|
||||
const res = await got(gOptions);
|
||||
if(res.body && (options.responseType !== 'buffer' && (res.body as string).match(/^</))){
|
||||
throw { name: 'HTMLError', res };
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
res: {
|
||||
...res,
|
||||
body: res.body as T
|
||||
},
|
||||
};
|
||||
}
|
||||
catch(_error){
|
||||
const error = _error as {
|
||||
name: string,
|
||||
} & ReadError & {
|
||||
res: Response<unknown>
|
||||
};
|
||||
if(options.debug){
|
||||
console.log(error);
|
||||
}
|
||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||
}
|
||||
else if(error.name && error.name == 'HTMLError' && error.res && error.res.body){
|
||||
console.log(`[ERROR] ${error.name}:`);
|
||||
console.log(error.res.body);
|
||||
}
|
||||
else{
|
||||
console.log(`[ERROR] ${error.name}: ${error.code||error.message}`);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default getData;
|
||||
import got, { OptionsOfUnknownResponseBody, ReadError, Response, ResponseType } from 'got';
|
||||
|
||||
// Used for future updates
|
||||
// const argv = require('../funi').argv;
|
||||
//
|
||||
// const lang = {
|
||||
// 'ptBR': {
|
||||
// langCode: 'pt-BR',
|
||||
// regionCode: 'BR'
|
||||
// },
|
||||
// 'esLA': {
|
||||
// langCode: 'es-LA',
|
||||
// regionCode: 'MX'
|
||||
// }
|
||||
// };
|
||||
|
||||
|
||||
export type Options = {
|
||||
url: string,
|
||||
responseType?: ResponseType,
|
||||
baseUrl?: string,
|
||||
querystring?: Record<string, any>,
|
||||
auth?: {
|
||||
user: string,
|
||||
pass: string
|
||||
},
|
||||
useToken?: boolean,
|
||||
token?: string|boolean,
|
||||
dinstid?: boolean|string,
|
||||
debug?: boolean
|
||||
}
|
||||
// TODO convert to class
|
||||
const getData = async <T = string>(options: Options) => {
|
||||
const regionHeaders = {};
|
||||
|
||||
|
||||
const gOptions = {
|
||||
url: options.url,
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0',
|
||||
'Accept-Encoding': 'gzip',
|
||||
...regionHeaders
|
||||
}
|
||||
} as OptionsOfUnknownResponseBody;
|
||||
if(options.responseType) {
|
||||
gOptions.responseType = options.responseType;
|
||||
}
|
||||
if(options.baseUrl){
|
||||
gOptions.prefixUrl = options.baseUrl;
|
||||
gOptions.url = gOptions.url?.toString().replace(/^\//,'');
|
||||
}
|
||||
if(options.querystring){
|
||||
gOptions.url += `?${new URLSearchParams(options.querystring).toString()}`;
|
||||
}
|
||||
if(options.auth){
|
||||
gOptions.method = 'POST';
|
||||
const newHeaders = {
|
||||
...gOptions.headers,
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Origin': 'https://ww.funimation.com',
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0'
|
||||
};
|
||||
gOptions.headers = newHeaders;
|
||||
gOptions.body = `username=${encodeURIComponent(options.auth.user)}&password=${encodeURIComponent(options.auth.pass)}`;
|
||||
}
|
||||
if(options.useToken && options.token){
|
||||
gOptions.headers = {
|
||||
...gOptions.headers,
|
||||
Authorization: `Token ${options.token}`
|
||||
};
|
||||
}
|
||||
if(options.dinstid){
|
||||
gOptions.headers = {
|
||||
...gOptions.headers,
|
||||
devicetype: 'Android Phone'
|
||||
};
|
||||
}
|
||||
// debug
|
||||
gOptions.hooks = {
|
||||
beforeRequest: [
|
||||
(gotOpts) => {
|
||||
if(options.debug){
|
||||
console.log('[DEBUG] GOT OPTIONS:');
|
||||
console.log(gotOpts);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
try {
|
||||
const res = await got(gOptions);
|
||||
if(res.body && (options.responseType !== 'buffer' && (res.body as string).match(/^</))){
|
||||
throw { name: 'HTMLError', res };
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
res: {
|
||||
...res,
|
||||
body: res.body as T
|
||||
},
|
||||
};
|
||||
}
|
||||
catch(_error){
|
||||
const error = _error as {
|
||||
name: string,
|
||||
} & ReadError & {
|
||||
res: Response<unknown>
|
||||
};
|
||||
if(options.debug){
|
||||
console.log(error);
|
||||
}
|
||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||
}
|
||||
else if(error.name && error.name == 'HTMLError' && error.res && error.res.body){
|
||||
console.log(`[ERROR] ${error.name}:`);
|
||||
console.log(error.res.body);
|
||||
}
|
||||
else{
|
||||
console.log(`[ERROR] ${error.name}: ${error.code||error.message}`);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default getData;
|
||||
|
|
|
|||
|
|
@ -1,182 +1,182 @@
|
|||
// available langs
|
||||
|
||||
export type LanguageItem = {
|
||||
cr_locale?: string,
|
||||
locale: string,
|
||||
code: string,
|
||||
name: string,
|
||||
language?: string,
|
||||
funi_locale?: string,
|
||||
funi_name?: string
|
||||
}
|
||||
|
||||
const languages: LanguageItem[] = [
|
||||
{ cr_locale: 'es-LA', funi_name: 'Spanish (Latin Am)', funi_locale: 'esLA', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' },
|
||||
{ cr_locale: 'es-419', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' },
|
||||
{ cr_locale: 'es-ES', locale: 'es', code: 'spa', name: 'Spanish' },
|
||||
{ cr_locale: 'pt-BR', funi_name: 'Portuguese (Brazil)', funi_locale: 'ptBR', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' },
|
||||
{ cr_locale: 'fr-FR', locale: 'fr', code: 'fra', name: 'French' },
|
||||
{ cr_locale: 'de-DE', locale: 'de', code: 'deu', name: 'German' },
|
||||
{ cr_locale: 'ar-ME', locale: 'ar', code: 'ara', name: 'Arabic' },
|
||||
{ cr_locale: 'ar-SA', locale: 'ar', code: 'ara', name: 'Arabic' },
|
||||
{ cr_locale: 'it-IT', locale: 'it', code: 'ita', name: 'Italian' },
|
||||
{ cr_locale: 'ru-RU', locale: 'ru', code: 'rus', name: 'Russian' },
|
||||
{ cr_locale: 'tr-TR', locale: 'tr', code: 'tur', name: 'Turkish' },
|
||||
{ funi_locale: 'zhMN', locale: 'zh', code: 'cmn', name: 'Chinese (Mandarin, PRC)' },
|
||||
{ cr_locale: 'en-US', funi_locale: 'enUS', locale: 'en', code: 'eng', name: 'English' },
|
||||
{ cr_locale: 'ja-JP', funi_locale: 'jaJP', locale: 'ja', code: 'jpn', name: 'Japanese' },
|
||||
];
|
||||
|
||||
// add en language names
|
||||
(() =>{
|
||||
for(const languageIndex in languages){
|
||||
if(!languages[languageIndex].language){
|
||||
languages[languageIndex].language = languages[languageIndex].name;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// construct dub language codes
|
||||
const dubLanguageCodes = (() => {
|
||||
const dubLanguageCodesArray = [];
|
||||
for(const language of languages){
|
||||
dubLanguageCodesArray.push(language.code);
|
||||
}
|
||||
return [...new Set(dubLanguageCodesArray)];
|
||||
})();
|
||||
|
||||
// construct subtitle languages filter
|
||||
const subtitleLanguagesFilter = (() => {
|
||||
const subtitleLanguagesExtraParameters = ['all', 'none'];
|
||||
return [...subtitleLanguagesExtraParameters, ...new Set(languages.map(l => { return l.locale; }))];
|
||||
})();
|
||||
|
||||
const searchLocales = (() => {
|
||||
return ['', ...new Set(languages.map(l => { return l.cr_locale; }).slice(0, -1))];
|
||||
})();
|
||||
|
||||
// convert
|
||||
const fixLanguageTag = (tag: string) => {
|
||||
tag = typeof tag == 'string' ? tag : 'und';
|
||||
const tagLangLC = tag.match(/^(\w{2})-?(\w{2})$/);
|
||||
if(tagLangLC){
|
||||
const tagLang = `${tagLangLC[1]}-${tagLangLC[2].toUpperCase()}`;
|
||||
if(findLang(tagLang).cr_locale != 'und'){
|
||||
return findLang(tagLang).cr_locale;
|
||||
}
|
||||
else{
|
||||
return tagLang;
|
||||
}
|
||||
}
|
||||
else{
|
||||
return tag;
|
||||
}
|
||||
};
|
||||
|
||||
// find lang by cr_locale
|
||||
const findLang = (cr_locale: string) => {
|
||||
const lang = languages.find(l => { return l.cr_locale == cr_locale; });
|
||||
return lang ? lang : { cr_locale: 'und', locale: 'un', code: 'und', name: '', language: '' };
|
||||
};
|
||||
|
||||
const fixAndFindCrLC = (cr_locale: string) => {
|
||||
const str = fixLanguageTag(cr_locale);
|
||||
return findLang(str || '');
|
||||
};
|
||||
|
||||
// rss subs lang parser
|
||||
const parseRssSubtitlesString = (subs: string) => {
|
||||
const splitMap = subs.replace(/\s/g, '').split(',').map((s) => {
|
||||
return fixAndFindCrLC(s).locale;
|
||||
});
|
||||
const sort = sortTags(splitMap);
|
||||
return sort.join(', ');
|
||||
};
|
||||
|
||||
|
||||
// parse subtitles Array
|
||||
const parseSubtitlesArray = (tags: string[]) => {
|
||||
const sort = sortSubtitles(tags.map((t) => {
|
||||
return { locale: fixAndFindCrLC(t).locale };
|
||||
}));
|
||||
return sort.map((t) => { return t.locale; }).join(', ');
|
||||
};
|
||||
|
||||
// sort subtitles
|
||||
const sortSubtitles = <T extends {
|
||||
[key: string]: unknown
|
||||
} = Record<string, string>> (data: T[], sortkey?: keyof T) : T[] => {
|
||||
const idx: Record<string, number> = {};
|
||||
const key = sortkey || 'locale' as keyof T;
|
||||
const tags = [...new Set(Object.values(languages).map(e => e.locale))];
|
||||
for(const l of tags){
|
||||
idx[l] = Object.keys(idx).length + 1;
|
||||
}
|
||||
data.sort((a, b) => {
|
||||
const ia = idx[a[key] as string] ? idx[a[key] as string] : 50;
|
||||
const ib = idx[b[key] as string] ? idx[b[key] as string] : 50;
|
||||
return ia - ib;
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
const sortTags = (data: string[]) => {
|
||||
const retData = data.map(e => { return { locale: e }; });
|
||||
const sort = sortSubtitles(retData);
|
||||
return sort.map(e => e.locale as string);
|
||||
};
|
||||
|
||||
const subsFile = (fnOutput:string, subsIndex: string, langItem: LanguageItem) => {
|
||||
subsIndex = (parseInt(subsIndex) + 1).toString().padStart(2, '0');
|
||||
return `${fnOutput}.${subsIndex} ${langItem.code} ${langItem.language}.ass`;
|
||||
};
|
||||
|
||||
// construct dub langs const
|
||||
const dubLanguages = (() => {
|
||||
const dubDb: Record<string, string> = {};
|
||||
for(const lang of languages){
|
||||
if(!Object.keys(dubDb).includes(lang.name)){
|
||||
dubDb[lang.name] = lang.code;
|
||||
}
|
||||
}
|
||||
return dubDb;
|
||||
})();
|
||||
|
||||
// dub regex
|
||||
const dubRegExpStr =
|
||||
`\\((${Object.keys(dubLanguages).join('|')})(?: (Dub|VO))?\\)$`;
|
||||
const dubRegExp = new RegExp(dubRegExpStr);
|
||||
|
||||
// code to lang name
|
||||
const langCode2name = (code: string) => {
|
||||
const codeIdx = dubLanguageCodes.indexOf(code);
|
||||
return Object.keys(dubLanguages)[codeIdx];
|
||||
};
|
||||
|
||||
// locale to lang name
|
||||
const locale2language = (locale: string) => {
|
||||
const filteredLocale = languages.filter(l => {
|
||||
return l.locale == locale;
|
||||
});
|
||||
return filteredLocale[0];
|
||||
};
|
||||
|
||||
// output
|
||||
export {
|
||||
languages,
|
||||
dubLanguageCodes,
|
||||
dubLanguages,
|
||||
langCode2name,
|
||||
locale2language,
|
||||
dubRegExp,
|
||||
subtitleLanguagesFilter,
|
||||
searchLocales,
|
||||
fixLanguageTag,
|
||||
findLang,
|
||||
fixAndFindCrLC,
|
||||
parseRssSubtitlesString,
|
||||
parseSubtitlesArray,
|
||||
sortSubtitles,
|
||||
sortTags,
|
||||
subsFile,
|
||||
};
|
||||
// available langs
|
||||
|
||||
export type LanguageItem = {
|
||||
cr_locale?: string,
|
||||
locale: string,
|
||||
code: string,
|
||||
name: string,
|
||||
language?: string,
|
||||
funi_locale?: string,
|
||||
funi_name?: string
|
||||
}
|
||||
|
||||
const languages: LanguageItem[] = [
|
||||
{ cr_locale: 'es-LA', funi_name: 'Spanish (Latin Am)', funi_locale: 'esLA', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' },
|
||||
{ cr_locale: 'es-419', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' },
|
||||
{ cr_locale: 'es-ES', locale: 'es', code: 'spa', name: 'Spanish' },
|
||||
{ cr_locale: 'pt-BR', funi_name: 'Portuguese (Brazil)', funi_locale: 'ptBR', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' },
|
||||
{ cr_locale: 'fr-FR', locale: 'fr', code: 'fra', name: 'French' },
|
||||
{ cr_locale: 'de-DE', locale: 'de', code: 'deu', name: 'German' },
|
||||
{ cr_locale: 'ar-ME', locale: 'ar', code: 'ara', name: 'Arabic' },
|
||||
{ cr_locale: 'ar-SA', locale: 'ar', code: 'ara', name: 'Arabic' },
|
||||
{ cr_locale: 'it-IT', locale: 'it', code: 'ita', name: 'Italian' },
|
||||
{ cr_locale: 'ru-RU', locale: 'ru', code: 'rus', name: 'Russian' },
|
||||
{ cr_locale: 'tr-TR', locale: 'tr', code: 'tur', name: 'Turkish' },
|
||||
{ funi_locale: 'zhMN', locale: 'zh', code: 'cmn', name: 'Chinese (Mandarin, PRC)' },
|
||||
{ cr_locale: 'en-US', funi_locale: 'enUS', locale: 'en', code: 'eng', name: 'English' },
|
||||
{ cr_locale: 'ja-JP', funi_locale: 'jaJP', locale: 'ja', code: 'jpn', name: 'Japanese' },
|
||||
];
|
||||
|
||||
// add en language names
|
||||
(() =>{
|
||||
for(const languageIndex in languages){
|
||||
if(!languages[languageIndex].language){
|
||||
languages[languageIndex].language = languages[languageIndex].name;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// construct dub language codes
|
||||
const dubLanguageCodes = (() => {
|
||||
const dubLanguageCodesArray = [];
|
||||
for(const language of languages){
|
||||
dubLanguageCodesArray.push(language.code);
|
||||
}
|
||||
return [...new Set(dubLanguageCodesArray)];
|
||||
})();
|
||||
|
||||
// construct subtitle languages filter
|
||||
const subtitleLanguagesFilter = (() => {
|
||||
const subtitleLanguagesExtraParameters = ['all', 'none'];
|
||||
return [...subtitleLanguagesExtraParameters, ...new Set(languages.map(l => { return l.locale; }))];
|
||||
})();
|
||||
|
||||
const searchLocales = (() => {
|
||||
return ['', ...new Set(languages.map(l => { return l.cr_locale; }).slice(0, -1))];
|
||||
})();
|
||||
|
||||
// convert
|
||||
const fixLanguageTag = (tag: string) => {
|
||||
tag = typeof tag == 'string' ? tag : 'und';
|
||||
const tagLangLC = tag.match(/^(\w{2})-?(\w{2})$/);
|
||||
if(tagLangLC){
|
||||
const tagLang = `${tagLangLC[1]}-${tagLangLC[2].toUpperCase()}`;
|
||||
if(findLang(tagLang).cr_locale != 'und'){
|
||||
return findLang(tagLang).cr_locale;
|
||||
}
|
||||
else{
|
||||
return tagLang;
|
||||
}
|
||||
}
|
||||
else{
|
||||
return tag;
|
||||
}
|
||||
};
|
||||
|
||||
// find lang by cr_locale
|
||||
const findLang = (cr_locale: string) => {
|
||||
const lang = languages.find(l => { return l.cr_locale == cr_locale; });
|
||||
return lang ? lang : { cr_locale: 'und', locale: 'un', code: 'und', name: '', language: '' };
|
||||
};
|
||||
|
||||
const fixAndFindCrLC = (cr_locale: string) => {
|
||||
const str = fixLanguageTag(cr_locale);
|
||||
return findLang(str || '');
|
||||
};
|
||||
|
||||
// rss subs lang parser
|
||||
const parseRssSubtitlesString = (subs: string) => {
|
||||
const splitMap = subs.replace(/\s/g, '').split(',').map((s) => {
|
||||
return fixAndFindCrLC(s).locale;
|
||||
});
|
||||
const sort = sortTags(splitMap);
|
||||
return sort.join(', ');
|
||||
};
|
||||
|
||||
|
||||
// parse subtitles Array
|
||||
const parseSubtitlesArray = (tags: string[]) => {
|
||||
const sort = sortSubtitles(tags.map((t) => {
|
||||
return { locale: fixAndFindCrLC(t).locale };
|
||||
}));
|
||||
return sort.map((t) => { return t.locale; }).join(', ');
|
||||
};
|
||||
|
||||
// sort subtitles
|
||||
const sortSubtitles = <T extends {
|
||||
[key: string]: unknown
|
||||
} = Record<string, string>> (data: T[], sortkey?: keyof T) : T[] => {
|
||||
const idx: Record<string, number> = {};
|
||||
const key = sortkey || 'locale' as keyof T;
|
||||
const tags = [...new Set(Object.values(languages).map(e => e.locale))];
|
||||
for(const l of tags){
|
||||
idx[l] = Object.keys(idx).length + 1;
|
||||
}
|
||||
data.sort((a, b) => {
|
||||
const ia = idx[a[key] as string] ? idx[a[key] as string] : 50;
|
||||
const ib = idx[b[key] as string] ? idx[b[key] as string] : 50;
|
||||
return ia - ib;
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
const sortTags = (data: string[]) => {
|
||||
const retData = data.map(e => { return { locale: e }; });
|
||||
const sort = sortSubtitles(retData);
|
||||
return sort.map(e => e.locale as string);
|
||||
};
|
||||
|
||||
const subsFile = (fnOutput:string, subsIndex: string, langItem: LanguageItem) => {
|
||||
subsIndex = (parseInt(subsIndex) + 1).toString().padStart(2, '0');
|
||||
return `${fnOutput}.${subsIndex} ${langItem.code} ${langItem.language}.ass`;
|
||||
};
|
||||
|
||||
// construct dub langs const
|
||||
const dubLanguages = (() => {
|
||||
const dubDb: Record<string, string> = {};
|
||||
for(const lang of languages){
|
||||
if(!Object.keys(dubDb).includes(lang.name)){
|
||||
dubDb[lang.name] = lang.code;
|
||||
}
|
||||
}
|
||||
return dubDb;
|
||||
})();
|
||||
|
||||
// dub regex
|
||||
const dubRegExpStr =
|
||||
`\\((${Object.keys(dubLanguages).join('|')})(?: (Dub|VO))?\\)$`;
|
||||
const dubRegExp = new RegExp(dubRegExpStr);
|
||||
|
||||
// code to lang name
|
||||
const langCode2name = (code: string) => {
|
||||
const codeIdx = dubLanguageCodes.indexOf(code);
|
||||
return Object.keys(dubLanguages)[codeIdx];
|
||||
};
|
||||
|
||||
// locale to lang name
|
||||
const locale2language = (locale: string) => {
|
||||
const filteredLocale = languages.filter(l => {
|
||||
return l.locale == locale;
|
||||
});
|
||||
return filteredLocale[0];
|
||||
};
|
||||
|
||||
// output
|
||||
export {
|
||||
languages,
|
||||
dubLanguageCodes,
|
||||
dubLanguages,
|
||||
langCode2name,
|
||||
locale2language,
|
||||
dubRegExp,
|
||||
subtitleLanguagesFilter,
|
||||
searchLocales,
|
||||
fixLanguageTag,
|
||||
findLang,
|
||||
fixAndFindCrLC,
|
||||
parseRssSubtitlesString,
|
||||
parseSubtitlesArray,
|
||||
sortSubtitles,
|
||||
sortTags,
|
||||
subsFile,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,287 +1,287 @@
|
|||
import * as iso639 from 'iso-639';
|
||||
import { fontFamilies, fontMime } from './module.fontsData';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { LanguageItem } from './module.langsData';
|
||||
import { AvailableMuxer } from './module.args';
|
||||
|
||||
export type MergerInput = {
|
||||
path: string,
|
||||
lang: LanguageItem
|
||||
}
|
||||
|
||||
export type SubtitleInput = {
|
||||
language: LanguageItem,
|
||||
file: string,
|
||||
closedCaption?: boolean
|
||||
}
|
||||
|
||||
export type Font = keyof typeof fontFamilies;
|
||||
|
||||
export type ParsedFont = {
|
||||
name: string,
|
||||
path: string,
|
||||
mime: string,
|
||||
}
|
||||
|
||||
export type MergerOptions = {
|
||||
videoAndAudio: MergerInput[],
|
||||
onlyVid: MergerInput[],
|
||||
onlyAudio: MergerInput[],
|
||||
subtitles: SubtitleInput[],
|
||||
output: string,
|
||||
simul?: boolean,
|
||||
fonts?: ParsedFont[],
|
||||
skipSubMux?: boolean,
|
||||
coustomOptions?: string,
|
||||
}
|
||||
|
||||
class Merger {
|
||||
|
||||
constructor(private options: MergerOptions) {
|
||||
if (this.options.skipSubMux)
|
||||
this.options.subtitles = [];
|
||||
}
|
||||
|
||||
public FFmpeg() : string {
|
||||
const args = [];
|
||||
const metaData = [];
|
||||
|
||||
let index = 0;
|
||||
let audioIndex = 0;
|
||||
let hasVideo = false;
|
||||
|
||||
for (const vid of this.options.videoAndAudio) {
|
||||
args.push(`-i "${vid.path}"`);
|
||||
if (!hasVideo) {
|
||||
metaData.push(`-map ${index}:a -map ${index}:v`);
|
||||
metaData.push(`-metadata:s:a:${audioIndex} language=${vid.lang.code}`);
|
||||
metaData.push(`-metadata:s:v:${index} title="[Video Stream]"`);
|
||||
hasVideo = true;
|
||||
} else {
|
||||
metaData.push(`-map ${index}:a`);
|
||||
metaData.push(`-metadata:s:a:${audioIndex} language=${vid.lang.code}`);
|
||||
}
|
||||
audioIndex++;
|
||||
index++;
|
||||
}
|
||||
|
||||
for (const vid of this.options.onlyVid) {
|
||||
if (!hasVideo) {
|
||||
args.push(`-i "${vid.path}"`);
|
||||
metaData.push(`-map ${index} -map -${index}:a`);
|
||||
metaData.push(`-metadata:s:v:${index} title="[Video Stream]"`);
|
||||
hasVideo = true;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const aud of this.options.onlyAudio) {
|
||||
args.push(`-i "${aud.path}"`);
|
||||
metaData.push(`-map ${index}`);
|
||||
metaData.push(`-metadata:s:a:${audioIndex} language=${aud.lang.code}`);
|
||||
index++;
|
||||
audioIndex++;
|
||||
}
|
||||
|
||||
for (const index in this.options.subtitles) {
|
||||
const sub = this.options.subtitles[index];
|
||||
args.push(`-i "${sub.file}"`);
|
||||
}
|
||||
|
||||
if (this.options.output.split('.').pop() === 'mkv')
|
||||
if (this.options.fonts) {
|
||||
let fontIndex = 0;
|
||||
for (const font of this.options.fonts) {
|
||||
args.push(`-attach ${font.path} -metadata:s:t:${fontIndex} mimetype=${font.mime}`);
|
||||
fontIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
args.push(...metaData);
|
||||
args.push(...this.options.subtitles.map((_, subIndex) => `-map ${subIndex + index}`));
|
||||
args.push(
|
||||
'-c:v copy',
|
||||
'-c:a copy',
|
||||
this.options.output.split('.').pop()?.toLowerCase() === 'mp4' ? '-c:s mov_text' : '-c:s ass',
|
||||
...this.options.subtitles.map((sub, subindex) => `-metadata:s:s:${subindex} title="${
|
||||
(sub.language.language || sub.language.name) + `${sub.closedCaption === true ? ' CC' : ''}`
|
||||
}" -metadata:s:s:${subindex} language=${sub.language.code}`),
|
||||
this.options.coustomOptions ?? ''
|
||||
);
|
||||
args.push(`"${this.options.output}"`);
|
||||
return args.join(' ');
|
||||
}
|
||||
|
||||
public static getLanguageCode = (from: string, _default = 'eng'): string => {
|
||||
if (from === 'cmn') return 'chi';
|
||||
for (const lang in iso639.iso_639_2) {
|
||||
const langObj = iso639.iso_639_2[lang];
|
||||
if (Object.prototype.hasOwnProperty.call(langObj, '639-1') && langObj['639-1'] === from) {
|
||||
return langObj['639-2'] as string;
|
||||
}
|
||||
}
|
||||
return _default;
|
||||
};
|
||||
|
||||
public MkvMerge = () => {
|
||||
const args = [];
|
||||
|
||||
let hasVideo = false;
|
||||
|
||||
args.push(`-o "${this.options.output}"`);
|
||||
args.push(
|
||||
'--no-date',
|
||||
'--disable-track-statistics-tags',
|
||||
'--engage no_variable_data',
|
||||
);
|
||||
|
||||
for (const vid of this.options.onlyVid) {
|
||||
if (!hasVideo) {
|
||||
args.push(
|
||||
'--video-tracks 0',
|
||||
'--no-audio'
|
||||
);
|
||||
const trackName = (vid.lang.name + (this.options.simul ? ' [Simulcast]' : ' [Uncut]'));
|
||||
args.push('--track-name', `0:"${trackName}"`);
|
||||
args.push(`--language 0:${vid.lang.code}`);
|
||||
hasVideo = true;
|
||||
args.push(`"${vid.path}"`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const vid of this.options.videoAndAudio) {
|
||||
if (!hasVideo) {
|
||||
args.push(
|
||||
'--video-tracks 0',
|
||||
'--audio-tracks 1'
|
||||
);
|
||||
const trackName = (vid.lang.name + (this.options.simul ? ' [Simulcast]' : ' [Uncut]'));
|
||||
args.push('--track-name', `0:"${trackName}"`);
|
||||
args.push('--track-name', `1:"${trackName}"`);
|
||||
args.push(`--language 1:${vid.lang.code}`);
|
||||
hasVideo = true;
|
||||
} else {
|
||||
args.push(
|
||||
'--no-video',
|
||||
'--audio-tracks 1'
|
||||
);
|
||||
const trackName = (vid.lang.name + (this.options.simul ? ' [Simulcast]' : ' [Uncut]'));
|
||||
args.push('--track-name', `1:"${trackName}"`);
|
||||
args.push(`--language 1:${vid.lang.code}`);
|
||||
}
|
||||
args.push(`"${vid.path}"`);
|
||||
}
|
||||
|
||||
for (const aud of this.options.onlyAudio) {
|
||||
const trackName = aud.lang.name;
|
||||
args.push('--track-name', `0:"${trackName}"`);
|
||||
args.push(`--language 0:${aud.lang.code}`);
|
||||
args.push(
|
||||
'--no-video',
|
||||
'--audio-tracks 0'
|
||||
);
|
||||
args.push(`"${aud.path}"`);
|
||||
}
|
||||
|
||||
if (this.options.subtitles.length > 0) {
|
||||
for (const subObj of this.options.subtitles) {
|
||||
args.push('--track-name', `0:"${(subObj.language.language || subObj.language.name) + `${subObj.closedCaption === true ? ' CC' : ''}`}"`);
|
||||
args.push('--language', `0:"${subObj.language.code}"`);
|
||||
args.push(`"${subObj.file}"`);
|
||||
}
|
||||
} else {
|
||||
args.push(
|
||||
'--no-subtitles',
|
||||
);
|
||||
}
|
||||
if (this.options.fonts && this.options.fonts.length > 0) {
|
||||
for (const f of this.options.fonts) {
|
||||
console.log(f.path);
|
||||
args.push('--attachment-name', f.name);
|
||||
args.push('--attachment-mime-type', f.mime);
|
||||
args.push('--attach-file', `"${f.path}"`);
|
||||
}
|
||||
} else {
|
||||
args.push(
|
||||
'--no-attachments'
|
||||
);
|
||||
}
|
||||
|
||||
return args.join(' ');
|
||||
};
|
||||
|
||||
public static checkMerger(bin: {
|
||||
mkvmerge?: string,
|
||||
ffmpeg?: string,
|
||||
}, useMP4format: boolean, force: AvailableMuxer|undefined) : {
|
||||
MKVmerge?: string,
|
||||
FFmpeg?: string
|
||||
} {
|
||||
if (force && bin[force]) {
|
||||
return {
|
||||
FFmpeg: force === 'ffmpeg' ? bin.ffmpeg : undefined,
|
||||
MKVmerge: force === 'mkvmerge' ? bin.mkvmerge : undefined
|
||||
};
|
||||
}
|
||||
if (useMP4format && bin.ffmpeg) {
|
||||
return {
|
||||
FFmpeg: bin.ffmpeg
|
||||
};
|
||||
} else if (!useMP4format && (bin.mkvmerge || bin.ffmpeg)) {
|
||||
return {
|
||||
MKVmerge: bin.mkvmerge,
|
||||
FFmpeg: bin.ffmpeg
|
||||
};
|
||||
} else if (useMP4format) {
|
||||
console.log('[WARN] FFmpeg not found, skip muxing...');
|
||||
} else if (!bin.mkvmerge) {
|
||||
console.log('[WARN] MKVMerge not found, skip muxing...');
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
public static makeFontsList (fontsDir: string, subs: {
|
||||
language: LanguageItem,
|
||||
fonts: Font[]
|
||||
}[]) : ParsedFont[] {
|
||||
let fontsNameList: Font[] = []; const fontsList = [], subsList = []; let isNstr = true;
|
||||
for(const s of subs){
|
||||
fontsNameList.push(...s.fonts);
|
||||
subsList.push(s.language.locale);
|
||||
}
|
||||
fontsNameList = [...new Set(fontsNameList)];
|
||||
if(subsList.length > 0){
|
||||
console.log('\n[INFO] Subtitles: %s (Total: %s)', subsList.join(', '), subsList.length);
|
||||
isNstr = false;
|
||||
}
|
||||
if(fontsNameList.length > 0){
|
||||
console.log((isNstr ? '\n' : '') + '[INFO] Required fonts: %s (Total: %s)', fontsNameList.join(', '), fontsNameList.length);
|
||||
}
|
||||
for(const f of fontsNameList){
|
||||
const fontFiles = fontFamilies[f];
|
||||
if(fontFiles){
|
||||
for (const fontFile of fontFiles) {
|
||||
const fontPath = path.join(fontsDir, fontFile);
|
||||
const mime = fontMime(fontFile);
|
||||
if(fs.existsSync(fontPath) && fs.statSync(fontPath).size != 0){
|
||||
fontsList.push({
|
||||
name: fontFile,
|
||||
path: fontPath,
|
||||
mime: mime,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fontsList;
|
||||
}
|
||||
|
||||
public cleanUp() {
|
||||
this.options.onlyAudio.concat(this.options.onlyVid).concat(this.options.videoAndAudio).forEach(a => fs.unlinkSync(a.path));
|
||||
this.options.subtitles.forEach(a => fs.unlinkSync(a.file));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
import * as iso639 from 'iso-639';
|
||||
import { fontFamilies, fontMime } from './module.fontsData';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { LanguageItem } from './module.langsData';
|
||||
import { AvailableMuxer } from './module.args';
|
||||
|
||||
export type MergerInput = {
|
||||
path: string,
|
||||
lang: LanguageItem
|
||||
}
|
||||
|
||||
export type SubtitleInput = {
|
||||
language: LanguageItem,
|
||||
file: string,
|
||||
closedCaption?: boolean
|
||||
}
|
||||
|
||||
export type Font = keyof typeof fontFamilies;
|
||||
|
||||
export type ParsedFont = {
|
||||
name: string,
|
||||
path: string,
|
||||
mime: string,
|
||||
}
|
||||
|
||||
export type MergerOptions = {
|
||||
videoAndAudio: MergerInput[],
|
||||
onlyVid: MergerInput[],
|
||||
onlyAudio: MergerInput[],
|
||||
subtitles: SubtitleInput[],
|
||||
output: string,
|
||||
simul?: boolean,
|
||||
fonts?: ParsedFont[],
|
||||
skipSubMux?: boolean,
|
||||
coustomOptions?: string,
|
||||
}
|
||||
|
||||
class Merger {
|
||||
|
||||
constructor(private options: MergerOptions) {
|
||||
if (this.options.skipSubMux)
|
||||
this.options.subtitles = [];
|
||||
}
|
||||
|
||||
public FFmpeg() : string {
|
||||
const args = [];
|
||||
const metaData = [];
|
||||
|
||||
let index = 0;
|
||||
let audioIndex = 0;
|
||||
let hasVideo = false;
|
||||
|
||||
for (const vid of this.options.videoAndAudio) {
|
||||
args.push(`-i "${vid.path}"`);
|
||||
if (!hasVideo) {
|
||||
metaData.push(`-map ${index}:a -map ${index}:v`);
|
||||
metaData.push(`-metadata:s:a:${audioIndex} language=${vid.lang.code}`);
|
||||
metaData.push(`-metadata:s:v:${index} title="[Video Stream]"`);
|
||||
hasVideo = true;
|
||||
} else {
|
||||
metaData.push(`-map ${index}:a`);
|
||||
metaData.push(`-metadata:s:a:${audioIndex} language=${vid.lang.code}`);
|
||||
}
|
||||
audioIndex++;
|
||||
index++;
|
||||
}
|
||||
|
||||
for (const vid of this.options.onlyVid) {
|
||||
if (!hasVideo) {
|
||||
args.push(`-i "${vid.path}"`);
|
||||
metaData.push(`-map ${index} -map -${index}:a`);
|
||||
metaData.push(`-metadata:s:v:${index} title="[Video Stream]"`);
|
||||
hasVideo = true;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const aud of this.options.onlyAudio) {
|
||||
args.push(`-i "${aud.path}"`);
|
||||
metaData.push(`-map ${index}`);
|
||||
metaData.push(`-metadata:s:a:${audioIndex} language=${aud.lang.code}`);
|
||||
index++;
|
||||
audioIndex++;
|
||||
}
|
||||
|
||||
for (const index in this.options.subtitles) {
|
||||
const sub = this.options.subtitles[index];
|
||||
args.push(`-i "${sub.file}"`);
|
||||
}
|
||||
|
||||
if (this.options.output.split('.').pop() === 'mkv')
|
||||
if (this.options.fonts) {
|
||||
let fontIndex = 0;
|
||||
for (const font of this.options.fonts) {
|
||||
args.push(`-attach ${font.path} -metadata:s:t:${fontIndex} mimetype=${font.mime}`);
|
||||
fontIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
args.push(...metaData);
|
||||
args.push(...this.options.subtitles.map((_, subIndex) => `-map ${subIndex + index}`));
|
||||
args.push(
|
||||
'-c:v copy',
|
||||
'-c:a copy',
|
||||
this.options.output.split('.').pop()?.toLowerCase() === 'mp4' ? '-c:s mov_text' : '-c:s ass',
|
||||
...this.options.subtitles.map((sub, subindex) => `-metadata:s:s:${subindex} title="${
|
||||
(sub.language.language || sub.language.name) + `${sub.closedCaption === true ? ' CC' : ''}`
|
||||
}" -metadata:s:s:${subindex} language=${sub.language.code}`),
|
||||
this.options.coustomOptions ?? ''
|
||||
);
|
||||
args.push(`"${this.options.output}"`);
|
||||
return args.join(' ');
|
||||
}
|
||||
|
||||
public static getLanguageCode = (from: string, _default = 'eng'): string => {
|
||||
if (from === 'cmn') return 'chi';
|
||||
for (const lang in iso639.iso_639_2) {
|
||||
const langObj = iso639.iso_639_2[lang];
|
||||
if (Object.prototype.hasOwnProperty.call(langObj, '639-1') && langObj['639-1'] === from) {
|
||||
return langObj['639-2'] as string;
|
||||
}
|
||||
}
|
||||
return _default;
|
||||
};
|
||||
|
||||
public MkvMerge = () => {
|
||||
const args = [];
|
||||
|
||||
let hasVideo = false;
|
||||
|
||||
args.push(`-o "${this.options.output}"`);
|
||||
args.push(
|
||||
'--no-date',
|
||||
'--disable-track-statistics-tags',
|
||||
'--engage no_variable_data',
|
||||
);
|
||||
|
||||
for (const vid of this.options.onlyVid) {
|
||||
if (!hasVideo) {
|
||||
args.push(
|
||||
'--video-tracks 0',
|
||||
'--no-audio'
|
||||
);
|
||||
const trackName = (vid.lang.name + (this.options.simul ? ' [Simulcast]' : ' [Uncut]'));
|
||||
args.push('--track-name', `0:"${trackName}"`);
|
||||
args.push(`--language 0:${vid.lang.code}`);
|
||||
hasVideo = true;
|
||||
args.push(`"${vid.path}"`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const vid of this.options.videoAndAudio) {
|
||||
if (!hasVideo) {
|
||||
args.push(
|
||||
'--video-tracks 0',
|
||||
'--audio-tracks 1'
|
||||
);
|
||||
const trackName = (vid.lang.name + (this.options.simul ? ' [Simulcast]' : ' [Uncut]'));
|
||||
args.push('--track-name', `0:"${trackName}"`);
|
||||
args.push('--track-name', `1:"${trackName}"`);
|
||||
args.push(`--language 1:${vid.lang.code}`);
|
||||
hasVideo = true;
|
||||
} else {
|
||||
args.push(
|
||||
'--no-video',
|
||||
'--audio-tracks 1'
|
||||
);
|
||||
const trackName = (vid.lang.name + (this.options.simul ? ' [Simulcast]' : ' [Uncut]'));
|
||||
args.push('--track-name', `1:"${trackName}"`);
|
||||
args.push(`--language 1:${vid.lang.code}`);
|
||||
}
|
||||
args.push(`"${vid.path}"`);
|
||||
}
|
||||
|
||||
for (const aud of this.options.onlyAudio) {
|
||||
const trackName = aud.lang.name;
|
||||
args.push('--track-name', `0:"${trackName}"`);
|
||||
args.push(`--language 0:${aud.lang.code}`);
|
||||
args.push(
|
||||
'--no-video',
|
||||
'--audio-tracks 0'
|
||||
);
|
||||
args.push(`"${aud.path}"`);
|
||||
}
|
||||
|
||||
if (this.options.subtitles.length > 0) {
|
||||
for (const subObj of this.options.subtitles) {
|
||||
args.push('--track-name', `0:"${(subObj.language.language || subObj.language.name) + `${subObj.closedCaption === true ? ' CC' : ''}`}"`);
|
||||
args.push('--language', `0:"${subObj.language.code}"`);
|
||||
args.push(`"${subObj.file}"`);
|
||||
}
|
||||
} else {
|
||||
args.push(
|
||||
'--no-subtitles',
|
||||
);
|
||||
}
|
||||
if (this.options.fonts && this.options.fonts.length > 0) {
|
||||
for (const f of this.options.fonts) {
|
||||
console.log(f.path);
|
||||
args.push('--attachment-name', f.name);
|
||||
args.push('--attachment-mime-type', f.mime);
|
||||
args.push('--attach-file', `"${f.path}"`);
|
||||
}
|
||||
} else {
|
||||
args.push(
|
||||
'--no-attachments'
|
||||
);
|
||||
}
|
||||
|
||||
return args.join(' ');
|
||||
};
|
||||
|
||||
public static checkMerger(bin: {
|
||||
mkvmerge?: string,
|
||||
ffmpeg?: string,
|
||||
}, useMP4format: boolean, force: AvailableMuxer|undefined) : {
|
||||
MKVmerge?: string,
|
||||
FFmpeg?: string
|
||||
} {
|
||||
if (force && bin[force]) {
|
||||
return {
|
||||
FFmpeg: force === 'ffmpeg' ? bin.ffmpeg : undefined,
|
||||
MKVmerge: force === 'mkvmerge' ? bin.mkvmerge : undefined
|
||||
};
|
||||
}
|
||||
if (useMP4format && bin.ffmpeg) {
|
||||
return {
|
||||
FFmpeg: bin.ffmpeg
|
||||
};
|
||||
} else if (!useMP4format && (bin.mkvmerge || bin.ffmpeg)) {
|
||||
return {
|
||||
MKVmerge: bin.mkvmerge,
|
||||
FFmpeg: bin.ffmpeg
|
||||
};
|
||||
} else if (useMP4format) {
|
||||
console.log('[WARN] FFmpeg not found, skip muxing...');
|
||||
} else if (!bin.mkvmerge) {
|
||||
console.log('[WARN] MKVMerge not found, skip muxing...');
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
public static makeFontsList (fontsDir: string, subs: {
|
||||
language: LanguageItem,
|
||||
fonts: Font[]
|
||||
}[]) : ParsedFont[] {
|
||||
let fontsNameList: Font[] = []; const fontsList = [], subsList = []; let isNstr = true;
|
||||
for(const s of subs){
|
||||
fontsNameList.push(...s.fonts);
|
||||
subsList.push(s.language.locale);
|
||||
}
|
||||
fontsNameList = [...new Set(fontsNameList)];
|
||||
if(subsList.length > 0){
|
||||
console.log('\n[INFO] Subtitles: %s (Total: %s)', subsList.join(', '), subsList.length);
|
||||
isNstr = false;
|
||||
}
|
||||
if(fontsNameList.length > 0){
|
||||
console.log((isNstr ? '\n' : '') + '[INFO] Required fonts: %s (Total: %s)', fontsNameList.join(', '), fontsNameList.length);
|
||||
}
|
||||
for(const f of fontsNameList){
|
||||
const fontFiles = fontFamilies[f];
|
||||
if(fontFiles){
|
||||
for (const fontFile of fontFiles) {
|
||||
const fontPath = path.join(fontsDir, fontFile);
|
||||
const mime = fontMime(fontFile);
|
||||
if(fs.existsSync(fontPath) && fs.statSync(fontPath).size != 0){
|
||||
fontsList.push({
|
||||
name: fontFile,
|
||||
path: fontPath,
|
||||
mime: mime,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fontsList;
|
||||
}
|
||||
|
||||
public cleanUp() {
|
||||
this.options.onlyAudio.concat(this.options.onlyVid).concat(this.options.videoAndAudio).forEach(a => fs.unlinkSync(a.path));
|
||||
this.options.subtitles.forEach(a => fs.unlinkSync(a.file));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Merger;
|
||||
|
|
@ -1,105 +1,105 @@
|
|||
const parseSelect = (selectString: string, but = false) : {
|
||||
isSelected: (val: string|string[]) => boolean,
|
||||
values: string[]
|
||||
} => {
|
||||
if (!selectString)
|
||||
return {
|
||||
values: [],
|
||||
isSelected: () => but
|
||||
};
|
||||
const parts = selectString.split(',');
|
||||
const select: string[] = [];
|
||||
|
||||
parts.forEach(part => {
|
||||
if (part.includes('-')) {
|
||||
const splits = part.split('-');
|
||||
if (splits.length !== 2) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
const firstPart = splits[0];
|
||||
const match = firstPart.match(/[A-Za-z]+/);
|
||||
if (match && match.length > 0) {
|
||||
if (match.index && match.index !== 0) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
const letters = firstPart.substring(0, match[0].length);
|
||||
const number = parseInt(firstPart.substring(match[0].length));
|
||||
const b = parseInt(splits[1]);
|
||||
if (isNaN(number) || isNaN(b)) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
for (let i = number; i <= b; i++) {
|
||||
select.push(`${letters}${i}`);
|
||||
}
|
||||
|
||||
} else {
|
||||
const a = parseInt(firstPart);
|
||||
const b = parseInt(splits[1]);
|
||||
if (isNaN(a) || isNaN(b)) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
for (let i = a; i <= b; i++) {
|
||||
select.push(`${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (part.match(/[0-9A-Z]{9}/)) {
|
||||
select.push(part);
|
||||
return;
|
||||
}
|
||||
const match = part.match(/[A-Za-z]+/);
|
||||
if (match && match.length > 0) {
|
||||
if (match.index && match.index !== 0) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
const letters = part.substring(0, match[0].length);
|
||||
const number = parseInt(part.substring(match[0].length));
|
||||
if (isNaN(number)) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
select.push(`${letters}${number}`);
|
||||
} else {
|
||||
select.push(`${parseInt(part)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
values: select,
|
||||
isSelected: (st) => {
|
||||
if (typeof st === 'string')
|
||||
st = [st];
|
||||
return st.some(st => {
|
||||
const match = st.match(/[A-Za-z]+/);
|
||||
if (st.match(/[0-9A-Z]{9}/)) {
|
||||
const included = select.includes(st);
|
||||
return but ? !included : included;
|
||||
} else if (match && match.length > 0) {
|
||||
if (match.index && match.index !== 0) {
|
||||
return false;
|
||||
}
|
||||
const letter = st.substring(0, match[0].length);
|
||||
const number = parseInt(st.substring(match[0].length));
|
||||
if (isNaN(number)) {
|
||||
return false;
|
||||
}
|
||||
const included = select.includes(`${letter}${number}`);
|
||||
return but ? !included : included;
|
||||
} else {
|
||||
const included = select.includes(`${parseInt(st)}`);
|
||||
return but ? !included : included;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const parseSelect = (selectString: string, but = false) : {
|
||||
isSelected: (val: string|string[]) => boolean,
|
||||
values: string[]
|
||||
} => {
|
||||
if (!selectString)
|
||||
return {
|
||||
values: [],
|
||||
isSelected: () => but
|
||||
};
|
||||
const parts = selectString.split(',');
|
||||
const select: string[] = [];
|
||||
|
||||
parts.forEach(part => {
|
||||
if (part.includes('-')) {
|
||||
const splits = part.split('-');
|
||||
if (splits.length !== 2) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
const firstPart = splits[0];
|
||||
const match = firstPart.match(/[A-Za-z]+/);
|
||||
if (match && match.length > 0) {
|
||||
if (match.index && match.index !== 0) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
const letters = firstPart.substring(0, match[0].length);
|
||||
const number = parseInt(firstPart.substring(match[0].length));
|
||||
const b = parseInt(splits[1]);
|
||||
if (isNaN(number) || isNaN(b)) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
for (let i = number; i <= b; i++) {
|
||||
select.push(`${letters}${i}`);
|
||||
}
|
||||
|
||||
} else {
|
||||
const a = parseInt(firstPart);
|
||||
const b = parseInt(splits[1]);
|
||||
if (isNaN(a) || isNaN(b)) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
for (let i = a; i <= b; i++) {
|
||||
select.push(`${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (part.match(/[0-9A-Z]{9}/)) {
|
||||
select.push(part);
|
||||
return;
|
||||
}
|
||||
const match = part.match(/[A-Za-z]+/);
|
||||
if (match && match.length > 0) {
|
||||
if (match.index && match.index !== 0) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
const letters = part.substring(0, match[0].length);
|
||||
const number = parseInt(part.substring(match[0].length));
|
||||
if (isNaN(number)) {
|
||||
console.log(`[WARN] Unable to parse input "${part}"`);
|
||||
return;
|
||||
}
|
||||
select.push(`${letters}${number}`);
|
||||
} else {
|
||||
select.push(`${parseInt(part)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
values: select,
|
||||
isSelected: (st) => {
|
||||
if (typeof st === 'string')
|
||||
st = [st];
|
||||
return st.some(st => {
|
||||
const match = st.match(/[A-Za-z]+/);
|
||||
if (st.match(/[0-9A-Z]{9}/)) {
|
||||
const included = select.includes(st);
|
||||
return but ? !included : included;
|
||||
} else if (match && match.length > 0) {
|
||||
if (match.index && match.index !== 0) {
|
||||
return false;
|
||||
}
|
||||
const letter = st.substring(0, match[0].length);
|
||||
const number = parseInt(st.substring(match[0].length));
|
||||
if (isNaN(number)) {
|
||||
return false;
|
||||
}
|
||||
const included = select.includes(`${letter}${number}`);
|
||||
return but ? !included : included;
|
||||
} else {
|
||||
const included = select.includes(`${parseInt(st)}`);
|
||||
return but ? !included : included;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default parseSelect;
|
||||
|
|
@ -1,234 +1,234 @@
|
|||
import shlp from 'sei-helper';
|
||||
import got, { Headers, Method, Options, ReadError, Response } from 'got';
|
||||
import cookieFile from './module.cookieFile';
|
||||
import * as yamlCfg from './module.cfg-loader';
|
||||
import curlReq from './module.curl-req';
|
||||
|
||||
export type Params = {
|
||||
method?: Method,
|
||||
headers?: Headers,
|
||||
body?: string | Buffer,
|
||||
binary?: boolean,
|
||||
followRedirect?: boolean
|
||||
}
|
||||
|
||||
// set usable cookies
|
||||
const usefulCookies = {
|
||||
auth: [
|
||||
'etp_rt',
|
||||
'c_visitor',
|
||||
],
|
||||
sess: [
|
||||
'session_id',
|
||||
],
|
||||
};
|
||||
|
||||
// req
|
||||
class Req {
|
||||
private sessCfg = yamlCfg.sessCfgFile;
|
||||
private session: Record<string, {
|
||||
value: string;
|
||||
expires: Date;
|
||||
path: string;
|
||||
domain: string;
|
||||
secure: boolean;
|
||||
'Max-Age'?: string
|
||||
}> = {};
|
||||
private cfgDir = yamlCfg.cfgDir
|
||||
private curl: boolean|string = false;
|
||||
|
||||
constructor(private domain: Record<string, unknown>, private debug: boolean, private nosess = false) {}
|
||||
async getData<T = string> (durl: string, params?: Params) {
|
||||
params = params || {};
|
||||
// options
|
||||
const options: Options & {
|
||||
minVersion?: string,
|
||||
maxVersion?: string
|
||||
curlDebug?: boolean
|
||||
} = {
|
||||
method: params.method ? params.method : 'GET',
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:90.0) Gecko/20100101 Firefox/90.0',
|
||||
},
|
||||
};
|
||||
// additional params
|
||||
if(params.headers){
|
||||
options.headers = {...options.headers, ...params.headers};
|
||||
}
|
||||
if(options.method == 'POST'){
|
||||
(options.headers as Headers)['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
if(params.body){
|
||||
options.body = params.body;
|
||||
}
|
||||
if(params.binary == true){
|
||||
options.responseType = 'buffer';
|
||||
}
|
||||
if(typeof params.followRedirect == 'boolean'){
|
||||
options.followRedirect = params.followRedirect;
|
||||
}
|
||||
// if auth
|
||||
const loc = new URL(durl);
|
||||
// avoid cloudflare protection
|
||||
// debug
|
||||
options.hooks = {
|
||||
beforeRequest: [
|
||||
(options) => {
|
||||
if(this.debug){
|
||||
console.log('[DEBUG] GOT OPTIONS:');
|
||||
console.log(options);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if(this.debug){
|
||||
options.curlDebug = true;
|
||||
}
|
||||
// try do request
|
||||
try {
|
||||
const res = await got(durl.toString(), options) as unknown as Response<T>;
|
||||
return {
|
||||
ok: true,
|
||||
res
|
||||
};
|
||||
}
|
||||
catch(_error){
|
||||
const error = _error as {
|
||||
name: string
|
||||
} & ReadError & {
|
||||
res: Response<unknown>
|
||||
};
|
||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||
}
|
||||
else{
|
||||
console.log(`[ERROR] ${error.name}: ${error.code || error.message}`);
|
||||
}
|
||||
if(error.response && !error.res){
|
||||
error.res = error.response;
|
||||
const docTitle = (error.res.body as string).match(/<title>(.*)<\/title>/);
|
||||
if(error.res.body && docTitle){
|
||||
console.log('[ERROR]', docTitle[1]);
|
||||
}
|
||||
}
|
||||
if(error.res && error.res.body && error.response.statusCode
|
||||
&& error.response.statusCode != 404 && error.response.statusCode != 403){
|
||||
console.log('[ERROR] Body:', error.res.body);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
||||
setNewCookie(setCookie: Record<string, string>, isAuth: boolean, fileData?: string){
|
||||
const cookieUpdated = []; let lastExp = 0;
|
||||
console.trace('Type of setCookie:', typeof setCookie, setCookie);
|
||||
const parsedCookie = fileData ? cookieFile(fileData) : shlp.cookie.parse(setCookie);
|
||||
for(const cookieName of Object.keys(parsedCookie)){
|
||||
if(parsedCookie[cookieName] && parsedCookie[cookieName].value && parsedCookie[cookieName].value == 'deleted'){
|
||||
delete parsedCookie[cookieName];
|
||||
}
|
||||
}
|
||||
for(const uCookie of usefulCookies.auth){
|
||||
const cookieForceExp = 60*60*24*7;
|
||||
const cookieExpCur = this.session[uCookie] ? this.session[uCookie] : { expires: 0 };
|
||||
const cookieExp = new Date(cookieExpCur.expires).getTime() - cookieForceExp;
|
||||
if(cookieExp > lastExp){
|
||||
lastExp = cookieExp;
|
||||
}
|
||||
}
|
||||
for(const uCookie of usefulCookies.auth){
|
||||
if(!parsedCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(isAuth || parsedCookie[uCookie] && Date.now() > lastExp){
|
||||
this.session[uCookie] = parsedCookie[uCookie];
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
for(const uCookie of usefulCookies.sess){
|
||||
if(!parsedCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(
|
||||
isAuth
|
||||
|| this.nosess && parsedCookie[uCookie]
|
||||
|| parsedCookie[uCookie] && !this.checkSessId(this.session[uCookie])
|
||||
){
|
||||
const sessionExp = 60*60;
|
||||
this.session[uCookie] = parsedCookie[uCookie];
|
||||
this.session[uCookie].expires = new Date(Date.now() + sessionExp*1000);
|
||||
this.session[uCookie]['Max-Age'] = sessionExp.toString();
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
if(cookieUpdated.length > 0){
|
||||
if(this.debug){
|
||||
console.log('[SAVING FILE]',`${this.sessCfg}.yml`);
|
||||
}
|
||||
yamlCfg.saveCRSession(this.session);
|
||||
console.log(`[INFO] Cookies were updated! (${cookieUpdated.join(', ')})\n`);
|
||||
}
|
||||
}
|
||||
checkCookieVal(chcookie: Record<string, string>){
|
||||
return chcookie
|
||||
&& chcookie.toString() == '[object Object]'
|
||||
&& typeof chcookie.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
checkSessId(session_id: Record<string, unknown>){
|
||||
if(session_id && typeof session_id.expires == 'string'){
|
||||
session_id.expires = new Date(session_id.expires);
|
||||
}
|
||||
return session_id
|
||||
&& session_id.toString() == '[object Object]'
|
||||
&& typeof session_id.expires == 'object'
|
||||
&& Date.now() < new Date(session_id.expires as any).getTime()
|
||||
&& typeof session_id.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
uuidv4(){
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildProxy(proxyBaseUrl: string, proxyAuth: string){
|
||||
if(!proxyBaseUrl.match(/^(https?|socks4|socks5):/)){
|
||||
proxyBaseUrl = 'http://' + proxyBaseUrl;
|
||||
}
|
||||
|
||||
const proxyCfg = new URL(proxyBaseUrl);
|
||||
let proxyStr = `${proxyCfg.protocol}//`;
|
||||
|
||||
if(typeof proxyCfg.hostname != 'string' || proxyCfg.hostname == ''){
|
||||
throw new Error('[ERROR] Hostname and port required for proxy!');
|
||||
}
|
||||
|
||||
if(proxyAuth && typeof proxyAuth == 'string' && proxyAuth.match(':')){
|
||||
proxyCfg.username = proxyAuth.split(':')[0];
|
||||
proxyCfg.password = proxyAuth.split(':')[1];
|
||||
proxyStr += `${proxyCfg.username}:${proxyCfg.password}@`;
|
||||
}
|
||||
|
||||
proxyStr += proxyCfg.hostname;
|
||||
|
||||
if(!proxyCfg.port && proxyCfg.protocol == 'http:'){
|
||||
proxyStr += ':80';
|
||||
}
|
||||
else if(!proxyCfg.port && proxyCfg.protocol == 'https:'){
|
||||
proxyStr += ':443';
|
||||
}
|
||||
|
||||
return proxyStr;
|
||||
}
|
||||
|
||||
export {
|
||||
buildProxy,
|
||||
usefulCookies,
|
||||
Req,
|
||||
};
|
||||
import shlp from 'sei-helper';
|
||||
import got, { Headers, Method, Options, ReadError, Response } from 'got';
|
||||
import cookieFile from './module.cookieFile';
|
||||
import * as yamlCfg from './module.cfg-loader';
|
||||
import curlReq from './module.curl-req';
|
||||
|
||||
export type Params = {
|
||||
method?: Method,
|
||||
headers?: Headers,
|
||||
body?: string | Buffer,
|
||||
binary?: boolean,
|
||||
followRedirect?: boolean
|
||||
}
|
||||
|
||||
// set usable cookies
|
||||
const usefulCookies = {
|
||||
auth: [
|
||||
'etp_rt',
|
||||
'c_visitor',
|
||||
],
|
||||
sess: [
|
||||
'session_id',
|
||||
],
|
||||
};
|
||||
|
||||
// req
|
||||
class Req {
|
||||
private sessCfg = yamlCfg.sessCfgFile;
|
||||
private session: Record<string, {
|
||||
value: string;
|
||||
expires: Date;
|
||||
path: string;
|
||||
domain: string;
|
||||
secure: boolean;
|
||||
'Max-Age'?: string
|
||||
}> = {};
|
||||
private cfgDir = yamlCfg.cfgDir;
|
||||
private curl: boolean|string = false;
|
||||
|
||||
constructor(private domain: Record<string, unknown>, private debug: boolean, private nosess = false) {}
|
||||
async getData<T = string> (durl: string, params?: Params) {
|
||||
params = params || {};
|
||||
// options
|
||||
const options: Options & {
|
||||
minVersion?: string,
|
||||
maxVersion?: string
|
||||
curlDebug?: boolean
|
||||
} = {
|
||||
method: params.method ? params.method : 'GET',
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:90.0) Gecko/20100101 Firefox/90.0',
|
||||
},
|
||||
};
|
||||
// additional params
|
||||
if(params.headers){
|
||||
options.headers = {...options.headers, ...params.headers};
|
||||
}
|
||||
if(options.method == 'POST'){
|
||||
(options.headers as Headers)['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
if(params.body){
|
||||
options.body = params.body;
|
||||
}
|
||||
if(params.binary == true){
|
||||
options.responseType = 'buffer';
|
||||
}
|
||||
if(typeof params.followRedirect == 'boolean'){
|
||||
options.followRedirect = params.followRedirect;
|
||||
}
|
||||
// if auth
|
||||
const loc = new URL(durl);
|
||||
// avoid cloudflare protection
|
||||
// debug
|
||||
options.hooks = {
|
||||
beforeRequest: [
|
||||
(options) => {
|
||||
if(this.debug){
|
||||
console.log('[DEBUG] GOT OPTIONS:');
|
||||
console.log(options);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if(this.debug){
|
||||
options.curlDebug = true;
|
||||
}
|
||||
// try do request
|
||||
try {
|
||||
const res = await got(durl.toString(), options) as unknown as Response<T>;
|
||||
return {
|
||||
ok: true,
|
||||
res
|
||||
};
|
||||
}
|
||||
catch(_error){
|
||||
const error = _error as {
|
||||
name: string
|
||||
} & ReadError & {
|
||||
res: Response<unknown>
|
||||
};
|
||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||
}
|
||||
else{
|
||||
console.log(`[ERROR] ${error.name}: ${error.code || error.message}`);
|
||||
}
|
||||
if(error.response && !error.res){
|
||||
error.res = error.response;
|
||||
const docTitle = (error.res.body as string).match(/<title>(.*)<\/title>/);
|
||||
if(error.res.body && docTitle){
|
||||
console.log('[ERROR]', docTitle[1]);
|
||||
}
|
||||
}
|
||||
if(error.res && error.res.body && error.response.statusCode
|
||||
&& error.response.statusCode != 404 && error.response.statusCode != 403){
|
||||
console.log('[ERROR] Body:', error.res.body);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
||||
setNewCookie(setCookie: Record<string, string>, isAuth: boolean, fileData?: string){
|
||||
const cookieUpdated = []; let lastExp = 0;
|
||||
console.trace('Type of setCookie:', typeof setCookie, setCookie);
|
||||
const parsedCookie = fileData ? cookieFile(fileData) : shlp.cookie.parse(setCookie);
|
||||
for(const cookieName of Object.keys(parsedCookie)){
|
||||
if(parsedCookie[cookieName] && parsedCookie[cookieName].value && parsedCookie[cookieName].value == 'deleted'){
|
||||
delete parsedCookie[cookieName];
|
||||
}
|
||||
}
|
||||
for(const uCookie of usefulCookies.auth){
|
||||
const cookieForceExp = 60*60*24*7;
|
||||
const cookieExpCur = this.session[uCookie] ? this.session[uCookie] : { expires: 0 };
|
||||
const cookieExp = new Date(cookieExpCur.expires).getTime() - cookieForceExp;
|
||||
if(cookieExp > lastExp){
|
||||
lastExp = cookieExp;
|
||||
}
|
||||
}
|
||||
for(const uCookie of usefulCookies.auth){
|
||||
if(!parsedCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(isAuth || parsedCookie[uCookie] && Date.now() > lastExp){
|
||||
this.session[uCookie] = parsedCookie[uCookie];
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
for(const uCookie of usefulCookies.sess){
|
||||
if(!parsedCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(
|
||||
isAuth
|
||||
|| this.nosess && parsedCookie[uCookie]
|
||||
|| parsedCookie[uCookie] && !this.checkSessId(this.session[uCookie])
|
||||
){
|
||||
const sessionExp = 60*60;
|
||||
this.session[uCookie] = parsedCookie[uCookie];
|
||||
this.session[uCookie].expires = new Date(Date.now() + sessionExp*1000);
|
||||
this.session[uCookie]['Max-Age'] = sessionExp.toString();
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
if(cookieUpdated.length > 0){
|
||||
if(this.debug){
|
||||
console.log('[SAVING FILE]',`${this.sessCfg}.yml`);
|
||||
}
|
||||
yamlCfg.saveCRSession(this.session);
|
||||
console.log(`[INFO] Cookies were updated! (${cookieUpdated.join(', ')})\n`);
|
||||
}
|
||||
}
|
||||
checkCookieVal(chcookie: Record<string, string>){
|
||||
return chcookie
|
||||
&& chcookie.toString() == '[object Object]'
|
||||
&& typeof chcookie.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
checkSessId(session_id: Record<string, unknown>){
|
||||
if(session_id && typeof session_id.expires == 'string'){
|
||||
session_id.expires = new Date(session_id.expires);
|
||||
}
|
||||
return session_id
|
||||
&& session_id.toString() == '[object Object]'
|
||||
&& typeof session_id.expires == 'object'
|
||||
&& Date.now() < new Date(session_id.expires as any).getTime()
|
||||
&& typeof session_id.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
uuidv4(){
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildProxy(proxyBaseUrl: string, proxyAuth: string){
|
||||
if(!proxyBaseUrl.match(/^(https?|socks4|socks5):/)){
|
||||
proxyBaseUrl = 'http://' + proxyBaseUrl;
|
||||
}
|
||||
|
||||
const proxyCfg = new URL(proxyBaseUrl);
|
||||
let proxyStr = `${proxyCfg.protocol}//`;
|
||||
|
||||
if(typeof proxyCfg.hostname != 'string' || proxyCfg.hostname == ''){
|
||||
throw new Error('[ERROR] Hostname and port required for proxy!');
|
||||
}
|
||||
|
||||
if(proxyAuth && typeof proxyAuth == 'string' && proxyAuth.match(':')){
|
||||
proxyCfg.username = proxyAuth.split(':')[0];
|
||||
proxyCfg.password = proxyAuth.split(':')[1];
|
||||
proxyStr += `${proxyCfg.username}:${proxyCfg.password}@`;
|
||||
}
|
||||
|
||||
proxyStr += proxyCfg.hostname;
|
||||
|
||||
if(!proxyCfg.port && proxyCfg.protocol == 'http:'){
|
||||
proxyStr += ':80';
|
||||
}
|
||||
else if(!proxyCfg.port && proxyCfg.protocol == 'https:'){
|
||||
proxyStr += ':443';
|
||||
}
|
||||
|
||||
return proxyStr;
|
||||
}
|
||||
|
||||
export {
|
||||
buildProxy,
|
||||
usefulCookies,
|
||||
Req,
|
||||
};
|
||||
|
||||
|
|
@ -1,175 +1,175 @@
|
|||
import got from 'got';
|
||||
import fs from 'fs';
|
||||
import { GithubTag, TagCompare } from '../@types/github';
|
||||
import path from 'path';
|
||||
import { UpdateFile } from '../@types/updateFile';
|
||||
import packageJson from '../package.json';
|
||||
import { CompilerOptions, transpileModule } from 'typescript';
|
||||
import tsConfig from '../tsconfig.json';
|
||||
import fsextra from 'fs-extra';
|
||||
import seiHelper from 'sei-helper';
|
||||
const workingDir = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? path.dirname(process.execPath) : path.join(__dirname, '/..');
|
||||
const updateFilePlace = path.join(workingDir, 'config', 'updates.json');
|
||||
|
||||
const updateIgnore = [
|
||||
'*.d.ts',
|
||||
'.git',
|
||||
'lib',
|
||||
'node_modules',
|
||||
'@types',
|
||||
path.join('bin', 'mkvtoolnix'),
|
||||
path.join('config', 'token.yml'),
|
||||
'.eslint',
|
||||
'tsconfig.json',
|
||||
'updates.json',
|
||||
'tsc.ts'
|
||||
];
|
||||
|
||||
const askBeforeUpdate = [
|
||||
'*.yml'
|
||||
];
|
||||
|
||||
enum ApplyType {
|
||||
DELETE, ADD, UPDATE
|
||||
}
|
||||
|
||||
export type ApplyItem = {
|
||||
type: ApplyType,
|
||||
path: string,
|
||||
content: string
|
||||
}
|
||||
|
||||
export default (async (force = false) => {
|
||||
const isPackaged = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? true : false;
|
||||
if (isPackaged) {
|
||||
return;
|
||||
}
|
||||
let updateFile: UpdateFile|undefined;
|
||||
if (fs.existsSync(updateFilePlace)) {
|
||||
updateFile = JSON.parse(fs.readFileSync(updateFilePlace).toString()) as UpdateFile;
|
||||
if (new Date() < new Date(updateFile.nextCheck) && !force) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log('Checking for updates...');
|
||||
const tagRequest = await got('https://api.github.com/repos/anidl/multi-downloader-nx/tags');
|
||||
const tags = JSON.parse(tagRequest.body) as GithubTag[];
|
||||
|
||||
if (tags.length > 0) {
|
||||
const newer = tags.filter(a => {
|
||||
return isNewer(packageJson.version, a.name);
|
||||
});
|
||||
console.log(`Found ${tags.length} release tags and ${newer.length} that are new.`);
|
||||
|
||||
if (newer.length < 1) {
|
||||
console.log('[INFO] No new tags found');
|
||||
return done();
|
||||
}
|
||||
const newest = newer.sort((a, b) => a.name < b.name ? 1 : a.name > b.name ? -1 : 0)[0];
|
||||
const compareRequest = await got(`https://api.github.com/repos/anidl/multi-downloader-nx/compare/${packageJson.version}...${newest.name}`);
|
||||
|
||||
const compareJSON = JSON.parse(compareRequest.body) as TagCompare;
|
||||
|
||||
console.log(`You are behind by ${compareJSON.ahead_by} releases!`);
|
||||
const changedFiles = compareJSON.files.map(a => ({
|
||||
...a,
|
||||
filename: path.join(...a.filename.split('/'))
|
||||
})).filter(a => {
|
||||
return !updateIgnore.some(_filter => matchString(_filter, a.filename));
|
||||
});
|
||||
if (changedFiles.length < 1) {
|
||||
console.log('[INFO] No file changes found... updating package.json. If you think this is an error please get the newst version yourself.');
|
||||
return done(newest.name);
|
||||
}
|
||||
console.log(`Found file changes: \n${changedFiles.map(a => ` [${
|
||||
a.status === 'modified' ? '*' : a.status === 'added' ? '+' : '-'
|
||||
}] ${a.filename}`).join('\n')}`);
|
||||
|
||||
const remove: string[] = [];
|
||||
|
||||
changedFiles.filter(a => a.status !== 'added').forEach(async a => {
|
||||
if (!askBeforeUpdate.some(pattern => matchString(pattern, a.filename)))
|
||||
return;
|
||||
const answer = await seiHelper.question(`The developer decided that the file '${a.filename}' may contain information you changed yourself. Should they be overriden to be updated? [y/N]`);
|
||||
if (answer.toLowerCase() === 'y')
|
||||
remove.push(a.sha);
|
||||
});
|
||||
|
||||
const changesToApply = await Promise.all(changedFiles.filter(a => !remove.includes(a.sha)).map(async (a): Promise<ApplyItem> => {
|
||||
if (a.filename.endsWith('.ts')) {
|
||||
const ret = {
|
||||
path: a.filename.slice(0, -2) + 'js',
|
||||
content: transpileModule((await got(a.raw_url)).body, {
|
||||
compilerOptions: tsConfig.compilerOptions as unknown as CompilerOptions
|
||||
}).outputText,
|
||||
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
||||
};
|
||||
console.log('✓ transpiled %s', ret.path);
|
||||
return ret;
|
||||
} else {
|
||||
const ret = {
|
||||
path: a.filename,
|
||||
content: (await got(a.raw_url)).body,
|
||||
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
||||
};
|
||||
console.log('✓ transpiled %s', ret.path);
|
||||
return ret;
|
||||
}
|
||||
}));
|
||||
|
||||
changesToApply.forEach(a => {
|
||||
fsextra.ensureDirSync(path.dirname(a.path));
|
||||
fs.writeFileSync(path.join(__dirname, '..', a.path), a.content);
|
||||
console.log('✓ written %s', a.path);
|
||||
});
|
||||
|
||||
console.log('[INFO] Done');
|
||||
return done();
|
||||
}
|
||||
});
|
||||
|
||||
function done(newVersion?: string) {
|
||||
const next = new Date(Date.now() + 1000 * 60 * 60 * 24);
|
||||
fs.writeFileSync(updateFilePlace, JSON.stringify({
|
||||
lastCheck: Date.now(),
|
||||
nextCheck: next.getTime()
|
||||
} as UpdateFile, null, 2));
|
||||
if (newVersion) {
|
||||
fs.writeFileSync('../package.json', JSON.stringify({
|
||||
...packageJson,
|
||||
version: newVersion
|
||||
}, null, 4));
|
||||
}
|
||||
console.log('[INFO] Searching for update finished. Next time running on the ' + next.toLocaleDateString() + ' at ' + next.toLocaleTimeString() + '.');
|
||||
}
|
||||
|
||||
function isNewer(curr: string, compare: string) : boolean {
|
||||
const currParts = curr.split('.').map(a => parseInt(a));
|
||||
const compareParts = compare.split('.').map(a => parseInt(a));
|
||||
|
||||
for (let i = 0; i < Math.max(currParts.length, compareParts.length); i++) {
|
||||
if (currParts.length <= i)
|
||||
return true;
|
||||
if (compareParts.length <= i)
|
||||
return false;
|
||||
if (currParts[i] !== compareParts[i])
|
||||
return compareParts[i] > currParts[i];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function matchString(pattern: string, toMatch: string) : boolean {
|
||||
const filter = path.join('..', pattern);
|
||||
if (pattern.startsWith('*')) {
|
||||
return toMatch.endsWith(pattern.slice(1));
|
||||
} else if (filter.split(path.sep).pop()?.indexOf('.') === -1) {
|
||||
return toMatch.startsWith(filter);
|
||||
} else {
|
||||
return toMatch.split(path.sep).pop() === pattern;
|
||||
}
|
||||
import got from 'got';
|
||||
import fs from 'fs';
|
||||
import { GithubTag, TagCompare } from '../@types/github';
|
||||
import path from 'path';
|
||||
import { UpdateFile } from '../@types/updateFile';
|
||||
import packageJson from '../package.json';
|
||||
import { CompilerOptions, transpileModule } from 'typescript';
|
||||
import tsConfig from '../tsconfig.json';
|
||||
import fsextra from 'fs-extra';
|
||||
import seiHelper from 'sei-helper';
|
||||
const workingDir = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? path.dirname(process.execPath) : path.join(__dirname, '/..');
|
||||
const updateFilePlace = path.join(workingDir, 'config', 'updates.json');
|
||||
|
||||
const updateIgnore = [
|
||||
'*.d.ts',
|
||||
'.git',
|
||||
'lib',
|
||||
'node_modules',
|
||||
'@types',
|
||||
path.join('bin', 'mkvtoolnix'),
|
||||
path.join('config', 'token.yml'),
|
||||
'.eslint',
|
||||
'tsconfig.json',
|
||||
'updates.json',
|
||||
'tsc.ts'
|
||||
];
|
||||
|
||||
const askBeforeUpdate = [
|
||||
'*.yml'
|
||||
];
|
||||
|
||||
enum ApplyType {
|
||||
DELETE, ADD, UPDATE
|
||||
}
|
||||
|
||||
export type ApplyItem = {
|
||||
type: ApplyType,
|
||||
path: string,
|
||||
content: string
|
||||
}
|
||||
|
||||
export default (async (force = false) => {
|
||||
const isPackaged = (process as NodeJS.Process & {
|
||||
pkg?: unknown
|
||||
}).pkg ? true : false;
|
||||
if (isPackaged) {
|
||||
return;
|
||||
}
|
||||
let updateFile: UpdateFile|undefined;
|
||||
if (fs.existsSync(updateFilePlace)) {
|
||||
updateFile = JSON.parse(fs.readFileSync(updateFilePlace).toString()) as UpdateFile;
|
||||
if (new Date() < new Date(updateFile.nextCheck) && !force) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log('Checking for updates...');
|
||||
const tagRequest = await got('https://api.github.com/repos/anidl/multi-downloader-nx/tags');
|
||||
const tags = JSON.parse(tagRequest.body) as GithubTag[];
|
||||
|
||||
if (tags.length > 0) {
|
||||
const newer = tags.filter(a => {
|
||||
return isNewer(packageJson.version, a.name);
|
||||
});
|
||||
console.log(`Found ${tags.length} release tags and ${newer.length} that are new.`);
|
||||
|
||||
if (newer.length < 1) {
|
||||
console.log('[INFO] No new tags found');
|
||||
return done();
|
||||
}
|
||||
const newest = newer.sort((a, b) => a.name < b.name ? 1 : a.name > b.name ? -1 : 0)[0];
|
||||
const compareRequest = await got(`https://api.github.com/repos/anidl/multi-downloader-nx/compare/${packageJson.version}...${newest.name}`);
|
||||
|
||||
const compareJSON = JSON.parse(compareRequest.body) as TagCompare;
|
||||
|
||||
console.log(`You are behind by ${compareJSON.ahead_by} releases!`);
|
||||
const changedFiles = compareJSON.files.map(a => ({
|
||||
...a,
|
||||
filename: path.join(...a.filename.split('/'))
|
||||
})).filter(a => {
|
||||
return !updateIgnore.some(_filter => matchString(_filter, a.filename));
|
||||
});
|
||||
if (changedFiles.length < 1) {
|
||||
console.log('[INFO] No file changes found... updating package.json. If you think this is an error please get the newst version yourself.');
|
||||
return done(newest.name);
|
||||
}
|
||||
console.log(`Found file changes: \n${changedFiles.map(a => ` [${
|
||||
a.status === 'modified' ? '*' : a.status === 'added' ? '+' : '-'
|
||||
}] ${a.filename}`).join('\n')}`);
|
||||
|
||||
const remove: string[] = [];
|
||||
|
||||
changedFiles.filter(a => a.status !== 'added').forEach(async a => {
|
||||
if (!askBeforeUpdate.some(pattern => matchString(pattern, a.filename)))
|
||||
return;
|
||||
const answer = await seiHelper.question(`The developer decided that the file '${a.filename}' may contain information you changed yourself. Should they be overriden to be updated? [y/N]`);
|
||||
if (answer.toLowerCase() === 'y')
|
||||
remove.push(a.sha);
|
||||
});
|
||||
|
||||
const changesToApply = await Promise.all(changedFiles.filter(a => !remove.includes(a.sha)).map(async (a): Promise<ApplyItem> => {
|
||||
if (a.filename.endsWith('.ts')) {
|
||||
const ret = {
|
||||
path: a.filename.slice(0, -2) + 'js',
|
||||
content: transpileModule((await got(a.raw_url)).body, {
|
||||
compilerOptions: tsConfig.compilerOptions as unknown as CompilerOptions
|
||||
}).outputText,
|
||||
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
||||
};
|
||||
console.log('✓ transpiled %s', ret.path);
|
||||
return ret;
|
||||
} else {
|
||||
const ret = {
|
||||
path: a.filename,
|
||||
content: (await got(a.raw_url)).body,
|
||||
type: a.status === 'modified' ? ApplyType.UPDATE : a.status === 'added' ? ApplyType.ADD : ApplyType.DELETE
|
||||
};
|
||||
console.log('✓ transpiled %s', ret.path);
|
||||
return ret;
|
||||
}
|
||||
}));
|
||||
|
||||
changesToApply.forEach(a => {
|
||||
fsextra.ensureDirSync(path.dirname(a.path));
|
||||
fs.writeFileSync(path.join(__dirname, '..', a.path), a.content);
|
||||
console.log('✓ written %s', a.path);
|
||||
});
|
||||
|
||||
console.log('[INFO] Done');
|
||||
return done();
|
||||
}
|
||||
});
|
||||
|
||||
function done(newVersion?: string) {
|
||||
const next = new Date(Date.now() + 1000 * 60 * 60 * 24);
|
||||
fs.writeFileSync(updateFilePlace, JSON.stringify({
|
||||
lastCheck: Date.now(),
|
||||
nextCheck: next.getTime()
|
||||
} as UpdateFile, null, 2));
|
||||
if (newVersion) {
|
||||
fs.writeFileSync('../package.json', JSON.stringify({
|
||||
...packageJson,
|
||||
version: newVersion
|
||||
}, null, 4));
|
||||
}
|
||||
console.log('[INFO] Searching for update finished. Next time running on the ' + next.toLocaleDateString() + ' at ' + next.toLocaleTimeString() + '.');
|
||||
}
|
||||
|
||||
function isNewer(curr: string, compare: string) : boolean {
|
||||
const currParts = curr.split('.').map(a => parseInt(a));
|
||||
const compareParts = compare.split('.').map(a => parseInt(a));
|
||||
|
||||
for (let i = 0; i < Math.max(currParts.length, compareParts.length); i++) {
|
||||
if (currParts.length <= i)
|
||||
return true;
|
||||
if (compareParts.length <= i)
|
||||
return false;
|
||||
if (currParts[i] !== compareParts[i])
|
||||
return compareParts[i] > currParts[i];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function matchString(pattern: string, toMatch: string) : boolean {
|
||||
const filter = path.join('..', pattern);
|
||||
if (pattern.startsWith('*')) {
|
||||
return toMatch.endsWith(pattern.slice(1));
|
||||
} else if (filter.split(path.sep).pop()?.indexOf('.') === -1) {
|
||||
return toMatch.startsWith(filter);
|
||||
} else {
|
||||
return toMatch.split(path.sep).pop() === pattern;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,174 +1,174 @@
|
|||
// vtt loader
|
||||
export type Record = {
|
||||
text?: string,
|
||||
time_start?: string,
|
||||
time_end?: string,
|
||||
ext_param?: unknown
|
||||
};
|
||||
export type NullRecord = Record | null;
|
||||
|
||||
function loadVtt(vttStr: string) {
|
||||
const rx = /^([\d:.]*) --> ([\d:.]*)\s?(.*?)\s*$/;
|
||||
const lines = vttStr.replace(/\r?\n/g, '\n').split('\n');
|
||||
const data = []; let lineBuf = [], record: NullRecord = null;
|
||||
// check lines
|
||||
for (const l of lines) {
|
||||
const m = l.match(rx);
|
||||
if (m) {
|
||||
if (lineBuf.length > 0) {
|
||||
lineBuf.pop();
|
||||
}
|
||||
if (record !== null) {
|
||||
record.text = lineBuf.join('\n');
|
||||
data.push(record);
|
||||
}
|
||||
record = {
|
||||
time_start: m[1],
|
||||
time_end: m[2],
|
||||
ext_param: m[3].split(' ').map(x => x.split(':')).reduce((p: any, c: any) => (p[c[0]] = c[1]) && p, {}),
|
||||
};
|
||||
lineBuf = [];
|
||||
continue;
|
||||
}
|
||||
lineBuf.push(l);
|
||||
}
|
||||
if (record !== null) {
|
||||
if (lineBuf[lineBuf.length - 1] === '') {
|
||||
lineBuf.pop();
|
||||
}
|
||||
record.text = lineBuf.join('\n');
|
||||
data.push(record);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// ass specific
|
||||
function convertToAss(vttStr: string, lang: string, fontSize: number, fontName?: string){
|
||||
let ass = [
|
||||
'\ufeff[Script Info]',
|
||||
`Title: ${lang}`,
|
||||
'ScriptType: v4.00+',
|
||||
'PlayResX: 1280',
|
||||
'PlayResY: 720',
|
||||
'WrapStyle: 0',
|
||||
'ScaledBorderAndShadow: yes',
|
||||
'',
|
||||
'[V4+ Styles]',
|
||||
'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, '
|
||||
+ 'Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, '
|
||||
+ 'BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding',
|
||||
`Style: Main,${fontName || 'Noto Sans'},${fontSize},&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,10,10,10,1`,
|
||||
`Style: MainTop,${fontName || 'Noto Sans'},${fontSize},&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,8,10,10,10,1`,
|
||||
'',
|
||||
'[Events]',
|
||||
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
|
||||
];
|
||||
|
||||
const vttData = loadVtt(vttStr);
|
||||
for (const l of vttData) {
|
||||
const line = convertToAssLine(l, 'Main');
|
||||
ass = ass.concat(line);
|
||||
}
|
||||
|
||||
return ass.join('\r\n') + '\r\n';
|
||||
}
|
||||
|
||||
function convertToAssLine(l: Record, style: string) {
|
||||
const start = convertTime(l.time_start as string);
|
||||
const end = convertTime(l.time_end as string);
|
||||
const text = convertToAssText(l.text as string);
|
||||
|
||||
// debugger
|
||||
if ((l.ext_param as any).line === '7%') {
|
||||
style = 'MainTop';
|
||||
}
|
||||
|
||||
if ((l.ext_param as any).line === '10%') {
|
||||
style = 'MainTop';
|
||||
}
|
||||
|
||||
return `Dialogue: 0,${start},${end},${style},,0,0,0,,${text}`;
|
||||
}
|
||||
|
||||
function convertToAssText(text: string) {
|
||||
text = text
|
||||
.replace(/\r/g, '')
|
||||
.replace(/\n/g, '\\N')
|
||||
.replace(/\\N +/g, '\\N')
|
||||
.replace(/ +\\N/g, '\\N')
|
||||
.replace(/(\\N)+/g, '\\N')
|
||||
.replace(/<b[^>]*>([^<]*)<\/b>/g, '{\\b1}$1{\\b0}')
|
||||
.replace(/<i[^>]*>([^<]*)<\/i>/g, '{\\i1}$1{\\i0}')
|
||||
.replace(/<u[^>]*>([^<]*)<\/u>/g, '{\\u1}$1{\\u0}')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/<[^>]>/g, '')
|
||||
.replace(/\\N$/, '')
|
||||
.replace(/ +$/, '');
|
||||
return text;
|
||||
}
|
||||
|
||||
// srt specific
|
||||
function convertToSrt(vttStr: string){
|
||||
let srt: string[] = [], srtLineIdx = 0;
|
||||
|
||||
const vttData = loadVtt(vttStr);
|
||||
for (const l of vttData) {
|
||||
srtLineIdx++;
|
||||
const line = convertToSrtLine(l, srtLineIdx);
|
||||
srt = srt.concat(line);
|
||||
}
|
||||
|
||||
return srt.join('\r\n') + '\r\n';
|
||||
}
|
||||
|
||||
function convertToSrtLine(l: Record, idx: number) : string {
|
||||
const bom = idx == 1 ? '\ufeff' : '';
|
||||
const start = convertTime(l.time_start as string, true);
|
||||
const end = convertTime(l.time_end as string, true);
|
||||
const text = l.text;
|
||||
return `${bom}${idx}\r\n${start} --> ${end}\r\n${text}\r\n`;
|
||||
}
|
||||
|
||||
// time parser
|
||||
function convertTime(time: string, srtFormat = false) {
|
||||
const mTime = time.match(/([\d:]*)\.?(\d*)/);
|
||||
if (!mTime){
|
||||
return srtFormat ? '00:00:00,000' : '0:00:00.00';
|
||||
}
|
||||
return toSubsTime(mTime[0], srtFormat);
|
||||
}
|
||||
|
||||
function toSubsTime(str: string, srtFormat: boolean) : string {
|
||||
|
||||
const n = [], x: (string|number)[] = str.split(/[:.]/).map(x => Number(x)); let sx;
|
||||
|
||||
const msLen = srtFormat ? 3 : 2;
|
||||
const hLen = srtFormat ? 2 : 1;
|
||||
|
||||
x[3] = '0.' + ('' + x[3]).padStart(3, '0');
|
||||
sx = (x[0] as number)*60*60 + (x[1] as number)*60 + (x[2] as number) + Number(x[3]);
|
||||
sx = sx.toFixed(msLen).split('.');
|
||||
|
||||
|
||||
n.unshift(padTimeNum((srtFormat ? ',' : '.'), sx[1], msLen));
|
||||
sx = Number(sx[0]);
|
||||
|
||||
n.unshift(padTimeNum(':', sx%60, 2));
|
||||
n.unshift(padTimeNum(':', Math.floor(sx/60)%60, 2));
|
||||
n.unshift(padTimeNum('', Math.floor(sx/3600)%60, hLen));
|
||||
|
||||
return n.join('');
|
||||
}
|
||||
|
||||
function padTimeNum(sep: string, input: string|number , pad:number){
|
||||
return sep + ('' + input).padStart(pad, '0');
|
||||
}
|
||||
|
||||
// export module
|
||||
const _default = (vttStr: string, toSrt: boolean, lang = 'English', fontSize: number, fontName?: string) => {
|
||||
const convert = toSrt ? convertToSrt : convertToAss;
|
||||
return convert(vttStr, lang, fontSize, fontName);
|
||||
};
|
||||
export default _default;
|
||||
// vtt loader
|
||||
export type Record = {
|
||||
text?: string,
|
||||
time_start?: string,
|
||||
time_end?: string,
|
||||
ext_param?: unknown
|
||||
};
|
||||
export type NullRecord = Record | null;
|
||||
|
||||
function loadVtt(vttStr: string) {
|
||||
const rx = /^([\d:.]*) --> ([\d:.]*)\s?(.*?)\s*$/;
|
||||
const lines = vttStr.replace(/\r?\n/g, '\n').split('\n');
|
||||
const data = []; let lineBuf = [], record: NullRecord = null;
|
||||
// check lines
|
||||
for (const l of lines) {
|
||||
const m = l.match(rx);
|
||||
if (m) {
|
||||
if (lineBuf.length > 0) {
|
||||
lineBuf.pop();
|
||||
}
|
||||
if (record !== null) {
|
||||
record.text = lineBuf.join('\n');
|
||||
data.push(record);
|
||||
}
|
||||
record = {
|
||||
time_start: m[1],
|
||||
time_end: m[2],
|
||||
ext_param: m[3].split(' ').map(x => x.split(':')).reduce((p: any, c: any) => (p[c[0]] = c[1]) && p, {}),
|
||||
};
|
||||
lineBuf = [];
|
||||
continue;
|
||||
}
|
||||
lineBuf.push(l);
|
||||
}
|
||||
if (record !== null) {
|
||||
if (lineBuf[lineBuf.length - 1] === '') {
|
||||
lineBuf.pop();
|
||||
}
|
||||
record.text = lineBuf.join('\n');
|
||||
data.push(record);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// ass specific
|
||||
function convertToAss(vttStr: string, lang: string, fontSize: number, fontName?: string){
|
||||
let ass = [
|
||||
'\ufeff[Script Info]',
|
||||
`Title: ${lang}`,
|
||||
'ScriptType: v4.00+',
|
||||
'PlayResX: 1280',
|
||||
'PlayResY: 720',
|
||||
'WrapStyle: 0',
|
||||
'ScaledBorderAndShadow: yes',
|
||||
'',
|
||||
'[V4+ Styles]',
|
||||
'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, '
|
||||
+ 'Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, '
|
||||
+ 'BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding',
|
||||
`Style: Main,${fontName || 'Noto Sans'},${fontSize},&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,10,10,10,1`,
|
||||
`Style: MainTop,${fontName || 'Noto Sans'},${fontSize},&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,8,10,10,10,1`,
|
||||
'',
|
||||
'[Events]',
|
||||
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
|
||||
];
|
||||
|
||||
const vttData = loadVtt(vttStr);
|
||||
for (const l of vttData) {
|
||||
const line = convertToAssLine(l, 'Main');
|
||||
ass = ass.concat(line);
|
||||
}
|
||||
|
||||
return ass.join('\r\n') + '\r\n';
|
||||
}
|
||||
|
||||
function convertToAssLine(l: Record, style: string) {
|
||||
const start = convertTime(l.time_start as string);
|
||||
const end = convertTime(l.time_end as string);
|
||||
const text = convertToAssText(l.text as string);
|
||||
|
||||
// debugger
|
||||
if ((l.ext_param as any).line === '7%') {
|
||||
style = 'MainTop';
|
||||
}
|
||||
|
||||
if ((l.ext_param as any).line === '10%') {
|
||||
style = 'MainTop';
|
||||
}
|
||||
|
||||
return `Dialogue: 0,${start},${end},${style},,0,0,0,,${text}`;
|
||||
}
|
||||
|
||||
function convertToAssText(text: string) {
|
||||
text = text
|
||||
.replace(/\r/g, '')
|
||||
.replace(/\n/g, '\\N')
|
||||
.replace(/\\N +/g, '\\N')
|
||||
.replace(/ +\\N/g, '\\N')
|
||||
.replace(/(\\N)+/g, '\\N')
|
||||
.replace(/<b[^>]*>([^<]*)<\/b>/g, '{\\b1}$1{\\b0}')
|
||||
.replace(/<i[^>]*>([^<]*)<\/i>/g, '{\\i1}$1{\\i0}')
|
||||
.replace(/<u[^>]*>([^<]*)<\/u>/g, '{\\u1}$1{\\u0}')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/<[^>]>/g, '')
|
||||
.replace(/\\N$/, '')
|
||||
.replace(/ +$/, '');
|
||||
return text;
|
||||
}
|
||||
|
||||
// srt specific
|
||||
function convertToSrt(vttStr: string){
|
||||
let srt: string[] = [], srtLineIdx = 0;
|
||||
|
||||
const vttData = loadVtt(vttStr);
|
||||
for (const l of vttData) {
|
||||
srtLineIdx++;
|
||||
const line = convertToSrtLine(l, srtLineIdx);
|
||||
srt = srt.concat(line);
|
||||
}
|
||||
|
||||
return srt.join('\r\n') + '\r\n';
|
||||
}
|
||||
|
||||
function convertToSrtLine(l: Record, idx: number) : string {
|
||||
const bom = idx == 1 ? '\ufeff' : '';
|
||||
const start = convertTime(l.time_start as string, true);
|
||||
const end = convertTime(l.time_end as string, true);
|
||||
const text = l.text;
|
||||
return `${bom}${idx}\r\n${start} --> ${end}\r\n${text}\r\n`;
|
||||
}
|
||||
|
||||
// time parser
|
||||
function convertTime(time: string, srtFormat = false) {
|
||||
const mTime = time.match(/([\d:]*)\.?(\d*)/);
|
||||
if (!mTime){
|
||||
return srtFormat ? '00:00:00,000' : '0:00:00.00';
|
||||
}
|
||||
return toSubsTime(mTime[0], srtFormat);
|
||||
}
|
||||
|
||||
function toSubsTime(str: string, srtFormat: boolean) : string {
|
||||
|
||||
const n = [], x: (string|number)[] = str.split(/[:.]/).map(x => Number(x)); let sx;
|
||||
|
||||
const msLen = srtFormat ? 3 : 2;
|
||||
const hLen = srtFormat ? 2 : 1;
|
||||
|
||||
x[3] = '0.' + ('' + x[3]).padStart(3, '0');
|
||||
sx = (x[0] as number)*60*60 + (x[1] as number)*60 + (x[2] as number) + Number(x[3]);
|
||||
sx = sx.toFixed(msLen).split('.');
|
||||
|
||||
|
||||
n.unshift(padTimeNum((srtFormat ? ',' : '.'), sx[1], msLen));
|
||||
sx = Number(sx[0]);
|
||||
|
||||
n.unshift(padTimeNum(':', sx%60, 2));
|
||||
n.unshift(padTimeNum(':', Math.floor(sx/60)%60, 2));
|
||||
n.unshift(padTimeNum('', Math.floor(sx/3600)%60, hLen));
|
||||
|
||||
return n.join('');
|
||||
}
|
||||
|
||||
function padTimeNum(sep: string, input: string|number , pad:number){
|
||||
return sep + ('' + input).padStart(pad, '0');
|
||||
}
|
||||
|
||||
// export module
|
||||
const _default = (vttStr: string, toSrt: boolean, lang = 'English', fontSize: number, fontName?: string) => {
|
||||
const convert = toSrt ? convertToSrt : convertToAss;
|
||||
return convert(vttStr, lang, fontSize, fontName);
|
||||
};
|
||||
export default _default;
|
||||
|
|
|
|||
2650
package-lock.json
generated
2650
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "multi-downloader-nx",
|
||||
"short_name": "aniDL",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"description": "Download videos from Funimation or Crunchyroll via cli",
|
||||
"keywords": [
|
||||
"download",
|
||||
|
|
|
|||
322
tsc.ts
322
tsc.ts
|
|
@ -1,162 +1,162 @@
|
|||
import { ChildProcess, exec } from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { removeSync, copyFileSync } from 'fs-extra';
|
||||
import packageJSON from './package.json';
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
let buildIgnore: string[] = [];
|
||||
|
||||
const isTest = argv.length > 0 && argv[0] === 'test';
|
||||
const isGUI = !(argv.length > 1 && argv[1] === 'false');
|
||||
|
||||
if (!isTest)
|
||||
buildIgnore = [
|
||||
'*/\\.env',
|
||||
'*/node_modules/*'
|
||||
];
|
||||
|
||||
if (!isGUI)
|
||||
buildIgnore = buildIgnore.concat([
|
||||
'./gui*',
|
||||
'./build*'
|
||||
])
|
||||
|
||||
|
||||
const ignore = [
|
||||
...buildIgnore,
|
||||
'*/\\.git*',
|
||||
'./lib*',
|
||||
'*/@types*',
|
||||
'./out*',
|
||||
'./bin/mkvtoolnix*',
|
||||
'./config/token.yml$',
|
||||
'./config/updates.json$',
|
||||
'./config/cr_token.yml$',
|
||||
'./config/funi_token.yml$',
|
||||
'*/\\.eslint*',
|
||||
'*/*\\.tsx?$',
|
||||
'./fonts*',
|
||||
'./gui/react*',
|
||||
].map(a => a.replace(/\*/g, '[^]*').replace(/\.\//g, escapeRegExp(__dirname) + '/').replace(/\//g, path.sep === '\\' ? '\\\\' : '/')).map(a => new RegExp(a, 'i'));
|
||||
|
||||
export { ignore };
|
||||
|
||||
(async () => {
|
||||
|
||||
const waitForProcess = async (proc: ChildProcess) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
proc.stdout?.on('data', console.log);
|
||||
proc.stderr?.on('data', console.error);
|
||||
proc.on('close', resolve);
|
||||
proc.on('error', reject);
|
||||
});
|
||||
};
|
||||
|
||||
process.stdout.write('Removing lib dir... ');
|
||||
removeSync('lib');
|
||||
process.stdout.write('✓\nRunning tsc... ');
|
||||
const tsc = exec('npx tsc');
|
||||
|
||||
await waitForProcess(tsc);
|
||||
|
||||
if (!isGUI) {
|
||||
fs.emptyDirSync(path.join('lib', 'gui'));
|
||||
fs.rmdirSync(path.join('lib', 'gui'));
|
||||
}
|
||||
|
||||
if (!isTest && isGUI) {
|
||||
process.stdout.write('✓\nBuilding react... ');
|
||||
|
||||
const installReactDependencies = exec('npm install', {
|
||||
cwd: path.join(__dirname, 'gui', 'react'),
|
||||
});
|
||||
|
||||
await waitForProcess(installReactDependencies);
|
||||
|
||||
const react = exec('npm run build', {
|
||||
cwd: path.join(__dirname, 'gui', 'react'),
|
||||
});
|
||||
|
||||
await waitForProcess(react);
|
||||
}
|
||||
|
||||
process.stdout.write('✓\nCopying files... ');
|
||||
if (!isTest && isGUI) {
|
||||
copyDir(path.join(__dirname, 'gui', 'react', 'build'), path.join(__dirname, 'lib', 'gui', 'electron', 'build'));
|
||||
}
|
||||
|
||||
const files = readDir(__dirname);
|
||||
files.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);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdout.write('✓\nInstalling dependencies... ');
|
||||
if (!isTest && !isGUI) {
|
||||
alterJSON();
|
||||
}
|
||||
if (!isTest) {
|
||||
const dependencies = exec(`npm install ${isGUI ? '' : '--production'}`, {
|
||||
cwd: path.join(__dirname, 'lib')
|
||||
});
|
||||
await waitForProcess(dependencies);
|
||||
}
|
||||
|
||||
process.stdout.write('✓\n');
|
||||
})();
|
||||
|
||||
function alterJSON() {
|
||||
packageJSON.main = 'index.js';
|
||||
fs.writeFileSync(path.join('lib', 'package.json'), JSON.stringify(packageJSON, null, 4));
|
||||
}
|
||||
|
||||
function readDir (dir: string): {
|
||||
path: string,
|
||||
stats: fs.Stats
|
||||
}[] {
|
||||
const items: {
|
||||
path: string,
|
||||
stats: fs.Stats
|
||||
}[] = [];
|
||||
const content = fs.readdirSync(dir);
|
||||
itemLoop: for (const item of content) {
|
||||
const itemPath = path.join(dir, item);
|
||||
for (const ignoreItem of ignore) {
|
||||
if (ignoreItem.test(itemPath))
|
||||
continue itemLoop;
|
||||
}
|
||||
const stats = fs.statSync(itemPath);
|
||||
items.push({
|
||||
path: itemPath,
|
||||
stats
|
||||
});
|
||||
if (stats.isDirectory()) {
|
||||
items.push(...readDir(itemPath));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async function copyDir(src: string, dest: string) {
|
||||
await fs.promises.mkdir(dest, { recursive: true });
|
||||
const entries = await fs.promises.readdir(src, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
|
||||
entry.isDirectory() ?
|
||||
await copyDir(srcPath, destPath) :
|
||||
await fs.promises.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
import { ChildProcess, exec } from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { removeSync, copyFileSync } from 'fs-extra';
|
||||
import packageJSON from './package.json';
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
let buildIgnore: string[] = [];
|
||||
|
||||
const isTest = argv.length > 0 && argv[0] === 'test';
|
||||
const isGUI = !(argv.length > 1 && argv[1] === 'false');
|
||||
|
||||
if (!isTest)
|
||||
buildIgnore = [
|
||||
'*/\\.env',
|
||||
'*/node_modules/*'
|
||||
];
|
||||
|
||||
if (!isGUI)
|
||||
buildIgnore = buildIgnore.concat([
|
||||
'./gui*',
|
||||
'./build*'
|
||||
]);
|
||||
|
||||
|
||||
const ignore = [
|
||||
...buildIgnore,
|
||||
'*/\\.git*',
|
||||
'./lib*',
|
||||
'*/@types*',
|
||||
'./out*',
|
||||
'./bin/mkvtoolnix*',
|
||||
'./config/token.yml$',
|
||||
'./config/updates.json$',
|
||||
'./config/cr_token.yml$',
|
||||
'./config/funi_token.yml$',
|
||||
'*/\\.eslint*',
|
||||
'*/*\\.tsx?$',
|
||||
'./fonts*',
|
||||
'./gui/react*',
|
||||
].map(a => a.replace(/\*/g, '[^]*').replace(/\.\//g, escapeRegExp(__dirname) + '/').replace(/\//g, path.sep === '\\' ? '\\\\' : '/')).map(a => new RegExp(a, 'i'));
|
||||
|
||||
export { ignore };
|
||||
|
||||
(async () => {
|
||||
|
||||
const waitForProcess = async (proc: ChildProcess) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
proc.stdout?.on('data', console.log);
|
||||
proc.stderr?.on('data', console.error);
|
||||
proc.on('close', resolve);
|
||||
proc.on('error', reject);
|
||||
});
|
||||
};
|
||||
|
||||
process.stdout.write('Removing lib dir... ');
|
||||
removeSync('lib');
|
||||
process.stdout.write('✓\nRunning tsc... ');
|
||||
const tsc = exec('npx tsc');
|
||||
|
||||
await waitForProcess(tsc);
|
||||
|
||||
if (!isGUI) {
|
||||
fs.emptyDirSync(path.join('lib', 'gui'));
|
||||
fs.rmdirSync(path.join('lib', 'gui'));
|
||||
}
|
||||
|
||||
if (!isTest && isGUI) {
|
||||
process.stdout.write('✓\nBuilding react... ');
|
||||
|
||||
const installReactDependencies = exec('npm install', {
|
||||
cwd: path.join(__dirname, 'gui', 'react'),
|
||||
});
|
||||
|
||||
await waitForProcess(installReactDependencies);
|
||||
|
||||
const react = exec('npm run build', {
|
||||
cwd: path.join(__dirname, 'gui', 'react'),
|
||||
});
|
||||
|
||||
await waitForProcess(react);
|
||||
}
|
||||
|
||||
process.stdout.write('✓\nCopying files... ');
|
||||
if (!isTest && isGUI) {
|
||||
copyDir(path.join(__dirname, 'gui', 'react', 'build'), path.join(__dirname, 'lib', 'gui', 'electron', 'build'));
|
||||
}
|
||||
|
||||
const files = readDir(__dirname);
|
||||
files.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);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdout.write('✓\nInstalling dependencies... ');
|
||||
if (!isTest && !isGUI) {
|
||||
alterJSON();
|
||||
}
|
||||
if (!isTest) {
|
||||
const dependencies = exec(`npm install ${isGUI ? '' : '--production'}`, {
|
||||
cwd: path.join(__dirname, 'lib')
|
||||
});
|
||||
await waitForProcess(dependencies);
|
||||
}
|
||||
|
||||
process.stdout.write('✓\n');
|
||||
})();
|
||||
|
||||
function alterJSON() {
|
||||
packageJSON.main = 'index.js';
|
||||
fs.writeFileSync(path.join('lib', 'package.json'), JSON.stringify(packageJSON, null, 4));
|
||||
}
|
||||
|
||||
function readDir (dir: string): {
|
||||
path: string,
|
||||
stats: fs.Stats
|
||||
}[] {
|
||||
const items: {
|
||||
path: string,
|
||||
stats: fs.Stats
|
||||
}[] = [];
|
||||
const content = fs.readdirSync(dir);
|
||||
itemLoop: for (const item of content) {
|
||||
const itemPath = path.join(dir, item);
|
||||
for (const ignoreItem of ignore) {
|
||||
if (ignoreItem.test(itemPath))
|
||||
continue itemLoop;
|
||||
}
|
||||
const stats = fs.statSync(itemPath);
|
||||
items.push({
|
||||
path: itemPath,
|
||||
stats
|
||||
});
|
||||
if (stats.isDirectory()) {
|
||||
items.push(...readDir(itemPath));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async function copyDir(src: string, dest: string) {
|
||||
await fs.promises.mkdir(dest, { recursive: true });
|
||||
const entries = await fs.promises.readdir(src, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
|
||||
entry.isDirectory() ?
|
||||
await copyDir(srcPath, destPath) :
|
||||
await fs.promises.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
Loading…
Reference in a new issue