Rest of the tab conversion

This commit is contained in:
AnimeDL 2025-09-27 10:27:32 -07:00
parent 460b4c1d0e
commit a14466ec5d
76 changed files with 4315 additions and 4313 deletions

View file

@ -11,9 +11,9 @@
"requirePragma": false, "requirePragma": false,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"tabWidth": 2, "tabWidth": 4,
"trailingComma": "none", "trailingComma": "none",
"useTabs": false, "useTabs": true,
"vueIndentScriptAndStyle": false, "vueIndentScriptAndStyle": false,
"printWidth": 180, "printWidth": 180,
"endOfLine": "auto" "endOfLine": "auto"

View file

@ -1,50 +1,50 @@
export interface ADNPlayerConfig { export interface ADNPlayerConfig {
player: Player; player: Player;
} }
export interface Player { export interface Player {
image: string; image: string;
options: Options; options: Options;
} }
export interface Options { export interface Options {
user: User; user: User;
chromecast: Chromecast; chromecast: Chromecast;
ios: Ios; ios: Ios;
video: Video; video: Video;
dock: any[]; dock: any[];
preference: Preference; preference: Preference;
} }
export interface Chromecast { export interface Chromecast {
appId: string; appId: string;
refreshTokenUrl: string; refreshTokenUrl: string;
} }
export interface Ios { export interface Ios {
videoUrl: string; videoUrl: string;
appUrl: string; appUrl: string;
title: string; title: string;
} }
export interface Preference { export interface Preference {
quality: string; quality: string;
autoplay: boolean; autoplay: boolean;
language: string; language: string;
green: boolean; green: boolean;
} }
export interface User { export interface User {
hasAccess: boolean; hasAccess: boolean;
profileId: number; profileId: number;
refreshToken: string; refreshToken: string;
refreshTokenUrl: string; refreshTokenUrl: string;
} }
export interface Video { export interface Video {
startDate: null; startDate: null;
currentDate: Date; currentDate: Date;
available: boolean; available: boolean;
free: boolean; free: boolean;
url: string; url: string;
} }

76
@types/adnSearch.d.ts vendored
View file

@ -1,46 +1,46 @@
export interface ADNSearch { export interface ADNSearch {
shows: ADNSearchShow[]; shows: ADNSearchShow[];
total: number; total: number;
} }
export interface ADNSearchShow { export interface ADNSearchShow {
id: number; id: number;
title: string; title: string;
type: string; type: string;
originalTitle: string; originalTitle: string;
shortTitle: string; shortTitle: string;
reference: string; reference: string;
age: string; age: string;
languages: string[]; languages: string[];
summary: string; summary: string;
image: string; image: string;
image2x: string; image2x: string;
imageHorizontal: string; imageHorizontal: string;
imageHorizontal2x: string; imageHorizontal2x: string;
url: string; url: string;
urlPath: string; urlPath: string;
episodeCount: number; episodeCount: number;
genres: string[]; genres: string[];
copyright: string; copyright: string;
rating: number; rating: number;
ratingsCount: number; ratingsCount: number;
commentsCount: number; commentsCount: number;
qualities: string[]; qualities: string[];
simulcast: boolean; simulcast: boolean;
free: boolean; free: boolean;
available: boolean; available: boolean;
download: boolean; download: boolean;
basedOn: string; basedOn: string;
tagline: null; tagline: null;
firstReleaseYear: string; firstReleaseYear: string;
productionStudio: string; productionStudio: string;
countryOfOrigin: string; countryOfOrigin: string;
productionTeam: ProductionTeam[]; productionTeam: ProductionTeam[];
nextVideoReleaseDate: null; nextVideoReleaseDate: null;
indexable: boolean; indexable: boolean;
} }
export interface ProductionTeam { export interface ProductionTeam {
role: string; role: string;
name: string; name: string;
} }

View file

@ -1,51 +1,51 @@
export interface ADNStreams { export interface ADNStreams {
links: Links; links: Links;
video: Video; video: Video;
metadata: Metadata; metadata: Metadata;
} }
export interface Links { export interface Links {
streaming: Streaming; streaming: Streaming;
subtitles: Subtitles; subtitles: Subtitles;
history: string; history: string;
nextVideoUrl: string; nextVideoUrl: string;
previousVideoUrl: string; previousVideoUrl: string;
} }
export interface Streaming { export interface Streaming {
[streams: string]: Streams; [streams: string]: Streams;
} }
export interface Streams { export interface Streams {
mobile: string; mobile: string;
sd: string; sd: string;
hd: string; hd: string;
fhd: string; fhd: string;
auto: string; auto: string;
} }
export interface Subtitles { export interface Subtitles {
all: string; all: string;
} }
export interface Metadata { export interface Metadata {
title: string; title: string;
subtitle: string; subtitle: string;
summary: null; summary: null;
rating: number; rating: number;
} }
export interface Video { export interface Video {
guid: string; guid: string;
id: number; id: number;
currentTime: number; currentTime: number;
duration: number; duration: number;
url: string; url: string;
image: string; image: string;
tcEpisodeStart?:string; tcEpisodeStart?:string;
tcEpisodeEnd?: string; tcEpisodeEnd?: string;
tcIntroStart?: string; tcIntroStart?: string;
tcIntroEnd?: string; tcIntroEnd?: string;
tcEndingStart?: string; tcEndingStart?: string;
tcEndingEnd?: string; tcEndingEnd?: string;
} }

View file

@ -1,11 +1,11 @@
export interface ADNSubtitles { export interface ADNSubtitles {
[subtitleLang: string]: Subtitle[]; [subtitleLang: string]: Subtitle[];
} }
export interface Subtitle { export interface Subtitle {
startTime: number; startTime: number;
endTime: number; endTime: number;
positionAlign: string; positionAlign: string;
lineAlign: string; lineAlign: string;
text: string; text: string;
} }

132
@types/adnVideos.d.ts vendored
View file

@ -1,77 +1,77 @@
export interface ADNVideos { export interface ADNVideos {
videos: ADNVideo[]; videos: ADNVideo[];
} }
export interface ADNVideo { export interface ADNVideo {
id: number; id: number;
title: string; title: string;
name: string; name: string;
number: string; number: string;
shortNumber: string; shortNumber: string;
season: string; season: string;
reference: string; reference: string;
type: string; type: string;
order: number; order: number;
image: string; image: string;
image2x: string; image2x: string;
summary: string; summary: string;
releaseDate: Date; releaseDate: Date;
duration: number; duration: number;
url: string; url: string;
urlPath: string; urlPath: string;
embeddedUrl: string; embeddedUrl: string;
languages: string[]; languages: string[];
qualities: string[]; qualities: string[];
rating: number; rating: number;
ratingsCount: number; ratingsCount: number;
commentsCount: number; commentsCount: number;
available: boolean; available: boolean;
download: boolean; download: boolean;
free: boolean; free: boolean;
freeWithAds: boolean; freeWithAds: boolean;
show: Show; show: Show;
indexable: boolean; indexable: boolean;
isSelected?: boolean; isSelected?: boolean;
} }
export interface Show { export interface Show {
id: number; id: number;
title: string; title: string;
type: string; type: string;
originalTitle: string; originalTitle: string;
shortTitle: string; shortTitle: string;
reference: string; reference: string;
age: string; age: string;
languages: string[]; languages: string[];
summary: string; summary: string;
image: string; image: string;
image2x: string; image2x: string;
imageHorizontal: string; imageHorizontal: string;
imageHorizontal2x: string; imageHorizontal2x: string;
url: string; url: string;
urlPath: string; urlPath: string;
episodeCount: number; episodeCount: number;
genres: string[]; genres: string[];
copyright: string; copyright: string;
rating: number; rating: number;
ratingsCount: number; ratingsCount: number;
commentsCount: number; commentsCount: number;
qualities: string[]; qualities: string[];
simulcast: boolean; simulcast: boolean;
free: boolean; free: boolean;
available: boolean; available: boolean;
download: boolean; download: boolean;
basedOn: string; basedOn: string;
tagline: string; tagline: string;
firstReleaseYear: string; firstReleaseYear: string;
productionStudio: string; productionStudio: string;
countryOfOrigin: string; countryOfOrigin: string;
productionTeam: ProductionTeam[]; productionTeam: ProductionTeam[];
nextVideoReleaseDate: Date; nextVideoReleaseDate: Date;
indexable: boolean; indexable: boolean;
} }
export interface ProductionTeam { export interface ProductionTeam {
role: string; role: string;
name: string; name: string;
} }

View file

@ -1,88 +1,88 @@
export interface AnimeOnegaiSearch { export interface AnimeOnegaiSearch {
text: string; text: string;
list: AOSearchResult[]; list: AOSearchResult[];
} }
export interface AOSearchResult { export interface AOSearchResult {
/** /**
* Asset ID * Asset ID
*/ */
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
title: string; title: string;
active: boolean; active: boolean;
excerpt: string; excerpt: string;
description: string; description: string;
bg: string; bg: string;
poster: string; poster: string;
entry: string; entry: string;
code_name: string; code_name: string;
/** /**
* The Video ID required to get the streams * The Video ID required to get the streams
*/ */
video_entry: string; video_entry: string;
trailer: string; trailer: string;
year: number; year: number;
/** /**
* Asset Type, Known Possibilities * Asset Type, Known Possibilities
* * 1 - Video * * 1 - Video
* * 2 - Series * * 2 - Series
*/ */
asset_type: 1 | 2; asset_type: 1 | 2;
status: number; status: number;
permalink: string; permalink: string;
duration: string; duration: string;
subtitles: boolean; subtitles: boolean;
price: number; price: number;
rent_price: number; rent_price: number;
rating: number; rating: number;
color: number | null; color: number | null;
classification: number; classification: number;
brazil_classification: null | string; brazil_classification: null | string;
likes: number; likes: number;
views: number; views: number;
button: string; button: string;
stream_url: string; stream_url: string;
stream_url_backup: string; stream_url_backup: string;
copyright: null | string; copyright: null | string;
skip_intro: null | string; skip_intro: null | string;
ending: null | string; ending: null | string;
bumper_intro: string; bumper_intro: string;
ads: string; ads: string;
age_restriction: boolean | null; age_restriction: boolean | null;
epg: null; epg: null;
allow_languages: string[] | null; allow_languages: string[] | null;
allow_countries: string[] | null; allow_countries: string[] | null;
classification_text: string; classification_text: string;
locked: boolean; locked: boolean;
resign: boolean; resign: boolean;
favorite: boolean; favorite: boolean;
actors_list: null; actors_list: null;
voiceactors_list: null; voiceactors_list: null;
artdirectors_list: null; artdirectors_list: null;
audios_list: null; audios_list: null;
awards_list: null; awards_list: null;
companies_list: null; companies_list: null;
countries_list: null; countries_list: null;
directors_list: null; directors_list: null;
edition_list: null; edition_list: null;
genres_list: null; genres_list: null;
music_list: null; music_list: null;
photograpy_list: null; photograpy_list: null;
producer_list: null; producer_list: null;
screenwriter_list: null; screenwriter_list: null;
season_list: null; season_list: null;
tags_list: null; tags_list: null;
chapter_id: number; chapter_id: number;
chapter_entry: string; chapter_entry: string;
chapter_poster: string; chapter_poster: string;
progress_time: number; progress_time: number;
progress_percent: number; progress_percent: number;
included_subscription: number; included_subscription: number;
paid_content: number; paid_content: number;
rent_content: number; rent_content: number;
objectID: string; objectID: string;
lang: string; lang: string;
} }

View file

@ -1,36 +1,36 @@
export interface AnimeOnegaiSeasons { export interface AnimeOnegaiSeasons {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
name: string; name: string;
number: number; number: number;
asset_id: number; asset_id: number;
entry: string; entry: string;
description: string; description: string;
active: boolean; active: boolean;
allow_languages: string[]; allow_languages: string[];
allow_countries: string[]; allow_countries: string[];
list: Episode[]; list: Episode[];
} }
export interface Episode { export interface Episode {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
name: string; name: string;
number: number; number: number;
description: string; description: string;
thumbnail: string; thumbnail: string;
entry: string; entry: string;
video_entry: string; video_entry: string;
active: boolean; active: boolean;
season_id: number; season_id: number;
stream_url: string; stream_url: string;
skip_intro: null; skip_intro: null;
ending: null; ending: null;
open_free: boolean; open_free: boolean;
asset_id: number; asset_id: number;
age_restriction: boolean; age_restriction: boolean;
} }

View file

@ -1,111 +1,111 @@
export interface AnimeOnegaiSeries { export interface AnimeOnegaiSeries {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
title: string; title: string;
active: boolean; active: boolean;
excerpt: string; excerpt: string;
description: string; description: string;
bg: string; bg: string;
poster: string; poster: string;
entry: string; entry: string;
code_name: string; code_name: string;
/** /**
* The Video ID required to get the streams * The Video ID required to get the streams
*/ */
video_entry: string; video_entry: string;
trailer: string; trailer: string;
year: number; year: number;
asset_type: number; asset_type: number;
status: number; status: number;
permalink: string; permalink: string;
duration: string; duration: string;
subtitles: boolean; subtitles: boolean;
price: number; price: number;
rent_price: number; rent_price: number;
rating: number; rating: number;
color: number; color: number;
classification: number; classification: number;
brazil_classification: string; brazil_classification: string;
likes: number; likes: number;
views: number; views: number;
button: string; button: string;
stream_url: string; stream_url: string;
stream_url_backup: string; stream_url_backup: string;
copyright: string; copyright: string;
skip_intro: null; skip_intro: null;
ending: null; ending: null;
bumper_intro: string; bumper_intro: string;
ads: string; ads: string;
age_restriction: boolean; age_restriction: boolean;
epg: null; epg: null;
allow_languages: string[]; allow_languages: string[];
allow_countries: string[]; allow_countries: string[];
classification_text: string; classification_text: string;
locked: boolean; locked: boolean;
resign: boolean; resign: boolean;
favorite: boolean; favorite: boolean;
actors_list: CtorsList[]; actors_list: CtorsList[];
voiceactors_list: CtorsList[]; voiceactors_list: CtorsList[];
artdirectors_list: any[]; artdirectors_list: any[];
audios_list: SList[]; audios_list: SList[];
awards_list: any[]; awards_list: any[];
companies_list: any[]; companies_list: any[];
countries_list: any[]; countries_list: any[];
directors_list: CtorsList[]; directors_list: CtorsList[];
edition_list: any[]; edition_list: any[];
genres_list: SList[]; genres_list: SList[];
music_list: any[]; music_list: any[];
photograpy_list: any[]; photograpy_list: any[];
producer_list: any[]; producer_list: any[];
screenwriter_list: any[]; screenwriter_list: any[];
season_list: any[]; season_list: any[];
tags_list: TagsList[]; tags_list: TagsList[];
chapter_id: number; chapter_id: number;
chapter_entry: string; chapter_entry: string;
chapter_poster: string; chapter_poster: string;
progress_time: number; progress_time: number;
progress_percent: number; progress_percent: number;
included_subscription: number; included_subscription: number;
paid_content: number; paid_content: number;
rent_content: number; rent_content: number;
objectID: string; objectID: string;
lang: string; lang: string;
} }
export interface CtorsList { export interface CtorsList {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
name: string; name: string;
Permalink?: string; Permalink?: string;
country: number | null; country: number | null;
year: number | null; year: number | null;
death: number | null; death: number | null;
image: string; image: string;
genre: null; genre: null;
description: string; description: string;
permalink?: string; permalink?: string;
background?: string; background?: string;
} }
export interface SList { export interface SList {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
name: string; name: string;
age_restriction?: number; age_restriction?: number;
} }
export interface TagsList { export interface TagsList {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
name: string; name: string;
position: number; position: number;
status: boolean; status: boolean;
} }

View file

@ -1,41 +1,41 @@
export interface AnimeOnegaiStream { export interface AnimeOnegaiStream {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
name: string; name: string;
source_url: string; source_url: string;
backup_url: string; backup_url: string;
live: boolean; live: boolean;
token_handler: number; token_handler: number;
entry: string; entry: string;
job: string; job: string;
drm: boolean; drm: boolean;
transcoding_content_id: string; transcoding_content_id: string;
transcoding_asset_id: string; transcoding_asset_id: string;
status: number; status: number;
thumbnail: string; thumbnail: string;
hls: string; hls: string;
dash: string; dash: string;
widevine_proxy: string; widevine_proxy: string;
playready_proxy: string; playready_proxy: string;
apple_licence: string; apple_licence: string;
apple_certificate: string; apple_certificate: string;
dpath: string; dpath: string;
dbin: string; dbin: string;
subtitles: Subtitle[]; subtitles: Subtitle[];
origin: number; origin: number;
offline_entry: string; offline_entry: string;
offline_status: boolean; offline_status: boolean;
} }
export interface Subtitle { export interface Subtitle {
ID: number; ID: number;
CreatedAt: Date; CreatedAt: Date;
UpdatedAt: Date; UpdatedAt: Date;
DeletedAt: null; DeletedAt: null;
name: string; name: string;
lang: string; lang: string;
entry_id: string; entry_id: string;
url: string; url: string;
} }

View file

@ -1,136 +1,136 @@
import { Images } from './crunchyEpisodeList'; import { Images } from './crunchyEpisodeList';
export interface CrunchyAndroidEpisodes { export interface CrunchyAndroidEpisodes {
__class__: string; __class__: string;
__href__: string; __href__: string;
__resource_key__: string; __resource_key__: string;
__links__: object; __links__: object;
__actions__: object; __actions__: object;
total: number; total: number;
items: CrunchyAndroidEpisode[]; items: CrunchyAndroidEpisode[];
} }
export interface CrunchyAndroidEpisode { export interface CrunchyAndroidEpisode {
__class__: string; __class__: string;
__href__: string; __href__: string;
__resource_key__: string; __resource_key__: string;
__links__: Links; __links__: Links;
__actions__: Actions; __actions__: Actions;
playback: string; playback: string;
id: string; id: string;
channel_id: ChannelID; channel_id: ChannelID;
series_id: string; series_id: string;
series_title: string; series_title: string;
series_slug_title: string; series_slug_title: string;
season_id: string; season_id: string;
season_title: string; season_title: string;
season_slug_title: string; season_slug_title: string;
season_number: number; season_number: number;
episode: string; episode: string;
episode_number: number; episode_number: number;
sequence_number: number; sequence_number: number;
production_episode_id: string; production_episode_id: string;
title: string; title: string;
slug_title: string; slug_title: string;
description: string; description: string;
next_episode_id: string; next_episode_id: string;
next_episode_title: string; next_episode_title: string;
hd_flag: boolean; hd_flag: boolean;
maturity_ratings: MaturityRating[]; maturity_ratings: MaturityRating[];
extended_maturity_rating: Actions; extended_maturity_rating: Actions;
is_mature: boolean; is_mature: boolean;
mature_blocked: boolean; mature_blocked: boolean;
episode_air_date: Date; episode_air_date: Date;
upload_date: Date; upload_date: Date;
availability_starts: Date; availability_starts: Date;
availability_ends: Date; availability_ends: Date;
eligible_region: string; eligible_region: string;
available_date: Date; available_date: Date;
free_available_date: Date; free_available_date: Date;
premium_date: Date; premium_date: Date;
premium_available_date: Date; premium_available_date: Date;
is_subbed: boolean; is_subbed: boolean;
is_dubbed: boolean; is_dubbed: boolean;
is_clip: boolean; is_clip: boolean;
seo_title: string; seo_title: string;
seo_description: string; seo_description: string;
season_tags: string[]; season_tags: string[];
available_offline: boolean; available_offline: boolean;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
availability_notes: string; availability_notes: string;
audio_locale: Locale; audio_locale: Locale;
versions: Version[]; versions: Version[];
closed_captions_available: boolean; closed_captions_available: boolean;
identifier: string; identifier: string;
media_type: MediaType; media_type: MediaType;
slug: string; slug: string;
images: Images; images: Images;
duration_ms: number; duration_ms: number;
is_premium_only: boolean; is_premium_only: boolean;
listing_id: string; listing_id: string;
hide_season_title?: boolean; hide_season_title?: boolean;
hide_season_number?: boolean; hide_season_number?: boolean;
isSelected?: boolean; isSelected?: boolean;
seq_id: string; seq_id: string;
} }
export interface Links { export interface Links {
'episode/channel': Link; 'episode/channel': Link;
'episode/next_episode': Link; 'episode/next_episode': Link;
'episode/season': Link; 'episode/season': Link;
'episode/series': Link; 'episode/series': Link;
streams: Link; streams: Link;
} }
export interface Link { export interface Link {
href: string; href: string;
} }
export interface Thumbnail { export interface Thumbnail {
width: number; width: number;
height: number; height: number;
type: string; type: string;
source: string; source: string;
} }
export enum Locale { export enum Locale {
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }
export enum MediaType { export enum MediaType {
Episode = 'episode', Episode = 'episode',
} }
export enum ChannelID { export enum ChannelID {
Crunchyroll = 'crunchyroll', Crunchyroll = 'crunchyroll',
} }
export enum MaturityRating { export enum MaturityRating {
Tv14 = 'TV-14', Tv14 = 'TV-14',
} }
export interface Version { export interface Version {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
original: boolean; original: boolean;
variant: string; variant: string;
season_guid: string; season_guid: string;
media_guid: string; media_guid: string;
is_premium_only: boolean; is_premium_only: boolean;
} }

View file

@ -1,186 +1,186 @@
import { ImageType, Images, Image } from './objectInfo'; import { ImageType, Images, Image } from './objectInfo';
export interface CrunchyAndroidObject { export interface CrunchyAndroidObject {
__class__: string; __class__: string;
__href__: string; __href__: string;
__resource_key__: string; __resource_key__: string;
__links__: object; __links__: object;
__actions__: object; __actions__: object;
total: number; total: number;
items: AndroidObject[]; items: AndroidObject[];
} }
export interface AndroidObject { export interface AndroidObject {
__class__: string; __class__: string;
__href__: string; __href__: string;
__links__: Links; __links__: Links;
__actions__: Actions; __actions__: Actions;
id: string; id: string;
external_id: string; external_id: string;
channel_id: string; channel_id: string;
title: string; title: string;
description: string; description: string;
promo_title: string; promo_title: string;
promo_description: string; promo_description: string;
type: string; type: string;
slug: string; slug: string;
slug_title: string; slug_title: string;
images: Images; images: Images;
movie_listing_metadata?: MovieListingMetadata; movie_listing_metadata?: MovieListingMetadata;
movie_metadata?: MovieMetadata; movie_metadata?: MovieMetadata;
playback?: string; playback?: string;
episode_metadata?: EpisodeMetadata; episode_metadata?: EpisodeMetadata;
streams_link?: string; streams_link?: string;
season_metadata?: SeasonMetadata; season_metadata?: SeasonMetadata;
linked_resource_key: string; linked_resource_key: string;
isSelected?: boolean; isSelected?: boolean;
f_num: string; f_num: string;
s_num: string; s_num: string;
} }
export interface Links { export interface Links {
'episode/season': LinkData; 'episode/season': LinkData;
'episode/series': LinkData; 'episode/series': LinkData;
resource: LinkData; resource: LinkData;
'resource/channel': LinkData; 'resource/channel': LinkData;
streams: LinkData; streams: LinkData;
} }
export interface LinkData { export interface LinkData {
href: string; href: string;
} }
export interface EpisodeMetadata { export interface EpisodeMetadata {
audio_locale: Locale; audio_locale: Locale;
availability_ends: Date; availability_ends: Date;
availability_notes: string; availability_notes: string;
availability_starts: Date; availability_starts: Date;
available_date: null; available_date: null;
available_offline: boolean; available_offline: boolean;
closed_captions_available: boolean; closed_captions_available: boolean;
duration_ms: number; duration_ms: number;
eligible_region: string; eligible_region: string;
episode: string; episode: string;
episode_air_date: Date; episode_air_date: Date;
episode_number: number; episode_number: number;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
free_available_date: Date; free_available_date: Date;
identifier: string; identifier: string;
is_clip: boolean; is_clip: boolean;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
premium_available_date: Date; premium_available_date: Date;
premium_date: null; premium_date: null;
season_id: string; season_id: string;
season_number: number; season_number: number;
season_slug_title: string; season_slug_title: string;
season_title: string; season_title: string;
sequence_number: number; sequence_number: number;
series_id: string; series_id: string;
series_slug_title: string; series_slug_title: string;
series_title: string; series_title: string;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
tenant_categories?: string[]; tenant_categories?: string[];
upload_date: Date; upload_date: Date;
versions: EpisodeMetadataVersion[]; versions: EpisodeMetadataVersion[];
} }
export interface MovieListingMetadata { export interface MovieListingMetadata {
availability_notes: string; availability_notes: string;
available_date: null; available_date: null;
available_offline: boolean; available_offline: boolean;
duration_ms: number; duration_ms: number;
extended_description: string; extended_description: string;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
first_movie_id: string; first_movie_id: string;
free_available_date: Date; free_available_date: Date;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
movie_release_year: number; movie_release_year: number;
premium_available_date: Date; premium_available_date: Date;
premium_date: null; premium_date: null;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
tenant_categories: string[]; tenant_categories: string[];
} }
export interface MovieMetadata { export interface MovieMetadata {
availability_notes: string; availability_notes: string;
available_offline: boolean; available_offline: boolean;
closed_captions_available: boolean; closed_captions_available: boolean;
duration_ms: number; duration_ms: number;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
movie_listing_id: string; movie_listing_id: string;
movie_listing_slug_title: string; movie_listing_slug_title: string;
movie_listing_title: string; movie_listing_title: string;
} }
export interface SeasonMetadata { export interface SeasonMetadata {
audio_locale: Locale; audio_locale: Locale;
audio_locales: Locale[]; audio_locales: Locale[];
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
identifier: string; identifier: string;
is_mature: boolean; is_mature: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
season_display_number: string; season_display_number: string;
season_sequence_number: number; season_sequence_number: number;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
versions: SeasonMetadataVersion[]; versions: SeasonMetadataVersion[];
} }
export interface SeasonMetadataVersion { export interface SeasonMetadataVersion {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
original: boolean; original: boolean;
variant: string; variant: string;
} }
export interface SeriesMetadata { export interface SeriesMetadata {
audio_locales: Locale[]; audio_locales: Locale[];
availability_notes: string; availability_notes: string;
episode_count: number; episode_count: number;
extended_description: string; extended_description: string;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_simulcast: boolean; is_simulcast: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
season_count: number; season_count: number;
series_launch_year: number; series_launch_year: number;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
tenant_categories?: string[]; tenant_categories?: string[];
} }
export enum Locale { export enum Locale {
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }

View file

@ -1,93 +1,93 @@
export interface CrunchyAndroidStreams { export interface CrunchyAndroidStreams {
__class__: string; __class__: string;
__href__: string; __href__: string;
__resource_key__: string; __resource_key__: string;
__links__: Links; __links__: Links;
__actions__: Record<unknown, unknown>; __actions__: Record<unknown, unknown>;
media_id: string; media_id: string;
audio_locale: Locale; audio_locale: Locale;
subtitles: Subtitles; subtitles: Subtitles;
closed_captions: Subtitles; closed_captions: Subtitles;
streams: Streams; streams: Streams;
bifs: string[]; bifs: string[];
versions: Version[]; versions: Version[];
captions: Record<unknown, unknown>; captions: Record<unknown, unknown>;
} }
export interface Subtitles { export interface Subtitles {
'': Subtitle; '': Subtitle;
'en-US'?: Subtitle; 'en-US'?: Subtitle;
'es-LA'?: Subtitle; 'es-LA'?: Subtitle;
'es-419'?: Subtitle; 'es-419'?: Subtitle;
'es-ES'?: Subtitle; 'es-ES'?: Subtitle;
'pt-BR'?: Subtitle; 'pt-BR'?: Subtitle;
'fr-FR'?: Subtitle; 'fr-FR'?: Subtitle;
'de-DE'?: Subtitle; 'de-DE'?: Subtitle;
'ar-ME'?: Subtitle; 'ar-ME'?: Subtitle;
'ar-SA'?: Subtitle; 'ar-SA'?: Subtitle;
'it-IT'?: Subtitle; 'it-IT'?: Subtitle;
'ru-RU'?: Subtitle; 'ru-RU'?: Subtitle;
'tr-TR'?: Subtitle; 'tr-TR'?: Subtitle;
'hi-IN'?: Subtitle; 'hi-IN'?: Subtitle;
'zh-CN'?: Subtitle; 'zh-CN'?: Subtitle;
'ko-KR'?: Subtitle; 'ko-KR'?: Subtitle;
'ja-JP'?: Subtitle; 'ja-JP'?: Subtitle;
} }
export interface Links { export interface Links {
resource: Resource; resource: Resource;
} }
export interface Resource { export interface Resource {
href: string; href: string;
} }
export interface Streams { export interface Streams {
[key: string]: { [key: string]: Download }; [key: string]: { [key: string]: Download };
} }
export interface Download { export interface Download {
hardsub_locale: Locale; hardsub_locale: Locale;
hardsub_lang?: string; hardsub_lang?: string;
url: string; url: string;
} }
export interface Urls { export interface Urls {
'': Download; '': Download;
} }
export interface Subtitle { export interface Subtitle {
locale: Locale; locale: Locale;
url: string; url: string;
format: string; format: string;
} }
export interface Version { export interface Version {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
original: boolean; original: boolean;
variant: string; variant: string;
season_guid: string; season_guid: string;
media_guid: string; media_guid: string;
is_premium_only: boolean; is_premium_only: boolean;
} }
export enum Locale { export enum Locale {
default = '', default = '',
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }

View file

@ -1,134 +1,134 @@
import { Links } from './crunchyAndroidEpisodes'; import { Links } from './crunchyAndroidEpisodes';
export interface CrunchyEpisodeList { export interface CrunchyEpisodeList {
total: number; total: number;
data: CrunchyEpisode[]; data: CrunchyEpisode[];
meta: Meta; meta: Meta;
} }
export interface CrunchyEpisode { export interface CrunchyEpisode {
next_episode_id: string; next_episode_id: string;
series_id: string; series_id: string;
season_number: number; season_number: number;
next_episode_title: string; next_episode_title: string;
availability_notes: string; availability_notes: string;
duration_ms: number; duration_ms: number;
series_slug_title: string; series_slug_title: string;
series_title: string; series_title: string;
is_dubbed: boolean; is_dubbed: boolean;
versions: Version[] | null; versions: Version[] | null;
identifier: string; identifier: string;
sequence_number: number; sequence_number: number;
eligible_region: Record<unknown>; eligible_region: Record<unknown>;
availability_starts: Date; availability_starts: Date;
images: Images; images: Images;
season_id: string; season_id: string;
seo_title: string; seo_title: string;
is_premium_only: boolean; is_premium_only: boolean;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
title: string; title: string;
production_episode_id: string; production_episode_id: string;
premium_available_date: Date; premium_available_date: Date;
season_title: string; season_title: string;
seo_description: string; seo_description: string;
audio_locale: Locale; audio_locale: Locale;
id: string; id: string;
media_type: MediaType; media_type: MediaType;
availability_ends: Date; availability_ends: Date;
free_available_date: Date; free_available_date: Date;
playback: string; playback: string;
channel_id: ChannelID; channel_id: ChannelID;
episode: string; episode: string;
is_mature: boolean; is_mature: boolean;
listing_id: string; listing_id: string;
episode_air_date: Date; episode_air_date: Date;
slug: string; slug: string;
available_date: Date; available_date: Date;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
slug_title: string; slug_title: string;
available_offline: boolean; available_offline: boolean;
description: string; description: string;
is_subbed: boolean; is_subbed: boolean;
premium_date: Date; premium_date: Date;
upload_date: Date; upload_date: Date;
season_slug_title: string; season_slug_title: string;
closed_captions_available: boolean; closed_captions_available: boolean;
episode_number: number; episode_number: number;
season_tags: any[]; season_tags: any[];
maturity_ratings: MaturityRating[]; maturity_ratings: MaturityRating[];
streams_link?: string; streams_link?: string;
mature_blocked: boolean; mature_blocked: boolean;
is_clip: boolean; is_clip: boolean;
hd_flag: boolean; hd_flag: boolean;
hide_season_title?: boolean; hide_season_title?: boolean;
hide_season_number?: boolean; hide_season_number?: boolean;
isSelected?: boolean; isSelected?: boolean;
seq_id: string; seq_id: string;
__links__?: Links; __links__?: Links;
} }
export enum Locale { export enum Locale {
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }
export enum ChannelID { export enum ChannelID {
Crunchyroll = 'crunchyroll', Crunchyroll = 'crunchyroll',
} }
export interface Images { export interface Images {
poster_tall?: Array<Image[]>; poster_tall?: Array<Image[]>;
poster_wide?: Array<Image[]>; poster_wide?: Array<Image[]>;
promo_image?: Array<Image[]>; promo_image?: Array<Image[]>;
thumbnail?: Array<Image[]>; thumbnail?: Array<Image[]>;
} }
export interface Image { export interface Image {
height: number; height: number;
source: string; source: string;
type: ImageType; type: ImageType;
width: number; width: number;
} }
export enum ImageType { export enum ImageType {
PosterTall = 'poster_tall', PosterTall = 'poster_tall',
PosterWide = 'poster_wide', PosterWide = 'poster_wide',
PromoImage = 'promo_image', PromoImage = 'promo_image',
Thumbnail = 'thumbnail', Thumbnail = 'thumbnail',
} }
export enum MaturityRating { export enum MaturityRating {
Tv14 = 'TV-14', Tv14 = 'TV-14',
} }
export enum MediaType { export enum MediaType {
Episode = 'episode', Episode = 'episode',
} }
export interface Version { export interface Version {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
is_premium_only: boolean; is_premium_only: boolean;
media_guid: string; media_guid: string;
original: boolean; original: boolean;
season_guid: string; season_guid: string;
variant: string; variant: string;
} }
export interface Meta { export interface Meta {
versions_considered?: boolean; versions_considered?: boolean;
} }

View file

@ -1,183 +1,183 @@
// Generated by https://quicktype.io // Generated by https://quicktype.io
export interface CrunchySearch { export interface CrunchySearch {
total: number; total: number;
data: CrunchySearchData[]; data: CrunchySearchData[];
meta: Record<string, unknown>; meta: Record<string, unknown>;
} }
export interface CrunchySearchData { export interface CrunchySearchData {
type: string; type: string;
count: number; count: number;
items: CrunchySearchItem[]; items: CrunchySearchItem[];
} }
export interface CrunchySearchItem { export interface CrunchySearchItem {
title: string; title: string;
images: Images; images: Images;
series_metadata?: SeriesMetadata; series_metadata?: SeriesMetadata;
promo_description: string; promo_description: string;
external_id: string; external_id: string;
slug: string; slug: string;
new: boolean; new: boolean;
slug_title: string; slug_title: string;
channel_id: ChannelID; channel_id: ChannelID;
description: string; description: string;
linked_resource_key: string; linked_resource_key: string;
type: ItemType; type: ItemType;
id: string; id: string;
promo_title: string; promo_title: string;
search_metadata: SearchMetadata; search_metadata: SearchMetadata;
movie_listing_metadata?: MovieListingMetadata; movie_listing_metadata?: MovieListingMetadata;
playback?: string; playback?: string;
streams_link?: string; streams_link?: string;
episode_metadata?: EpisodeMetadata; episode_metadata?: EpisodeMetadata;
} }
export enum ChannelID { export enum ChannelID {
Crunchyroll = 'crunchyroll', Crunchyroll = 'crunchyroll',
} }
export interface EpisodeMetadata { export interface EpisodeMetadata {
audio_locale: Locale; audio_locale: Locale;
availability_ends: Date; availability_ends: Date;
availability_notes: string; availability_notes: string;
availability_starts: Date; availability_starts: Date;
available_date: null; available_date: null;
available_offline: boolean; available_offline: boolean;
closed_captions_available: boolean; closed_captions_available: boolean;
duration_ms: number; duration_ms: number;
eligible_region: string[]; eligible_region: string[];
episode: string; episode: string;
episode_air_date: Date; episode_air_date: Date;
episode_number: number; episode_number: number;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
free_available_date: Date; free_available_date: Date;
identifier: string; identifier: string;
is_clip: boolean; is_clip: boolean;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: MaturityRating[]; maturity_ratings: MaturityRating[];
premium_available_date: Date; premium_available_date: Date;
premium_date: null; premium_date: null;
season_id: string; season_id: string;
season_number: number; season_number: number;
season_slug_title: string; season_slug_title: string;
season_title: string; season_title: string;
sequence_number: number; sequence_number: number;
series_id: string; series_id: string;
series_slug_title: string; series_slug_title: string;
series_title: string; series_title: string;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
upload_date: Date; upload_date: Date;
versions: Version[] | null; versions: Version[] | null;
tenant_categories?: string[]; tenant_categories?: string[];
} }
export enum Locale { export enum Locale {
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }
export enum MaturityRating { export enum MaturityRating {
Tv14 = 'TV-14', Tv14 = 'TV-14',
TvMa = 'TV-MA', TvMa = 'TV-MA',
} }
export interface Version { export interface Version {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
is_premium_only: boolean; is_premium_only: boolean;
media_guid: string; media_guid: string;
original: boolean; original: boolean;
season_guid: string; season_guid: string;
variant: string; variant: string;
} }
export interface Images { export interface Images {
poster_tall?: Array<Image[]>; poster_tall?: Array<Image[]>;
poster_wide?: Array<Image[]>; poster_wide?: Array<Image[]>;
promo_image?: Array<Image[]>; promo_image?: Array<Image[]>;
thumbnail?: Array<Image[]>; thumbnail?: Array<Image[]>;
} }
export interface Image { export interface Image {
height: number; height: number;
source: string; source: string;
type: ImageType; type: ImageType;
width: number; width: number;
} }
export enum ImageType { export enum ImageType {
PosterTall = 'poster_tall', PosterTall = 'poster_tall',
PosterWide = 'poster_wide', PosterWide = 'poster_wide',
PromoImage = 'promo_image', PromoImage = 'promo_image',
Thumbnail = 'thumbnail', Thumbnail = 'thumbnail',
} }
export interface MovieListingMetadata { export interface MovieListingMetadata {
availability_notes: string; availability_notes: string;
available_date: null; available_date: null;
available_offline: boolean; available_offline: boolean;
duration_ms: number; duration_ms: number;
extended_description: string; extended_description: string;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
first_movie_id: string; first_movie_id: string;
free_available_date: Date; free_available_date: Date;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
movie_release_year: number; movie_release_year: number;
premium_available_date: Date; premium_available_date: Date;
premium_date: null; premium_date: null;
subtitle_locales: any[]; subtitle_locales: any[];
tenant_categories: string[]; tenant_categories: string[];
} }
export interface SearchMetadata { export interface SearchMetadata {
score: number; score: number;
} }
export interface SeriesMetadata { export interface SeriesMetadata {
audio_locales: Locale[]; audio_locales: Locale[];
availability_notes: string; availability_notes: string;
episode_count: number; episode_count: number;
extended_description: string; extended_description: string;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_simulcast: boolean; is_simulcast: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: MaturityRating[]; maturity_ratings: MaturityRating[];
season_count: number; season_count: number;
series_launch_year: number; series_launch_year: number;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
tenant_categories?: string[]; tenant_categories?: string[];
} }
export enum ItemType { export enum ItemType {
Episode = 'episode', Episode = 'episode',
MovieListing = 'movie_listing', MovieListing = 'movie_listing',
Series = 'series', Series = 'series',
} }

View file

@ -5,211 +5,211 @@ import { DownloadInfo } from './messageHandler';
import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from './enums'; import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from './enums';
export type CrunchyDownloadOptions = { export type CrunchyDownloadOptions = {
hslang: string, hslang: string,
// kstream: number, // kstream: number,
cstream: keyof typeof CrunchyVideoPlayStreams, cstream: keyof typeof CrunchyVideoPlayStreams,
vstream: keyof typeof CrunchyVideoPlayStreams, vstream: keyof typeof CrunchyVideoPlayStreams,
astream: keyof typeof CrunchyAudioPlayStreams, astream: keyof typeof CrunchyAudioPlayStreams,
tsd?: boolean, tsd?: boolean,
novids?: boolean, novids?: boolean,
noaudio?: boolean, noaudio?: boolean,
x: number, x: number,
q: number, q: number,
fileName: string, fileName: string,
numbers: number, numbers: number,
partsize: number, partsize: number,
callbackMaker?: (data: DownloadInfo) => HLSCallback, callbackMaker?: (data: DownloadInfo) => HLSCallback,
timeout: number, timeout: number,
waittime: number, waittime: number,
fsRetryTime: number, fsRetryTime: number,
dlsubs: string[], dlsubs: string[],
skipsubs: boolean, skipsubs: boolean,
nosubs?: boolean, nosubs?: boolean,
mp4: boolean, mp4: boolean,
override: string[], override: string[],
videoTitle: string, videoTitle: string,
force: 'Y'|'y'|'N'|'n'|'C'|'c', force: 'Y'|'y'|'N'|'n'|'C'|'c',
ffmpegOptions: string[], ffmpegOptions: string[],
mkvmergeOptions: string[], mkvmergeOptions: string[],
defaultSub: LanguageItem, defaultSub: LanguageItem,
defaultAudio: LanguageItem, defaultAudio: LanguageItem,
ccTag: string, ccTag: string,
dlVideoOnce: boolean, dlVideoOnce: boolean,
skipmux?: boolean, skipmux?: boolean,
syncTiming: boolean, syncTiming: boolean,
nocleanup: boolean, nocleanup: boolean,
chapters: boolean, chapters: boolean,
fontName: string | undefined, fontName: string | undefined,
originalFontSize: boolean, originalFontSize: boolean,
fontSize: number, fontSize: number,
dubLang: string[], dubLang: string[],
} }
export type CrunchyMultiDownload = { export type CrunchyMultiDownload = {
absolute?: boolean, absolute?: boolean,
dubLang: string[], dubLang: string[],
all?: boolean, all?: boolean,
but?: boolean, but?: boolean,
e?: string, e?: string,
s?: string s?: string
} }
export type CrunchyMuxOptions = { export type CrunchyMuxOptions = {
output: string, output: string,
skipSubMux?: boolean skipSubMux?: boolean
keepAllVideos?: bolean keepAllVideos?: bolean
novids?: boolean, novids?: boolean,
mp4: boolean, mp4: boolean,
forceMuxer?: 'ffmpeg'|'mkvmerge', forceMuxer?: 'ffmpeg'|'mkvmerge',
nocleanup?: boolean, nocleanup?: boolean,
videoTitle: string, videoTitle: string,
ffmpegOptions: string[], ffmpegOptions: string[],
mkvmergeOptions: string[], mkvmergeOptions: string[],
defaultSub: LanguageItem, defaultSub: LanguageItem,
defaultAudio: LanguageItem, defaultAudio: LanguageItem,
ccTag: string, ccTag: string,
syncTiming: boolean, syncTiming: boolean,
} }
export type CrunchyEpMeta = { export type CrunchyEpMeta = {
data: { data: {
mediaId: string, mediaId: string,
lang?: LanguageItem, lang?: LanguageItem,
playback?: string, playback?: string,
versions?: EpisodeVersion[] | null, versions?: EpisodeVersion[] | null,
isSubbed: boolean, isSubbed: boolean,
isDubbed: boolean, isDubbed: boolean,
}[], }[],
seriesTitle: string, seriesTitle: string,
seasonTitle: string, seasonTitle: string,
episodeNumber: string, episodeNumber: string,
episodeTitle: string, episodeTitle: string,
seasonID: string, seasonID: string,
season: number, season: number,
showID: string, showID: string,
e: string, e: string,
image: string, image: string,
} }
export type DownloadedMedia = { export type DownloadedMedia = {
type: 'Video', type: 'Video',
lang: LanguageItem, lang: LanguageItem,
path: string, path: string,
isPrimary?: boolean isPrimary?: boolean
} | { } | {
type: 'Audio', type: 'Audio',
lang: LanguageItem, lang: LanguageItem,
path: string, path: string,
isPrimary?: boolean isPrimary?: boolean
} | { } | {
type: 'Chapters', type: 'Chapters',
lang: LanguageItem, lang: LanguageItem,
path: string path: string
} | ({ } | ({
type: 'Subtitle', type: 'Subtitle',
signs: boolean, signs: boolean,
cc: boolean cc: boolean
} & sxItem ) } & sxItem )
export type ParseItem = { export type ParseItem = {
__class__?: string; __class__?: string;
isSelected?: boolean, isSelected?: boolean,
type?: string, type?: string,
id: string, id: string,
title: string, title: string,
playback?: string, playback?: string,
season_number?: number|string, season_number?: number|string,
episode_number?: number|string, episode_number?: number|string,
season_count?: number|string, season_count?: number|string,
is_premium_only?: boolean, is_premium_only?: boolean,
hide_metadata?: boolean, hide_metadata?: boolean,
seq_id?: string, seq_id?: string,
f_num?: string, f_num?: string,
s_num?: string s_num?: string
external_id?: string, external_id?: string,
ep_num?: string ep_num?: string
last_public?: string, last_public?: string,
subtitle_locales?: string[], subtitle_locales?: string[],
availability_notes?: string, availability_notes?: string,
identifier?: string, identifier?: string,
versions?: Version[] | null, versions?: Version[] | null,
media_type?: string | null, media_type?: string | null,
movie_release_year?: number | null, movie_release_year?: number | null,
} }
export interface SeriesSearch { export interface SeriesSearch {
total: number; total: number;
data: SeriesSearchItem[]; data: SeriesSearchItem[];
meta: Meta; meta: Meta;
} }
export interface SeriesSearchItem { export interface SeriesSearchItem {
description: string; description: string;
seo_description: string; seo_description: string;
number_of_episodes: number; number_of_episodes: number;
is_dubbed: boolean; is_dubbed: boolean;
identifier: string; identifier: string;
channel_id: string; channel_id: string;
slug_title: string; slug_title: string;
season_sequence_number: number; season_sequence_number: number;
season_tags: string[]; season_tags: string[];
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
is_mature: boolean; is_mature: boolean;
audio_locale: string; audio_locale: string;
season_number: number; season_number: number;
images: Record<unknown>; images: Record<unknown>;
mature_blocked: boolean; mature_blocked: boolean;
versions: Version[]; versions: Version[];
title: string; title: string;
is_subbed: boolean; is_subbed: boolean;
id: string; id: string;
audio_locales: string[]; audio_locales: string[];
subtitle_locales: string[]; subtitle_locales: string[];
availability_notes: string; availability_notes: string;
series_id: string; series_id: string;
season_display_number: string; season_display_number: string;
is_complete: boolean; is_complete: boolean;
keywords: any[]; keywords: any[];
maturity_ratings: string[]; maturity_ratings: string[];
is_simulcast: boolean; is_simulcast: boolean;
seo_title: string; seo_title: string;
} }
export interface Version { export interface Version {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
original: boolean; original: boolean;
variant: string; variant: string;
} }
export interface EpisodeVersion { export interface EpisodeVersion {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
is_premium_only: boolean; is_premium_only: boolean;
media_guid: string; media_guid: string;
original: boolean; original: boolean;
season_guid: string; season_guid: string;
variant: string; variant: string;
} }
export enum Locale { export enum Locale {
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }
export interface Meta { export interface Meta {
versions_considered: boolean; versions_considered: boolean;
} }

View file

@ -1,6 +1,6 @@
import { LanguageItem } from '../modules/module.langsData'; import { LanguageItem } from '../modules/module.langsData';
export type DownloadedFile = { export type DownloadedFile = {
path: string, path: string,
lang: LanguageItem lang: LanguageItem
} }

View file

@ -1,11 +1,11 @@
export enum CrunchyVideoPlayStreams { export enum CrunchyVideoPlayStreams {
'androidtv' = 'tv/android_tv', 'androidtv' = 'tv/android_tv',
'android' = 'android/phone', 'android' = 'android/phone',
'androidtab'= 'android/tablet' 'androidtab'= 'android/tablet'
} }
export enum CrunchyAudioPlayStreams { export enum CrunchyAudioPlayStreams {
'androidtv' = 'tv/android_tv', 'androidtv' = 'tv/android_tv',
'android' = 'android/phone', 'android' = 'android/phone',
'androidtab'= 'android/tablet' 'androidtab'= 'android/tablet'
} }

View file

@ -1,70 +1,70 @@
export interface HidiveDashboard { export interface HidiveDashboard {
Code: number; Code: number;
Status: string; Status: string;
Message: null; Message: null;
Messages: object; Messages: object;
Data: Data; Data: Data;
Timestamp: string; Timestamp: string;
IPAddress: string; IPAddress: string;
} }
export interface Data { export interface Data {
TitleRows: TitleRow[]; TitleRows: TitleRow[];
LoadTime: number; LoadTime: number;
} }
export interface TitleRow { export interface TitleRow {
Name: string; Name: string;
Titles: Title[]; Titles: Title[];
LoadTime: number; LoadTime: number;
} }
export interface Title { export interface Title {
Id: number; Id: number;
Name: string; Name: string;
ShortSynopsis: string; ShortSynopsis: string;
MediumSynopsis: string; MediumSynopsis: string;
LongSynopsis: string; LongSynopsis: string;
KeyArtUrl: string; KeyArtUrl: string;
MasterArtUrl: string; MasterArtUrl: string;
Rating: null | string; Rating: null | string;
OverallRating: number; OverallRating: number;
RatingCount: number; RatingCount: number;
MALScore: null; MALScore: null;
UserRating: number; UserRating: number;
RunTime: number | null; RunTime: number | null;
ShowInfoTitle: string; ShowInfoTitle: string;
FirstPremiereDate: Date; FirstPremiereDate: Date;
EpisodeCount: number; EpisodeCount: number;
SeasonName: string; SeasonName: string;
RokuHDArtUrl: string; RokuHDArtUrl: string;
RokuSDArtUrl: string; RokuSDArtUrl: string;
IsRateable: boolean; IsRateable: boolean;
InQueue: boolean; InQueue: boolean;
IsFavorite: boolean; IsFavorite: boolean;
IsContinueWatching: boolean; IsContinueWatching: boolean;
ContinueWatching: ContinueWatching; ContinueWatching: ContinueWatching;
Episodes: any[]; Episodes: any[];
LoadTime: number; LoadTime: number;
} }
export interface ContinueWatching { export interface ContinueWatching {
Id: string; Id: string;
ProfileId: number; ProfileId: number;
EpisodeId: number; EpisodeId: number;
Status: Status | null; Status: Status | null;
CurrentTime: number; CurrentTime: number;
UserId: number; UserId: number;
TitleId: number; TitleId: number;
SeasonId: number; SeasonId: number;
VideoId: number; VideoId: number;
TotalSeconds: number; TotalSeconds: number;
CreatedDT: Date; CreatedDT: Date;
ModifiedDT: Date | null; ModifiedDT: Date | null;
} }
export enum Status { export enum Status {
Paused = 'Paused', Paused = 'Paused',
Playing = 'Playing', Playing = 'Playing',
Watching = 'Watching', Watching = 'Watching',
} }

View file

@ -1,84 +1,84 @@
export interface HidiveEpisodeList { export interface HidiveEpisodeList {
Code: number; Code: number;
Status: string; Status: string;
Message: null; Message: null;
Messages: Record<unknown, unknown>; Messages: Record<unknown, unknown>;
Data: Data; Data: Data;
Timestamp: string; Timestamp: string;
IPAddress: string; IPAddress: string;
} }
export interface Data { export interface Data {
Title: HidiveTitle; Title: HidiveTitle;
} }
export interface HidiveTitle { export interface HidiveTitle {
Id: number; Id: number;
Name: string; Name: string;
ShortSynopsis: string; ShortSynopsis: string;
MediumSynopsis: string; MediumSynopsis: string;
LongSynopsis: string; LongSynopsis: string;
KeyArtUrl: string; KeyArtUrl: string;
MasterArtUrl: string; MasterArtUrl: string;
Rating: string; Rating: string;
OverallRating: number; OverallRating: number;
RatingCount: number; RatingCount: number;
MALScore: null; MALScore: null;
UserRating: number; UserRating: number;
RunTime: number; RunTime: number;
ShowInfoTitle: string; ShowInfoTitle: string;
FirstPremiereDate: Date; FirstPremiereDate: Date;
EpisodeCount: number; EpisodeCount: number;
SeasonName: string; SeasonName: string;
RokuHDArtUrl: string; RokuHDArtUrl: string;
RokuSDArtUrl: string; RokuSDArtUrl: string;
IsRateable: boolean; IsRateable: boolean;
InQueue: boolean; InQueue: boolean;
IsFavorite: boolean; IsFavorite: boolean;
IsContinueWatching: boolean; IsContinueWatching: boolean;
ContinueWatching: ContinueWatching; ContinueWatching: ContinueWatching;
Episodes: HidiveEpisode[]; Episodes: HidiveEpisode[];
LoadTime: number; LoadTime: number;
} }
export interface ContinueWatching { export interface ContinueWatching {
Id: string; Id: string;
ProfileId: number; ProfileId: number;
EpisodeId: number; EpisodeId: number;
Status: string; Status: string;
CurrentTime: number; CurrentTime: number;
UserId: number; UserId: number;
TitleId: number; TitleId: number;
SeasonId: number; SeasonId: number;
VideoId: number; VideoId: number;
TotalSeconds: number; TotalSeconds: number;
CreatedDT: Date; CreatedDT: Date;
ModifiedDT: Date; ModifiedDT: Date;
} }
export interface HidiveEpisode { export interface HidiveEpisode {
Id: number; Id: number;
Number: number; Number: number;
Name: string; Name: string;
Summary: string; Summary: string;
HIDIVEPremiereDate: Date; HIDIVEPremiereDate: Date;
ScreenShotSmallUrl: string; ScreenShotSmallUrl: string;
ScreenShotCompressedUrl: string; ScreenShotCompressedUrl: string;
SeasonNumber: number; SeasonNumber: number;
TitleId: number; TitleId: number;
SeasonNumberValue: number; SeasonNumberValue: number;
EpisodeNumberValue: number; EpisodeNumberValue: number;
VideoKey: string; VideoKey: string;
DisplayNameLong: string; DisplayNameLong: string;
PercentProgress: number; PercentProgress: number;
LoadTime: number; LoadTime: number;
} }
export interface HidiveEpisodeExtra extends HidiveEpisode { export interface HidiveEpisodeExtra extends HidiveEpisode {
titleId: number; titleId: number;
epKey: string; epKey: string;
nameLong: string; nameLong: string;
seriesTitle: string; seriesTitle: string;
seriesId?: number; seriesId?: number;
isSelected: boolean; isSelected: boolean;
} }

View file

@ -1,47 +1,47 @@
export interface HidiveSearch { export interface HidiveSearch {
Code: number; Code: number;
Status: string; Status: string;
Message: null; Message: null;
Messages: Record<unknown, unknown>; Messages: Record<unknown, unknown>;
Data: HidiveSearchData; Data: HidiveSearchData;
Timestamp: string; Timestamp: string;
IPAddress: string; IPAddress: string;
} }
export interface HidiveSearchData { export interface HidiveSearchData {
Query: string; Query: string;
Slug: string; Slug: string;
TitleResults: HidiveSearchItem[]; TitleResults: HidiveSearchItem[];
SearchId: number; SearchId: number;
IsSearchPinned: boolean; IsSearchPinned: boolean;
IsPinnedSearchAvailable: boolean; IsPinnedSearchAvailable: boolean;
} }
export interface HidiveSearchItem { export interface HidiveSearchItem {
Id: number; Id: number;
Name: string; Name: string;
ShortSynopsis: string; ShortSynopsis: string;
MediumSynopsis: string; MediumSynopsis: string;
LongSynopsis: string; LongSynopsis: string;
KeyArtUrl: string; KeyArtUrl: string;
MasterArtUrl: string; MasterArtUrl: string;
Rating: string; Rating: string;
OverallRating: number; OverallRating: number;
RatingCount: number; RatingCount: number;
MALScore: null; MALScore: null;
UserRating: number; UserRating: number;
RunTime: number | null; RunTime: number | null;
ShowInfoTitle: string; ShowInfoTitle: string;
FirstPremiereDate: Date; FirstPremiereDate: Date;
EpisodeCount: number; EpisodeCount: number;
SeasonName: string; SeasonName: string;
RokuHDArtUrl: string; RokuHDArtUrl: string;
RokuSDArtUrl: string; RokuSDArtUrl: string;
IsRateable: boolean; IsRateable: boolean;
InQueue: boolean; InQueue: boolean;
IsFavorite: boolean; IsFavorite: boolean;
IsContinueWatching: boolean; IsContinueWatching: boolean;
ContinueWatching: null; ContinueWatching: null;
Episodes: any[]; Episodes: any[];
LoadTime: number; LoadTime: number;
} }

View file

@ -1,61 +1,61 @@
export interface HidiveVideoList { export interface HidiveVideoList {
Code: number; Code: number;
Status: string; Status: string;
Message: null; Message: null;
Messages: Record<unknown, unknown>; Messages: Record<unknown, unknown>;
Data: HidiveVideo; Data: HidiveVideo;
Timestamp: string; Timestamp: string;
IPAddress: string; IPAddress: string;
} }
export interface HidiveVideo { export interface HidiveVideo {
ShowAds: boolean; ShowAds: boolean;
CaptionCssUrl: string; CaptionCssUrl: string;
FontSize: number; FontSize: number;
FontScale: number; FontScale: number;
CaptionLanguages: string[]; CaptionLanguages: string[];
CaptionLanguage: string; CaptionLanguage: string;
CaptionVttUrls: Record<string, string>; CaptionVttUrls: Record<string, string>;
VideoLanguages: string[]; VideoLanguages: string[];
VideoLanguage: string; VideoLanguage: string;
VideoUrls: Record<string, HidiveStreamList>; VideoUrls: Record<string, HidiveStreamList>;
FontColorName: string; FontColorName: string;
AutoPlayNextEpisode: boolean; AutoPlayNextEpisode: boolean;
MaxStreams: number; MaxStreams: number;
CurrentTime: number; CurrentTime: number;
FontColorCode: string; FontColorCode: string;
RunTime: number; RunTime: number;
AdUrl: null; AdUrl: null;
} }
export interface HidiveStreamList { export interface HidiveStreamList {
hls: string[]; hls: string[];
drm: string[]; drm: string[];
drmEnabled: boolean; drmEnabled: boolean;
} }
export interface HidiveStreamInfo extends HidiveStreamList { export interface HidiveStreamInfo extends HidiveStreamList {
language?: string; language?: string;
episodeTitle?: string; episodeTitle?: string;
seriesTitle?: string; seriesTitle?: string;
season?: number; season?: number;
episodeNumber?: number; episodeNumber?: number;
uncut?: boolean; uncut?: boolean;
image?: string; image?: string;
} }
export interface HidiveSubtitleInfo { export interface HidiveSubtitleInfo {
language: string; language: string;
cc: boolean; cc: boolean;
url: string; url: string;
} }
export type DownloadedMedia = { export type DownloadedMedia = {
type: 'Video', type: 'Video',
lang: LanguageItem, lang: LanguageItem,
path: string, path: string,
uncut: boolean uncut: boolean
} | ({ } | ({
type: 'Subtitle', type: 'Subtitle',
cc: boolean cc: boolean
} & sxItem ) } & sxItem )

14
@types/iso639.d.ts vendored
View file

@ -1,9 +1,9 @@
declare module 'iso-639' { declare module 'iso-639' {
export type iso639Type = { export type iso639Type = {
[key: string]: { [key: string]: {
'639-1'?: string, '639-1'?: string,
'639-2'?: string '639-2'?: string
} }
} }
export const iso_639_2: iso639Type; export const iso_639_2: iso639Type;
} }

262
@types/items.d.ts vendored
View file

@ -1,169 +1,169 @@
export interface Item { export interface Item {
// Added later // Added later
id: string, id: string,
id_split: (number|string)[] id_split: (number|string)[]
// Added from the start // Added from the start
mostRecentSvodJpnUs: MostRecentSvodJpnUs; mostRecentSvodJpnUs: MostRecentSvodJpnUs;
synopsis: string; synopsis: string;
mediaCategory: ContentType; mediaCategory: ContentType;
mostRecentSvodUsEndTimestamp: number; mostRecentSvodUsEndTimestamp: number;
quality: QualityClass; quality: QualityClass;
genres: Genre[]; genres: Genre[];
titleImages: TitleImages; titleImages: TitleImages;
engAllTerritoryAvail: EngAllTerritoryAvail; engAllTerritoryAvail: EngAllTerritoryAvail;
thumb: string; thumb: string;
mostRecentSvodJpnAllTerrStartTimestamp: number; mostRecentSvodJpnAllTerrStartTimestamp: number;
title: string; title: string;
starRating: number; starRating: number;
primaryAvail: PrimaryAvail; primaryAvail: PrimaryAvail;
access: Access[]; access: Access[];
version: Version[]; version: Version[];
mostRecentSvodJpnAllTerrEndTimestamp: number; mostRecentSvodJpnAllTerrEndTimestamp: number;
itemId: number; itemId: number;
versionAudio: VersionAudio; versionAudio: VersionAudio;
contentType: ContentType; contentType: ContentType;
mostRecentSvodUsStartTimestamp: number; mostRecentSvodUsStartTimestamp: number;
poster: string; poster: string;
mostRecentSvodEngAllTerrEndTimestamp: number; mostRecentSvodEngAllTerrEndTimestamp: number;
mostRecentSvodJpnUsStartTimestamp: number; mostRecentSvodJpnUsStartTimestamp: number;
mostRecentSvodJpnUsEndTimestamp: number; mostRecentSvodJpnUsEndTimestamp: number;
mostRecentSvodStartTimestamp: number; mostRecentSvodStartTimestamp: number;
mostRecentSvod: MostRecent; mostRecentSvod: MostRecent;
altAvail: AltAvail; altAvail: AltAvail;
ids: IDs; ids: IDs;
mostRecentSvodUs: MostRecent; mostRecentSvodUs: MostRecent;
item: Item; item: Item;
mostRecentSvodEngAllTerrStartTimestamp: number; mostRecentSvodEngAllTerrStartTimestamp: number;
audio: string[]; audio: string[];
mostRecentAvod: MostRecent; mostRecentAvod: MostRecent;
} }
export enum ContentType { export enum ContentType {
Episode = 'episode', Episode = 'episode',
Ova = 'ova', Ova = 'ova',
} }
export interface IDs { export interface IDs {
externalShowId: ID; externalShowId: ID;
externalSeasonId: ExternalSeasonID; externalSeasonId: ExternalSeasonID;
externalEpisodeId: string; externalEpisodeId: string;
externalAsianId?: string externalAsianId?: string
} }
export interface Item { export interface Item {
seasonTitle: string; seasonTitle: string;
seasonId: number; seasonId: number;
episodeOrder: number; episodeOrder: number;
episodeSlug: string; episodeSlug: string;
created: Date; created: Date;
titleSlug: string; titleSlug: string;
episodeNum: string; episodeNum: string;
episodeId: number; episodeId: number;
titleId: number; titleId: number;
seasonNum: string; seasonNum: string;
ratings: Array<string[]>; ratings: Array<string[]>;
showImage: string; showImage: string;
titleName: string; titleName: string;
runtime: string; runtime: string;
episodeName: string; episodeName: string;
seasonOrder: number; seasonOrder: number;
titleExternalId: string; titleExternalId: string;
} }
export interface MostRecent { export interface MostRecent {
image?: string; image?: string;
siblingStartTimestamp?: string; siblingStartTimestamp?: string;
devices?: Device[]; devices?: Device[];
availId?: number; availId?: number;
distributor?: Distributor; distributor?: Distributor;
quality?: MostRecentAvodQuality; quality?: MostRecentAvodQuality;
endTimestamp?: string; endTimestamp?: string;
mediaCategory?: ContentType; mediaCategory?: ContentType;
isPromo?: boolean; isPromo?: boolean;
siblingType?: Purchase; siblingType?: Purchase;
version?: Version; version?: Version;
territory?: Territory; territory?: Territory;
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
versionId?: number; versionId?: number;
tier?: Device | null; tier?: Device | null;
purchase?: Purchase; purchase?: Purchase;
startTimestamp?: string; startTimestamp?: string;
language?: Audio; language?: Audio;
itemTitle?: string; itemTitle?: string;
ids?: MostRecentAvodIDS; ids?: MostRecentAvodIDS;
experience?: number; experience?: number;
siblingEndTimestamp?: string; siblingEndTimestamp?: string;
item?: Item; item?: Item;
subscriptionRequired?: boolean; subscriptionRequired?: boolean;
purchased?: boolean; purchased?: boolean;
} }
export interface MostRecentAvodIDS { export interface MostRecentAvodIDS {
externalSeasonId: ExternalSeasonID; externalSeasonId: ExternalSeasonID;
externalAsianId: null; externalAsianId: null;
externalShowId: ID; externalShowId: ID;
externalEpisodeId: string; externalEpisodeId: string;
externalEnglishId: string; externalEnglishId: string;
externalAlphaId: string; externalAlphaId: string;
} }
export enum Purchase { export enum Purchase {
AVOD = 'A-VOD', AVOD = 'A-VOD',
Dfov = 'DFOV', Dfov = 'DFOV',
Est = 'EST', Est = 'EST',
Svod = 'SVOD', Svod = 'SVOD',
} }
export enum Version { export enum Version {
Simulcast = 'Simulcast', Simulcast = 'Simulcast',
Uncut = 'Uncut', Uncut = 'Uncut',
} }
export type MostRecentSvodJpnUs = Record<string, any> export type MostRecentSvodJpnUs = Record<string, any>
export interface QualityClass { export interface QualityClass {
quality: QualityQuality; quality: QualityQuality;
height: number; height: number;
} }
export enum QualityQuality { export enum QualityQuality {
HD = 'HD', HD = 'HD',
SD = 'SD', SD = 'SD',
} }
export interface TitleImages { export interface TitleImages {
showThumbnail: string; showThumbnail: string;
showBackgroundSite: string; showBackgroundSite: string;
showDetailHeaderDesktop: string; showDetailHeaderDesktop: string;
continueWatchingDesktop: string; continueWatchingDesktop: string;
showDetailHeroSite: string; showDetailHeroSite: string;
appleHorizontalBannerShow: string; appleHorizontalBannerShow: string;
backgroundImageXbox_360: string; backgroundImageXbox_360: string;
applePosterCover: string; applePosterCover: string;
showDetailBoxArtTablet: string; showDetailBoxArtTablet: string;
featuredShowBackgroundTablet: string; featuredShowBackgroundTablet: string;
backgroundImageAppletvfiretv: string; backgroundImageAppletvfiretv: string;
newShowDetailHero: string; newShowDetailHero: string;
showDetailHeroDesktop: string; showDetailHeroDesktop: string;
showKeyart: string; showKeyart: string;
continueWatchingMobile: string; continueWatchingMobile: string;
featuredSpotlightShowPhone: string; featuredSpotlightShowPhone: string;
appleHorizontalBannerMovie: string; appleHorizontalBannerMovie: string;
featuredSpotlightShowTablet: string; featuredSpotlightShowTablet: string;
showDetailBoxArtPhone: string; showDetailBoxArtPhone: string;
featuredShowBackgroundPhone: string; featuredShowBackgroundPhone: string;
appleSquareCover: string; appleSquareCover: string;
backgroundVideo: string; backgroundVideo: string;
showMasterKeyArt: string; showMasterKeyArt: string;
newShowDetailHeroPhone: string; newShowDetailHeroPhone: string;
showDetailBoxArtXbox_360: string; showDetailBoxArtXbox_360: string;
showDetailHeaderMobile: string; showDetailHeaderMobile: string;
showLogo: string; showLogo: string;
} }
export interface VersionAudio { export interface VersionAudio {
Uncut?: Audio[]; Uncut?: Audio[];
Simulcast: Audio[]; Simulcast: Audio[];
} }

View file

@ -1,49 +1,49 @@
declare module 'm3u8-parsed' { declare module 'm3u8-parsed' {
export type M3U8 = { export type M3U8 = {
allowCache: boolean, allowCache: boolean,
discontinuityStarts: [], discontinuityStarts: [],
segments: { segments: {
duration: number, duration: number,
byterange?: { byterange?: {
length: number, length: number,
offset: number offset: number
}, },
uri: string, uri: string,
key: { key: {
method: string, method: string,
uri: string, uri: string,
}, },
timeline: number timeline: number
}[], }[],
version: number, version: number,
mediaGroups: { mediaGroups: {
[type: string]: { [type: string]: {
[index: string]: { [index: string]: {
[language: string]: { [language: string]: {
default: boolean, default: boolean,
autoselect: boolean, autoselect: boolean,
language: string, language: string,
uri: string uri: string
} }
} }
} }
}, },
playlists: { playlists: {
uri: string, uri: string,
timeline: number, timeline: number,
attributes: { attributes: {
'CLOSED-CAPTIONS': string, 'CLOSED-CAPTIONS': string,
'AUDIO': string, 'AUDIO': string,
'FRAME-RATE': number, 'FRAME-RATE': number,
'RESOLUTION': { 'RESOLUTION': {
width: number, width: number,
height: number height: number
}, },
'CODECS': string, 'CODECS': string,
'AVERAGE-BANDWIDTH': string, 'AVERAGE-BANDWIDTH': string,
'BANDWIDTH': number 'BANDWIDTH': number
} }
}[], }[],
} }
export default function (data: string): M3U8; export default function (data: string): M3U8;
} }

View file

@ -4,97 +4,97 @@ import type { AvailableMuxer } from '../modules/module.args';
import { LanguageItem } from '../modules/module.langsData'; import { LanguageItem } from '../modules/module.langsData';
export interface MessageHandler { export interface MessageHandler {
name: string name: string
auth: (data: AuthData) => Promise<AuthResponse>; auth: (data: AuthData) => Promise<AuthResponse>;
version: () => Promise<string>; version: () => Promise<string>;
checkToken: () => Promise<CheckTokenResponse>; checkToken: () => Promise<CheckTokenResponse>;
search: (data: SearchData) => Promise<SearchResponse>, search: (data: SearchData) => Promise<SearchResponse>,
availableDubCodes: () => Promise<string[]>, availableDubCodes: () => Promise<string[]>,
availableSubCodes: () => Promise<string[]>, availableSubCodes: () => Promise<string[]>,
handleDefault: (name: string) => Promise<any>, handleDefault: (name: string) => Promise<any>,
resolveItems: (data: ResolveItemsData) => Promise<boolean>, resolveItems: (data: ResolveItemsData) => Promise<boolean>,
listEpisodes: (id: string) => Promise<EpisodeListResponse>, listEpisodes: (id: string) => Promise<EpisodeListResponse>,
downloadItem: (data: QueueItem) => void, downloadItem: (data: QueueItem) => void,
isDownloading: () => Promise<boolean>, isDownloading: () => Promise<boolean>,
openFolder: (path: FolderTypes) => void, openFolder: (path: FolderTypes) => void,
openFile: (data: [FolderTypes, string]) => void, openFile: (data: [FolderTypes, string]) => void,
openURL: (data: string) => void; openURL: (data: string) => void;
getQueue: () => Promise<QueueItem[]>, getQueue: () => Promise<QueueItem[]>,
removeFromQueue: (index: number) => void, removeFromQueue: (index: number) => void,
clearQueue: () => void, clearQueue: () => void,
setDownloadQueue: (data: boolean) => void, setDownloadQueue: (data: boolean) => void,
getDownloadQueue: () => Promise<boolean> getDownloadQueue: () => Promise<boolean>
} }
export type FolderTypes = 'content' | 'config'; export type FolderTypes = 'content' | 'config';
export type QueueItem = { export type QueueItem = {
title: string, title: string,
episode: string, episode: string,
fileName: string, fileName: string,
dlsubs: string[], dlsubs: string[],
parent: { parent: {
title: string, title: string,
season: string season: string
}, },
q: number, q: number,
dlVideoOnce: boolean, dlVideoOnce: boolean,
dubLang: string[], dubLang: string[],
image: string, image: string,
} & ResolveItemsData } & ResolveItemsData
export type ResolveItemsData = { export type ResolveItemsData = {
id: string, id: string,
dubLang: string[], dubLang: string[],
all: boolean, all: boolean,
but: boolean, but: boolean,
novids: boolean, novids: boolean,
noaudio: boolean noaudio: boolean
dlVideoOnce: boolean, dlVideoOnce: boolean,
e: string, e: string,
fileName: string, fileName: string,
q: number, q: number,
dlsubs: string[] dlsubs: string[]
} }
export type SearchResponseItem = { export type SearchResponseItem = {
image: string, image: string,
name: string, name: string,
desc?: string, desc?: string,
id: string, id: string,
lang?: string[], lang?: string[],
rating: number rating: number
}; };
export type Episode = { export type Episode = {
e: string, e: string,
lang: string[], lang: string[],
name: string, name: string,
season: string, season: string,
seasonTitle: string, seasonTitle: string,
episode: string, episode: string,
id: string, id: string,
img: string, img: string,
description: string, description: string,
time: string time: string
} }
export type SearchResponse = ResponseBase<SearchResponseItem[]> export type SearchResponse = ResponseBase<SearchResponseItem[]>
export type EpisodeListResponse = ResponseBase<Episode[]> export type EpisodeListResponse = ResponseBase<Episode[]>
export type FuniEpisodeData = { export type FuniEpisodeData = {
title: string, title: string,
episode: string, episode: string,
epsiodeNumber: string, epsiodeNumber: string,
episodeID: string, episodeID: string,
seasonTitle: string, seasonTitle: string,
seasonNumber: string, seasonNumber: string,
ids: { ids: {
episode: string, episode: string,
show: string, show: string,
season: string season: string
}, },
image: string image: string
}; };
export type AuthData = { username: string, password: string }; export type AuthData = { username: string, password: string };
@ -102,12 +102,12 @@ export type SearchData = { search: string, page?: number, 'search-type'?: string
export type FuniGetShowData = { id: number, e?: string, but: boolean, all: boolean }; 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 FuniGetEpisodeData = { subs: FuniSubsData, fnSlug: FuniEpisodeData, simul?: boolean; dubLang: string[], s: string }
export type FuniStreamData = { force?: 'Y'|'y'|'N'|'n'|'C'|'c', callbackMaker?: (data: DownloadInfo) => HLSCallback, q: number, x: number, fileName: string, numbers: number, novids?: boolean, export type FuniStreamData = { force?: 'Y'|'y'|'N'|'n'|'C'|'c', 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, 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, override: string[], videoTitle: string, forceMuxer: AvailableMuxer | undefined, simul: boolean, skipSubMux: boolean, nocleanup: boolean, override: string[], videoTitle: string,
ffmpegOptions: string[], mkvmergeOptions: string[], defaultAudio: LanguageItem, defaultSub: LanguageItem, ccTag: string } ffmpegOptions: string[], mkvmergeOptions: string[], defaultAudio: LanguageItem, defaultSub: LanguageItem, ccTag: string }
export type FuniSubsData = { nosubs?: boolean, sub: boolean, dlsubs: string[], ccTag: string } export type FuniSubsData = { nosubs?: boolean, sub: boolean, dlsubs: string[], ccTag: string }
export type DownloadData = { export type DownloadData = {
hslang?: string; id: string, e: string, dubLang: string[], dlsubs: string[], fileName: string, q: number, novids: boolean, noaudio: boolean, dlVideoOnce: boolean hslang?: string; id: string, e: string, dubLang: string[], dlsubs: string[], fileName: string, q: number, novids: boolean, noaudio: boolean, dlVideoOnce: boolean
} }
export type AuthResponse = ResponseBase<undefined>; export type AuthResponse = ResponseBase<undefined>;
@ -118,44 +118,44 @@ export type CheckTokenResponse = ResponseBase<undefined>;
export type ResponseBase<T> = ({ export type ResponseBase<T> = ({
isOk: true, isOk: true,
value: T value: T
} | { } | {
isOk: false, isOk: false,
reason: Error reason: Error
}); });
export type ProgressData = { export type ProgressData = {
total: number, total: number,
cur: number, cur: number,
percent: number|string, percent: number|string,
time: number, time: number,
downloadSpeed: number, downloadSpeed: number,
bytes: number bytes: number
}; };
export type PossibleMessages = keyof ServiceHandler; export type PossibleMessages = keyof ServiceHandler;
export type DownloadInfo = { export type DownloadInfo = {
image: string, image: string,
parent: { parent: {
title: string title: string
}, },
title: string, title: string,
language: LanguageItem, language: LanguageItem,
fileName: string fileName: string
} }
export type ExtendedProgress = { export type ExtendedProgress = {
progress: ProgressData, progress: ProgressData,
downloadInfo: DownloadInfo downloadInfo: DownloadInfo
} }
export type GuiState = { export type GuiState = {
setup: boolean, setup: boolean,
services: Record<string, GuiStateService> services: Record<string, GuiStateService>
} }
export type GuiStateService = { export type GuiStateService = {
queue: QueueItem[] queue: QueueItem[]
} }

192
@types/mpd-parser.d.ts vendored
View file

@ -1,101 +1,101 @@
declare module 'mpd-parser' { declare module 'mpd-parser' {
export type Segment = { export type Segment = {
uri: string, uri: string,
timeline: number, timeline: number,
duration: number, duration: number,
resolvedUri: string, resolvedUri: string,
map: { map: {
uri: string, uri: string,
resolvedUri: string, resolvedUri: string,
byterange?: { byterange?: {
length: number, length: number,
offset: number offset: number
} }
}, },
byterange?: { byterange?: {
length: number, length: number,
offset: number offset: number
}, },
number: number, number: number,
presentationTime: number presentationTime: number
} }
export type Sidx = { export type Sidx = {
uri: string, uri: string,
resolvedUri: string, resolvedUri: string,
byterange: { byterange: {
length: number, length: number,
offset: number offset: number
}, },
map: { map: {
uri: string, uri: string,
resolvedUri: string, resolvedUri: string,
byterange: { byterange: {
length: number, length: number,
offset: number offset: number
} }
}, },
duration: number, duration: number,
timeline: number, timeline: number,
presentationTime: number, presentationTime: number,
number: number number: number
} }
export type Playlist = { export type Playlist = {
attributes: { attributes: {
NAME: string, NAME: string,
BANDWIDTH: number, BANDWIDTH: number,
CODECS: string, CODECS: string,
'PROGRAM-ID': number, 'PROGRAM-ID': number,
// Following for video only // Following for video only
'FRAME-RATE'?: number, 'FRAME-RATE'?: number,
AUDIO?: string, // audio stream name AUDIO?: string, // audio stream name
SUBTITLES?: string, SUBTITLES?: string,
RESOLUTION?: { RESOLUTION?: {
width: number, width: number,
height: number height: number
} }
}, },
uri: string, uri: string,
endList: boolean, endList: boolean,
timeline: number, timeline: number,
resolvedUri: string, resolvedUri: string,
targetDuration: number, targetDuration: number,
discontinuitySequence: number, discontinuitySequence: number,
discontinuityStarts: [], discontinuityStarts: [],
timelineStarts: { timelineStarts: {
start: number, start: number,
timeline: number timeline: number
}[], }[],
mediaSequence: number, mediaSequence: number,
contentProtection?: { contentProtection?: {
[type: string]: { [type: string]: {
pssh?: Uint8Array pssh?: Uint8Array
} }
} }
segments: Segment[] segments: Segment[]
sidx?: Sidx sidx?: Sidx
} }
export type Manifest = { export type Manifest = {
allowCache: boolean, allowCache: boolean,
discontinuityStarts: [], discontinuityStarts: [],
segments: [], segments: [],
endList: true, endList: true,
duration: number, duration: number,
playlists: Playlist[], playlists: Playlist[],
mediaGroups: { mediaGroups: {
AUDIO: { AUDIO: {
audio: { audio: {
[name: string]: { [name: string]: {
language: string, language: string,
autoselect: boolean, autoselect: boolean,
default: boolean, default: boolean,
playlists: Playlist[] playlists: Playlist[]
} }
} }
} }
} }
} }
export function parse(manifest: string): Manifest export function parse(manifest: string): Manifest
} }

View file

@ -1,43 +1,43 @@
export interface NewHidiveEpisode { export interface NewHidiveEpisode {
description: string; description: string;
duration: number; duration: number;
title: string; title: string;
categories: string[]; categories: string[];
contentDownload: ContentDownload; contentDownload: ContentDownload;
favourite: boolean; favourite: boolean;
subEvents: any[]; subEvents: any[];
thumbnailUrl: string; thumbnailUrl: string;
longDescription: string; longDescription: string;
posterUrl: string; posterUrl: string;
offlinePlaybackLanguages: string[]; offlinePlaybackLanguages: string[];
externalAssetId: string; externalAssetId: string;
maxHeight: number; maxHeight: number;
rating: Rating; rating: Rating;
episodeInformation: EpisodeInformation; episodeInformation: EpisodeInformation;
id: number; id: number;
accessLevel: string; accessLevel: string;
playerUrlCallback: string; playerUrlCallback: string;
thumbnailsPreview: string; thumbnailsPreview: string;
displayableTags: any[]; displayableTags: any[];
plugins: any[]; plugins: any[];
watchStatus: string; watchStatus: string;
computedReleases: any[]; computedReleases: any[];
licences: any[]; licences: any[];
type: string; type: string;
} }
export interface ContentDownload { export interface ContentDownload {
permission: string; permission: string;
period: string; period: string;
} }
export interface EpisodeInformation { export interface EpisodeInformation {
seasonNumber: number; seasonNumber: number;
episodeNumber: number; episodeNumber: number;
season: number; season: number;
} }
export interface Rating { export interface Rating {
rating: string; rating: string;
descriptors: any[]; descriptors: any[];
} }

View file

@ -1,33 +1,33 @@
export interface NewHidivePlayback { export interface NewHidivePlayback {
watermark: null; watermark: null;
skipMarkers: any[]; skipMarkers: any[];
annotations: null; annotations: null;
dash: Format[]; dash: Format[];
hls: Format[]; hls: Format[];
} }
export interface Format { export interface Format {
subtitles: Subtitle[]; subtitles: Subtitle[];
url: string; url: string;
drm: DRM; drm: DRM;
} }
export interface DRM { export interface DRM {
encryptionMode: string; encryptionMode: string;
containerType: string; containerType: string;
jwtToken: string; jwtToken: string;
url: string; url: string;
keySystems: string[]; keySystems: string[];
} }
export interface Subtitle { export interface Subtitle {
format: Formats; format: Formats;
language: string; language: string;
url: string; url: string;
} }
export enum Formats { export enum Formats {
Scc = 'scc', Scc = 'scc',
Srt = 'srt', Srt = 'srt',
Vtt = 'vtt', Vtt = 'vtt',
} }

View file

@ -1,88 +1,88 @@
export interface NewHidiveSearch { export interface NewHidiveSearch {
results: Result[]; results: Result[];
} }
export interface Result { export interface Result {
hits: Hit[]; hits: Hit[];
nbHits: number; nbHits: number;
page: number; page: number;
nbPages: number; nbPages: number;
hitsPerPage: number; hitsPerPage: number;
exhaustiveNbHits: boolean; exhaustiveNbHits: boolean;
exhaustiveTypo: boolean; exhaustiveTypo: boolean;
exhaustive: Exhaustive; exhaustive: Exhaustive;
query: string; query: string;
params: string; params: string;
index: string; index: string;
renderingContent: object; renderingContent: object;
processingTimeMS: number; processingTimeMS: number;
processingTimingsMS: ProcessingTimingsMS; processingTimingsMS: ProcessingTimingsMS;
serverTimeMS: number; serverTimeMS: number;
} }
export interface Exhaustive { export interface Exhaustive {
nbHits: boolean; nbHits: boolean;
typo: boolean; typo: boolean;
} }
export interface Hit { export interface Hit {
type: string; type: string;
weight: number; weight: number;
id: number; id: number;
name: string; name: string;
description: string; description: string;
meta: object; meta: object;
coverUrl: string; coverUrl: string;
smallCoverUrl: string; smallCoverUrl: string;
seasonsCount: number; seasonsCount: number;
tags: string[]; tags: string[];
localisations: HitLocalisations; localisations: HitLocalisations;
ratings: Ratings; ratings: Ratings;
objectID: string; objectID: string;
_highlightResult: HighlightResult; _highlightResult: HighlightResult;
} }
export interface HighlightResult { export interface HighlightResult {
name: Description; name: Description;
description: Description; description: Description;
tags: Description[]; tags: Description[];
localisations: HighlightResultLocalisations; localisations: HighlightResultLocalisations;
} }
export interface Description { export interface Description {
value: string; value: string;
matchLevel: string; matchLevel: string;
matchedWords: string[]; matchedWords: string[];
fullyHighlighted?: boolean; fullyHighlighted?: boolean;
} }
export interface HighlightResultLocalisations { export interface HighlightResultLocalisations {
en_US: PurpleEnUS; en_US: PurpleEnUS;
} }
export interface PurpleEnUS { export interface PurpleEnUS {
title: Description; title: Description;
description: Description; description: Description;
} }
export interface HitLocalisations { export interface HitLocalisations {
[language: string]: HitLocalization; [language: string]: HitLocalization;
} }
export interface HitLocalization { export interface HitLocalization {
title: string; title: string;
description: string; description: string;
} }
export interface Ratings { export interface Ratings {
US: string[]; US: string[];
} }
export interface ProcessingTimingsMS { export interface ProcessingTimingsMS {
_request: Request; _request: Request;
} }
export interface Request { export interface Request {
queue: number; queue: number;
roundTrip: number; roundTrip: number;
} }

View file

@ -1,89 +1,89 @@
export interface NewHidiveSeason { export interface NewHidiveSeason {
title: string; title: string;
description: string; description: string;
longDescription: string; longDescription: string;
smallCoverUrl: string; smallCoverUrl: string;
coverUrl: string; coverUrl: string;
titleUrl: string; titleUrl: string;
posterUrl: string; posterUrl: string;
seasonNumber: number; seasonNumber: number;
episodeCount: number; episodeCount: number;
displayableTags: any[]; displayableTags: any[];
rating: Rating; rating: Rating;
contentRating: Rating; contentRating: Rating;
id: number; id: number;
series: Series; series: Series;
episodes: Episode[]; episodes: Episode[];
paging: Paging; paging: Paging;
licences: any[]; licences: any[];
} }
export interface Rating { export interface Rating {
rating: string; rating: string;
descriptors: any[]; descriptors: any[];
} }
export interface Episode { export interface Episode {
accessLevel: string; accessLevel: string;
availablePurchases?: any[]; availablePurchases?: any[];
licenceIds?: any[]; licenceIds?: any[];
type: string; type: string;
id: number; id: number;
title: string; title: string;
description: string; description: string;
thumbnailUrl: string; thumbnailUrl: string;
posterUrl: string; posterUrl: string;
duration: number; duration: number;
favourite: boolean; favourite: boolean;
contentDownload: ContentDownload; contentDownload: ContentDownload;
offlinePlaybackLanguages: string[]; offlinePlaybackLanguages: string[];
externalAssetId: string; externalAssetId: string;
subEvents: any[]; subEvents: any[];
maxHeight: number; maxHeight: number;
thumbnailsPreview: string; thumbnailsPreview: string;
longDescription: string; longDescription: string;
episodeInformation: EpisodeInformation; episodeInformation: EpisodeInformation;
categories: string[]; categories: string[];
displayableTags: any[]; displayableTags: any[];
watchStatus: string; watchStatus: string;
computedReleases: any[]; computedReleases: any[];
} }
export interface ContentDownload { export interface ContentDownload {
permission: string; permission: string;
} }
export interface EpisodeInformation { export interface EpisodeInformation {
seasonNumber: number; seasonNumber: number;
episodeNumber: number; episodeNumber: number;
season: number; season: number;
} }
export interface Paging { export interface Paging {
moreDataAvailable: boolean; moreDataAvailable: boolean;
lastSeen: number; lastSeen: number;
} }
export interface Series { export interface Series {
seriesId: number; seriesId: number;
title: string; title: string;
description: string; description: string;
longDescription: string; longDescription: string;
displayableTags: any[]; displayableTags: any[];
rating: Rating; rating: Rating;
contentRating: Rating; contentRating: Rating;
} }
export interface NewHidiveSeriesExtra extends Series { export interface NewHidiveSeriesExtra extends Series {
season: NewHidiveSeason; season: NewHidiveSeason;
} }
export interface NewHidiveEpisodeExtra extends Episode { export interface NewHidiveEpisodeExtra extends Episode {
titleId: number; titleId: number;
nameLong: string; nameLong: string;
seasonTitle: string; seasonTitle: string;
seriesTitle: string; seriesTitle: string;
seriesId?: number; seriesId?: number;
isSelected: boolean; isSelected: boolean;
jwtToken?: string; jwtToken?: string;
} }

View file

@ -1,35 +1,35 @@
export interface NewHidiveSeries { export interface NewHidiveSeries {
id: number; id: number;
title: string; title: string;
description: string; description: string;
longDescription: string; longDescription: string;
smallCoverUrl: string; smallCoverUrl: string;
coverUrl: string; coverUrl: string;
titleUrl: string; titleUrl: string;
posterUrl: string; posterUrl: string;
seasons: Season[]; seasons: Season[];
rating: Rating; rating: Rating;
contentRating: Rating; contentRating: Rating;
displayableTags: any[]; displayableTags: any[];
paging: Paging; paging: Paging;
} }
export interface Rating { export interface Rating {
rating: string; rating: string;
descriptors: any[]; descriptors: any[];
} }
export interface Paging { export interface Paging {
moreDataAvailable: boolean; moreDataAvailable: boolean;
lastSeen: number; lastSeen: number;
} }
export interface Season { export interface Season {
title: string; title: string;
description: string; description: string;
longDescription: string; longDescription: string;
seasonNumber: number; seasonNumber: number;
episodeCount: number; episodeCount: number;
displayableTags: any[]; displayableTags: any[];
id: number; id: number;
} }

332
@types/objectInfo.d.ts vendored
View file

@ -1,211 +1,211 @@
// Generated by https://quicktype.io // Generated by https://quicktype.io
export interface ObjectInfo { export interface ObjectInfo {
total: number; total: number;
data: CrunchyObject[]; data: CrunchyObject[];
meta: Record<unknown>; meta: Record<unknown>;
} }
export interface CrunchyObject { export interface CrunchyObject {
__links__?: Links; __links__?: Links;
channel_id: string; channel_id: string;
slug: string; slug: string;
images: Images; images: Images;
linked_resource_key: string; linked_resource_key: string;
description: string; description: string;
promo_description: string; promo_description: string;
external_id: string; external_id: string;
title: string; title: string;
series_metadata?: SeriesMetadata; series_metadata?: SeriesMetadata;
id: string; id: string;
slug_title: string; slug_title: string;
type: string; type: string;
promo_title: string; promo_title: string;
movie_listing_metadata?: MovieListingMetadata; movie_listing_metadata?: MovieListingMetadata;
movie_metadata?: MovieMetadata; movie_metadata?: MovieMetadata;
playback?: string; playback?: string;
episode_metadata?: EpisodeMetadata; episode_metadata?: EpisodeMetadata;
streams_link?: string; streams_link?: string;
season_metadata?: SeasonMetadata; season_metadata?: SeasonMetadata;
isSelected?: boolean; isSelected?: boolean;
f_num: string; f_num: string;
s_num: string; s_num: string;
} }
export interface Links { export interface Links {
'episode/season': LinkData; 'episode/season': LinkData;
'episode/series': LinkData; 'episode/series': LinkData;
resource: LinkData; resource: LinkData;
'resource/channel': LinkData; 'resource/channel': LinkData;
streams: LinkData; streams: LinkData;
} }
export interface LinkData { export interface LinkData {
href: string; href: string;
} }
export interface EpisodeMetadata { export interface EpisodeMetadata {
audio_locale: Locale; audio_locale: Locale;
availability_ends: Date; availability_ends: Date;
availability_notes: string; availability_notes: string;
availability_starts: Date; availability_starts: Date;
available_date: null; available_date: null;
available_offline: boolean; available_offline: boolean;
closed_captions_available: boolean; closed_captions_available: boolean;
duration_ms: number; duration_ms: number;
eligible_region: string; eligible_region: string;
episode: string; episode: string;
episode_air_date: Date; episode_air_date: Date;
episode_number: number; episode_number: number;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
free_available_date: Date; free_available_date: Date;
identifier: string; identifier: string;
is_clip: boolean; is_clip: boolean;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
premium_available_date: Date; premium_available_date: Date;
premium_date: null; premium_date: null;
season_id: string; season_id: string;
season_number: number; season_number: number;
season_slug_title: string; season_slug_title: string;
season_title: string; season_title: string;
sequence_number: number; sequence_number: number;
series_id: string; series_id: string;
series_slug_title: string; series_slug_title: string;
series_title: string; series_title: string;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
tenant_categories?: string[]; tenant_categories?: string[];
upload_date: Date; upload_date: Date;
versions: EpisodeMetadataVersion[]; versions: EpisodeMetadataVersion[];
} }
export interface EpisodeMetadataVersion { export interface EpisodeMetadataVersion {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
is_premium_only: boolean; is_premium_only: boolean;
media_guid: string; media_guid: string;
original: boolean; original: boolean;
season_guid: string; season_guid: string;
variant: string; variant: string;
} }
export interface Images { export interface Images {
poster_tall?: Array<Image[]>; poster_tall?: Array<Image[]>;
poster_wide?: Array<Image[]>; poster_wide?: Array<Image[]>;
promo_image?: Array<Image[]>; promo_image?: Array<Image[]>;
thumbnail?: Array<Image[]>; thumbnail?: Array<Image[]>;
} }
export interface Image { export interface Image {
height: number; height: number;
source: string; source: string;
type: ImageType; type: ImageType;
width: number; width: number;
} }
export enum ImageType { export enum ImageType {
PosterTall = 'poster_tall', PosterTall = 'poster_tall',
PosterWide = 'poster_wide', PosterWide = 'poster_wide',
PromoImage = 'promo_image', PromoImage = 'promo_image',
Thumbnail = 'thumbnail', Thumbnail = 'thumbnail',
} }
export interface MovieListingMetadata { export interface MovieListingMetadata {
availability_notes: string; availability_notes: string;
available_date: null; available_date: null;
available_offline: boolean; available_offline: boolean;
duration_ms: number; duration_ms: number;
extended_description: string; extended_description: string;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
first_movie_id: string; first_movie_id: string;
free_available_date: Date; free_available_date: Date;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
movie_release_year: number; movie_release_year: number;
premium_available_date: Date; premium_available_date: Date;
premium_date: null; premium_date: null;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
tenant_categories: string[]; tenant_categories: string[];
} }
export interface MovieMetadata { export interface MovieMetadata {
availability_notes: string; availability_notes: string;
available_offline: boolean; available_offline: boolean;
closed_captions_available: boolean; closed_captions_available: boolean;
duration_ms: number; duration_ms: number;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_premium_only: boolean; is_premium_only: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
movie_listing_id: string; movie_listing_id: string;
movie_listing_slug_title: string; movie_listing_slug_title: string;
movie_listing_title: string; movie_listing_title: string;
} }
export interface SeasonMetadata { export interface SeasonMetadata {
audio_locale: Locale; audio_locale: Locale;
audio_locales: Locale[]; audio_locales: Locale[];
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
identifier: string; identifier: string;
is_mature: boolean; is_mature: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
season_display_number: string; season_display_number: string;
season_sequence_number: number; season_sequence_number: number;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
versions: SeasonMetadataVersion[]; versions: SeasonMetadataVersion[];
} }
export interface SeasonMetadataVersion { export interface SeasonMetadataVersion {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
original: boolean; original: boolean;
variant: string; variant: string;
} }
export interface SeriesMetadata { export interface SeriesMetadata {
audio_locales: Locale[]; audio_locales: Locale[];
availability_notes: string; availability_notes: string;
episode_count: number; episode_count: number;
extended_description: string; extended_description: string;
extended_maturity_rating: Record<unknown>; extended_maturity_rating: Record<unknown>;
is_dubbed: boolean; is_dubbed: boolean;
is_mature: boolean; is_mature: boolean;
is_simulcast: boolean; is_simulcast: boolean;
is_subbed: boolean; is_subbed: boolean;
mature_blocked: boolean; mature_blocked: boolean;
maturity_ratings: string[]; maturity_ratings: string[];
season_count: number; season_count: number;
series_launch_year: number; series_launch_year: number;
subtitle_locales: Locale[]; subtitle_locales: Locale[];
tenant_categories?: string[]; tenant_categories?: string[];
} }
export enum Locale { export enum Locale {
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }

2
@types/pkg.d.ts vendored
View file

@ -1,3 +1,3 @@
declare module 'pkg' { declare module 'pkg' {
export async function exec(config: string[]); export async function exec(config: string[]);
} }

View file

@ -1,120 +1,120 @@
// Generated by https://quicktype.io // Generated by https://quicktype.io
export interface PlaybackData { export interface PlaybackData {
total: number; total: number;
vpb: { [key: string]: { [key: string]: StreamDetails } }; vpb: { [key: string]: { [key: string]: StreamDetails } };
apb: { [key: string]: { [key: string]: StreamDetails } }; apb: { [key: string]: { [key: string]: StreamDetails } };
meta: Meta; meta: Meta;
} }
export interface StreamList { export interface StreamList {
download_hls: CrunchyStreams; download_hls: CrunchyStreams;
drm_adaptive_hls: CrunchyStreams; drm_adaptive_hls: CrunchyStreams;
multitrack_adaptive_hls_v2: CrunchyStreams; multitrack_adaptive_hls_v2: CrunchyStreams;
vo_adaptive_hls: CrunchyStreams; vo_adaptive_hls: CrunchyStreams;
vo_drm_adaptive_hls: CrunchyStreams; vo_drm_adaptive_hls: CrunchyStreams;
adaptive_hls: CrunchyStreams; adaptive_hls: CrunchyStreams;
drm_download_dash: CrunchyStreams; drm_download_dash: CrunchyStreams;
drm_download_hls: CrunchyStreams; drm_download_hls: CrunchyStreams;
drm_multitrack_adaptive_hls_v2: CrunchyStreams; drm_multitrack_adaptive_hls_v2: CrunchyStreams;
vo_drm_adaptive_dash: CrunchyStreams; vo_drm_adaptive_dash: CrunchyStreams;
adaptive_dash: CrunchyStreams; adaptive_dash: CrunchyStreams;
urls: CrunchyStreams; urls: CrunchyStreams;
vo_adaptive_dash: CrunchyStreams; vo_adaptive_dash: CrunchyStreams;
download_dash: CrunchyStreams; download_dash: CrunchyStreams;
drm_adaptive_dash: CrunchyStreams; drm_adaptive_dash: CrunchyStreams;
} }
export interface CrunchyStreams { export interface CrunchyStreams {
'': StreamDetails; '': StreamDetails;
'en-US'?: StreamDetails; 'en-US'?: StreamDetails;
'es-LA'?: StreamDetails; 'es-LA'?: StreamDetails;
'es-419'?: StreamDetails; 'es-419'?: StreamDetails;
'es-ES'?: StreamDetails; 'es-ES'?: StreamDetails;
'pt-BR'?: StreamDetails; 'pt-BR'?: StreamDetails;
'fr-FR'?: StreamDetails; 'fr-FR'?: StreamDetails;
'de-DE'?: StreamDetails; 'de-DE'?: StreamDetails;
'ar-ME'?: StreamDetails; 'ar-ME'?: StreamDetails;
'ar-SA'?: StreamDetails; 'ar-SA'?: StreamDetails;
'it-IT'?: StreamDetails; 'it-IT'?: StreamDetails;
'ru-RU'?: StreamDetails; 'ru-RU'?: StreamDetails;
'tr-TR'?: StreamDetails; 'tr-TR'?: StreamDetails;
'hi-IN'?: StreamDetails; 'hi-IN'?: StreamDetails;
'zh-CN'?: StreamDetails; 'zh-CN'?: StreamDetails;
'ko-KR'?: StreamDetails; 'ko-KR'?: StreamDetails;
'ja-JP'?: StreamDetails; 'ja-JP'?: StreamDetails;
[string: string]: StreamDetails; [string: string]: StreamDetails;
} }
export interface StreamDetails { export interface StreamDetails {
//hardsub_locale: Locale; //hardsub_locale: Locale;
hardsub_locale: string; hardsub_locale: string;
url: string; url: string;
hardsub_lang?: string; hardsub_lang?: string;
audio_lang?: string; audio_lang?: string;
type?: string; type?: string;
} }
export interface Meta { export interface Meta {
media_id: string; media_id: string;
subtitles: Subtitles; subtitles: Subtitles;
bifs: string[]; bifs: string[];
versions: Version[]; versions: Version[];
audio_locale: Locale; audio_locale: Locale;
closed_captions: Subtitles; closed_captions: Subtitles;
captions: Subtitles; captions: Subtitles;
} }
export interface Subtitles { export interface Subtitles {
''?: SubtitleInfo; ''?: SubtitleInfo;
'en-US'?: SubtitleInfo; 'en-US'?: SubtitleInfo;
'es-LA'?: SubtitleInfo; 'es-LA'?: SubtitleInfo;
'es-419'?: SubtitleInfo; 'es-419'?: SubtitleInfo;
'es-ES'?: SubtitleInfo; 'es-ES'?: SubtitleInfo;
'pt-BR'?: SubtitleInfo; 'pt-BR'?: SubtitleInfo;
'fr-FR'?: SubtitleInfo; 'fr-FR'?: SubtitleInfo;
'de-DE'?: SubtitleInfo; 'de-DE'?: SubtitleInfo;
'ar-ME'?: SubtitleInfo; 'ar-ME'?: SubtitleInfo;
'ar-SA'?: SubtitleInfo; 'ar-SA'?: SubtitleInfo;
'it-IT'?: SubtitleInfo; 'it-IT'?: SubtitleInfo;
'ru-RU'?: SubtitleInfo; 'ru-RU'?: SubtitleInfo;
'tr-TR'?: SubtitleInfo; 'tr-TR'?: SubtitleInfo;
'hi-IN'?: SubtitleInfo; 'hi-IN'?: SubtitleInfo;
'zh-CN'?: SubtitleInfo; 'zh-CN'?: SubtitleInfo;
'ko-KR'?: SubtitleInfo; 'ko-KR'?: SubtitleInfo;
'ja-JP'?: SubtitleInfo; 'ja-JP'?: SubtitleInfo;
} }
export interface SubtitleInfo { export interface SubtitleInfo {
format: string; format: string;
locale: Locale; locale: Locale;
url: string; url: string;
} }
export interface Version { export interface Version {
audio_locale: Locale; audio_locale: Locale;
guid: string; guid: string;
is_premium_only: boolean; is_premium_only: boolean;
media_guid: string; media_guid: string;
original: boolean; original: boolean;
season_guid: string; season_guid: string;
variant: string; variant: string;
} }
export enum Locale { export enum Locale {
default = '', default = '',
enUS = 'en-US', enUS = 'en-US',
esLA = 'es-LA', esLA = 'es-LA',
es419 = 'es-419', es419 = 'es-419',
esES = 'es-ES', esES = 'es-ES',
ptBR = 'pt-BR', ptBR = 'pt-BR',
frFR = 'fr-FR', frFR = 'fr-FR',
deDE = 'de-DE', deDE = 'de-DE',
arME = 'ar-ME', arME = 'ar-ME',
arSA = 'ar-SA', arSA = 'ar-SA',
itIT = 'it-IT', itIT = 'it-IT',
ruRU = 'ru-RU', ruRU = 'ru-RU',
trTR = 'tr-TR', trTR = 'tr-TR',
hiIN = 'hi-IN', hiIN = 'hi-IN',
zhCN = 'zh-CN', zhCN = 'zh-CN',
koKR = 'ko-KR', koKR = 'ko-KR',
jaJP = 'ja-JP', jaJP = 'ja-JP',
} }

View file

@ -1,15 +1,15 @@
import { ExtendedProgress, QueueItem } from './messageHandler'; import { ExtendedProgress, QueueItem } from './messageHandler';
export type RandomEvents = { export type RandomEvents = {
progress: ExtendedProgress, progress: ExtendedProgress,
finish: undefined, finish: undefined,
queueChange: QueueItem[], queueChange: QueueItem[],
current: QueueItem|undefined current: QueueItem|undefined
} }
export interface RandomEvent<T extends keyof RandomEvents> { export interface RandomEvent<T extends keyof RandomEvents> {
name: T, name: T,
data: RandomEvents[T] data: RandomEvents[T]
} }
export type Handler<T extends keyof RandomEvents> = (data: RandomEvent<T>) => unknown; export type Handler<T extends keyof RandomEvents> = (data: RandomEvent<T>) => unknown;

View file

@ -1,3 +1,3 @@
declare module 'removeNPMAbsolutePaths' { declare module 'removeNPMAbsolutePaths' {
export default async function modulesCleanup(path: string); export default async function modulesCleanup(path: string);
} }

View file

@ -1,3 +1,3 @@
export interface ServiceClass { export interface ServiceClass {
cli: () => Promise<boolean|undefined|void> cli: () => Promise<boolean|undefined|void>
} }

View file

@ -1,28 +1,28 @@
// Generated by https://quicktype.io // Generated by https://quicktype.io
export interface StreamData { export interface StreamData {
items: Item[]; items: Item[];
watchHistorySaveInterval: number; watchHistorySaveInterval: number;
errors?: Error[] errors?: Error[]
} }
export interface Error { export interface Error {
detail: string, detail: string,
code: number code: number
} }
export interface Item { export interface Item {
src: string; src: string;
kind: string; kind: string;
isPromo: boolean; isPromo: boolean;
videoType: string; videoType: string;
aips: Aip[]; aips: Aip[];
experienceId: string; experienceId: string;
showAds: boolean; showAds: boolean;
id: number; id: number;
} }
export interface Aip { export interface Aip {
out: number; out: number;
in: number; in: number;
} }

View file

@ -1,4 +1,4 @@
export type UpdateFile = { export type UpdateFile = {
lastCheck: number, lastCheck: number,
nextCheck: number nextCheck: number
} }

62
@types/ws.d.ts vendored
View file

@ -2,44 +2,44 @@ import { GUIConfig } from '../modules/module.cfg-loader';
import { AuthResponse, CheckTokenResponse, EpisodeListResponse, FolderTypes, QueueItem, ResolveItemsData, SearchData, SearchResponse } from './messageHandler'; import { AuthResponse, CheckTokenResponse, EpisodeListResponse, FolderTypes, QueueItem, ResolveItemsData, SearchData, SearchResponse } from './messageHandler';
export type WSMessage<T extends keyof MessageTypes, P extends 0|1 = 0> = { export type WSMessage<T extends keyof MessageTypes, P extends 0|1 = 0> = {
name: T, name: T,
data: MessageTypes[T][P] data: MessageTypes[T][P]
} }
export type WSMessageWithID<T extends keyof MessageTypes, P extends 0|1 = 0> = WSMessage<T, P> & { export type WSMessageWithID<T extends keyof MessageTypes, P extends 0|1 = 0> = WSMessage<T, P> & {
id: string id: string
} }
export type UnknownWSMessage = { export type UnknownWSMessage = {
name: keyof MessageTypes, name: keyof MessageTypes,
data: MessageTypes[keyof MessageTypes][0], data: MessageTypes[keyof MessageTypes][0],
id: string id: string
} }
export type MessageTypes = { export type MessageTypes = {
'auth': [AuthData, AuthResponse], 'auth': [AuthData, AuthResponse],
'version': [undefined, string], 'version': [undefined, string],
'checkToken': [undefined, CheckTokenResponse], 'checkToken': [undefined, CheckTokenResponse],
'search': [SearchData, SearchResponse], 'search': [SearchData, SearchResponse],
'default': [string, unknown], 'default': [string, unknown],
'availableDubCodes': [undefined, string[]], 'availableDubCodes': [undefined, string[]],
'availableSubCodes': [undefined, string[]], 'availableSubCodes': [undefined, string[]],
'resolveItems': [ResolveItemsData, boolean], 'resolveItems': [ResolveItemsData, boolean],
'listEpisodes': [string, EpisodeListResponse], 'listEpisodes': [string, EpisodeListResponse],
'downloadItem': [QueueItem, undefined], 'downloadItem': [QueueItem, undefined],
'isDownloading': [undefined, boolean], 'isDownloading': [undefined, boolean],
'openFolder': [FolderTypes, undefined], 'openFolder': [FolderTypes, undefined],
'changeProvider': [undefined, boolean], 'changeProvider': [undefined, boolean],
'type': [undefined, 'crunchy'|'hidive'|'ao'|'adn'|undefined], 'type': [undefined, 'crunchy'|'hidive'|'ao'|'adn'|undefined],
'setup': ['crunchy'|'hidive'|'ao'|'adn'|undefined, undefined], 'setup': ['crunchy'|'hidive'|'ao'|'adn'|undefined, undefined],
'openFile': [[FolderTypes, string], undefined], 'openFile': [[FolderTypes, string], undefined],
'openURL': [string, undefined], 'openURL': [string, undefined],
'isSetup': [undefined, boolean], 'isSetup': [undefined, boolean],
'setupServer': [GUIConfig, boolean], 'setupServer': [GUIConfig, boolean],
'requirePassword': [undefined, boolean], 'requirePassword': [undefined, boolean],
'getQueue': [undefined, QueueItem[]], 'getQueue': [undefined, QueueItem[]],
'removeFromQueue': [number, undefined], 'removeFromQueue': [number, undefined],
'clearQueue': [undefined, undefined], 'clearQueue': [undefined, undefined],
'setDownloadQueue': [boolean, undefined], 'setDownloadQueue': [boolean, undefined],
'getDownloadQueue': [undefined, boolean] 'getDownloadQueue': [undefined, boolean]
} }

View file

@ -56,8 +56,10 @@ export default tseslint.config(
files: ['gui/react/**/*'], files: ['gui/react/**/*'],
rules: { rules: {
'no-console': 0, 'no-console': 0,
// Disabled because ESLint bugs around on .tsx files somehow? 'indent': [
indent: 'off' 'error',
4
],
} }
} }
); );

View file

@ -1,3 +1,3 @@
{ {
"presets": ["@babel/preset-env","@babel/preset-react", "@babel/preset-typescript"] "presets": ["@babel/preset-env","@babel/preset-react", "@babel/preset-typescript"]
} }

View file

@ -1,57 +1,57 @@
{ {
"name": "anidl-gui", "name": "anidl-gui",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.1.2", "@mui/icons-material": "^7.1.2",
"@mui/lab": "7.0.0-beta.12", "@mui/lab": "7.0.0-beta.12",
"@mui/material": "^7.1.2", "@mui/material": "^7.1.2",
"concurrently": "^9.2.0", "concurrently": "^9.2.0",
"notistack": "^3.0.2", "notistack": "^3.0.2",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"ws": "^8.18.2" "ws": "^8.18.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.27.2", "@babel/cli": "^7.27.2",
"@babel/core": "^7.27.4", "@babel/core": "^7.27.4",
"@babel/preset-env": "^7.27.2", "@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1", "@babel/preset-react": "^7.27.1",
"@babel/preset-typescript": "^7.27.1", "@babel/preset-typescript": "^7.27.1",
"@types/node": "^22.15.32", "@types/node": "^22.15.32",
"@types/react": "^19.1.8", "@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.6",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"babel-loader": "^10.0.0", "babel-loader": "^10.0.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3", "html-webpack-plugin": "^5.6.3",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"webpack": "^5.99.9", "webpack": "^5.99.9",
"webpack-cli": "^6.0.1", "webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2" "webpack-dev-server": "^5.2.2"
}, },
"proxy": "http://localhost:3000", "proxy": "http://localhost:3000",
"scripts": { "scripts": {
"build": "npx tsc && npx webpack", "build": "npx tsc && npx webpack",
"start": "npx concurrently -k npm:frontend npm:backend", "start": "npx concurrently -k npm:frontend npm:backend",
"frontend": "npx webpack-dev-server", "frontend": "npx webpack-dev-server",
"backend": "npx ts-node -T ../../gui.ts" "backend": "npx ts-node -T ../../gui.ts"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
">0.2%", ">0.2%",
"not dead", "not dead",
"not op_mini all" "not op_mini all"
], ],
"development": [ "development": [
"last 1 chrome version", "last 1 chrome version",
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
} }
} }

View file

@ -1,15 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Multi Downloader</title> <title>Multi Downloader</title>
<link rel="icon" type="image/webp" href="favicon.webp"> <link rel="icon" type="image/webp" href="favicon.webp">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-eval'" content="script-src 'self' 'unsafe-eval'"
/> />
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View file

@ -1,3 +1,3 @@
type FCWithChildren<T = object> = React.FC<{ type FCWithChildren<T = object> = React.FC<{
children?: React.ReactNode[]|React.ReactNode children?: React.ReactNode[]|React.ReactNode
} & T> } & T>

View file

@ -2,9 +2,9 @@ import React from 'react';
import Layout from './Layout'; import Layout from './Layout';
const App: React.FC = () => { const App: React.FC = () => {
return ( return (
<Layout /> <Layout />
); );
}; };
export default App; export default App;

View file

@ -11,28 +11,28 @@ import MenuBar from './components/MenuBar/MenuBar';
const Layout: React.FC = () => { const Layout: React.FC = () => {
const messageHandler = React.useContext(messageChannelContext); const messageHandler = React.useContext(messageChannelContext);
return <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '100%', alignItems: 'center',}}> return <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '100%', alignItems: 'center',}}>
<MenuBar /> <MenuBar />
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
width: '93vw', width: '93vw',
maxWidth: '93rem', maxWidth: '93rem',
maxHeight: '3rem' maxHeight: '3rem'
//backgroundColor: '#ffffff', //backgroundColor: '#ffffff',
}}> }}>
<LogoutButton /> <LogoutButton />
<AuthButton /> <AuthButton />
<Button variant="contained" startIcon={<Folder />} onClick={() => messageHandler?.openFolder('content')} sx={{ height: '37px' }}>Open Output Directory</Button> <Button variant="contained" startIcon={<Folder />} onClick={() => messageHandler?.openFolder('content')} sx={{ height: '37px' }}>Open Output Directory</Button>
<Button variant="contained" startIcon={<ClearAll />} onClick={() => messageHandler?.clearQueue() } sx={{ height: '37px' }}>Clear Queue</Button> <Button variant="contained" startIcon={<ClearAll />} onClick={() => messageHandler?.clearQueue() } sx={{ height: '37px' }}>Clear Queue</Button>
<AddToQueue /> <AddToQueue />
<StartQueueButton /> <StartQueueButton />
</Box> </Box>
<MainFrame /> <MainFrame />
</Box>; </Box>;
}; };
export default Layout; export default Layout;

View file

@ -2,18 +2,18 @@ import React from 'react';
import { Container, Box, ThemeProvider, createTheme, Theme } from '@mui/material'; import { Container, Box, ThemeProvider, createTheme, Theme } from '@mui/material';
const makeTheme = (mode: 'dark'|'light') : Partial<Theme> => { const makeTheme = (mode: 'dark'|'light') : Partial<Theme> => {
return createTheme({ return createTheme({
palette: { palette: {
mode, mode,
}, },
}); });
}; };
const Style: FCWithChildren = ({children}) => { const Style: FCWithChildren = ({children}) => {
return <ThemeProvider theme={makeTheme('dark')}> return <ThemeProvider theme={makeTheme('dark')}>
<Box sx={{ }}/> <Box sx={{ }}/>
{children} {children}
</ThemeProvider>; </ThemeProvider>;
}; };
export default Style; export default Style;

View file

@ -6,22 +6,22 @@ import EpisodeListing from './DownloadSelector/Listing/EpisodeListing';
import SearchBox from './SearchBox/SearchBox'; import SearchBox from './SearchBox/SearchBox';
const AddToQueue: React.FC = () => { const AddToQueue: React.FC = () => {
const [isOpen, setOpen] = React.useState(false); const [isOpen, setOpen] = React.useState(false);
return <Box> return <Box>
<EpisodeListing /> <EpisodeListing />
<Dialog open={isOpen} onClose={() => setOpen(false)} maxWidth='md' PaperProps={{ elevation:4 }}> <Dialog open={isOpen} onClose={() => setOpen(false)} maxWidth='md' PaperProps={{ elevation:4 }}>
<Box> <Box>
<SearchBox /> <SearchBox />
<Divider variant='middle'/> <Divider variant='middle'/>
<DownloadSelector onFinish={() => setOpen(false)} /> <DownloadSelector onFinish={() => setOpen(false)} />
</Box> </Box>
</Dialog> </Dialog>
<Button variant='contained' onClick={() => setOpen(true)} sx={{ maxHeight: '2.3rem' }}> <Button variant='contained' onClick={() => setOpen(true)} sx={{ maxHeight: '2.3rem' }}>
<Add /> <Add />
Add to Queue Add to Queue
</Button> </Button>
</Box>; </Box>;
}; };
export default AddToQueue; export default AddToQueue;

View file

@ -12,316 +12,316 @@ type DownloadSelectorProps = {
} }
const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => { const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
const messageHandler = React.useContext(messageChannelContext); const messageHandler = React.useContext(messageChannelContext);
const [store, dispatch] = useStore(); const [store, dispatch] = useStore();
const [availableDubs, setAvailableDubs] = React.useState<string[]>([]); const [availableDubs, setAvailableDubs] = React.useState<string[]>([]);
const [availableSubs, setAvailableSubs ] = React.useState<string[]>([]); const [availableSubs, setAvailableSubs ] = React.useState<string[]>([]);
const [ loading, setLoading ] = React.useState(false); const [ loading, setLoading ] = React.useState(false);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const ITEM_HEIGHT = 48; const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8; const ITEM_PADDING_TOP = 8;
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
/* If we don't wait the response is undefined? */ /* If we don't wait the response is undefined? */
await new Promise((resolve) => setTimeout(() => resolve(undefined), 100)); await new Promise((resolve) => setTimeout(() => resolve(undefined), 100));
const dubLang = messageHandler?.handleDefault('dubLang'); const dubLang = messageHandler?.handleDefault('dubLang');
const subLang = messageHandler?.handleDefault('dlsubs'); const subLang = messageHandler?.handleDefault('dlsubs');
const q = messageHandler?.handleDefault('q'); const q = messageHandler?.handleDefault('q');
const fileName = messageHandler?.handleDefault('fileName'); const fileName = messageHandler?.handleDefault('fileName');
const dlVideoOnce = messageHandler?.handleDefault('dlVideoOnce'); const dlVideoOnce = messageHandler?.handleDefault('dlVideoOnce');
const result = await Promise.all([dubLang, subLang, q, fileName, dlVideoOnce]); const result = await Promise.all([dubLang, subLang, q, fileName, dlVideoOnce]);
dispatch({ dispatch({
type: 'downloadOptions', type: 'downloadOptions',
payload: { payload: {
...store.downloadOptions, ...store.downloadOptions,
dubLang: result[0], dubLang: result[0],
dlsubs: result[1], dlsubs: result[1],
q: result[2], q: result[2],
fileName: result[3], fileName: result[3],
dlVideoOnce: result[4], dlVideoOnce: result[4],
}
});
setAvailableDubs(await messageHandler?.availableDubCodes() ?? []);
setAvailableSubs(await messageHandler?.availableSubCodes() ?? []);
})();
}, []);
const addToQueue = async () => {
setLoading(true);
const res = await messageHandler?.resolveItems(store.downloadOptions);
if (!res)
return enqueueSnackbar('The request failed. Please check if the ID is correct.', {
variant: 'error'
});
setLoading(false);
if (onFinish)
onFinish();
};
const listEpisodes = async () => {
if (!store.downloadOptions.id) {
return enqueueSnackbar('Please enter a ID', {
variant: 'error'
});
} }
}); setLoading(true);
setAvailableDubs(await messageHandler?.availableDubCodes() ?? []); const res = await messageHandler?.listEpisodes(store.downloadOptions.id);
setAvailableSubs(await messageHandler?.availableSubCodes() ?? []); if (!res || !res.isOk) {
})(); setLoading(false);
}, []); return enqueueSnackbar('The request failed. Please check if the ID is correct.', {
variant: 'error'
const addToQueue = async () => {
setLoading(true);
const res = await messageHandler?.resolveItems(store.downloadOptions);
if (!res)
return enqueueSnackbar('The request failed. Please check if the ID is correct.', {
variant: 'error'
});
setLoading(false);
if (onFinish)
onFinish();
};
const listEpisodes = async () => {
if (!store.downloadOptions.id) {
return enqueueSnackbar('Please enter a ID', {
variant: 'error'
});
}
setLoading(true);
const res = await messageHandler?.listEpisodes(store.downloadOptions.id);
if (!res || !res.isOk) {
setLoading(false);
return enqueueSnackbar('The request failed. Please check if the ID is correct.', {
variant: 'error'
});
} else {
dispatch({
type: 'episodeListing',
payload: res.value
});
}
setLoading(false);
};
return <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}>
<Box sx={{display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: '5px',
}}>
<Box sx={{
width: '50rem',
height: '21rem',
margin: '10px',
display: 'flex',
justifyContent: 'space-between',
//backgroundColor: '#ffffff30',
}}>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.7rem',
//backgroundColor: '#ff000030'
}}>
<Typography sx={{fontSize: '1.4rem'}}>
General Options
</Typography>
<TextField value={store.downloadOptions.id} required onChange={e => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, id: e.target.value }
}); });
}} label='Show ID'/> } else {
<TextField type='number' value={store.downloadOptions.q} required onChange={e => {
const parsed = parseInt(e.target.value);
if (isNaN(parsed) || parsed < 0 || parsed > 10)
return;
dispatch({ dispatch({
type: 'downloadOptions', type: 'episodeListing',
payload: { ...store.downloadOptions, q: parsed } payload: res.value
}); });
}} label='Quality Level (0 for max)'/> }
<Box sx={{ display: 'flex', gap: '5px' }}> setLoading(false);
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, noaudio: !store.downloadOptions.noaudio } })} variant={store.downloadOptions.noaudio ? 'contained' : 'outlined'}>Skip Audio</Button> };
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, novids: !store.downloadOptions.novids } })} variant={store.downloadOptions.novids ? 'contained' : 'outlined'}>Skip Video</Button>
</Box> return <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}>
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, dlVideoOnce: !store.downloadOptions.dlVideoOnce } })} variant={store.downloadOptions.dlVideoOnce ? 'contained' : 'outlined'}>Skip Unnecessary</Button> <Box sx={{display: 'flex',
<Tooltip title={store.service == 'hidive' ? '' :
<Typography>
Simulcast is only supported on Hidive
</Typography>}
arrow placement='top'
>
<Box>
<Button sx={{ textTransform: 'none'}} disabled={store.service != 'hidive'} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, simul: !store.downloadOptions.simul } })} variant={store.downloadOptions.simul ? 'contained' : 'outlined'}>Download Simulcast ver.</Button>
</Box>
</Tooltip>
</Box>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.7rem',
//backgroundColor: '#00000020'
}}>
<Typography sx={{fontSize: '1.4rem'}}>
Episode Options
</Typography>
<Box sx={{
display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '1px' alignItems: 'center',
}}> margin: '5px',
<Box sx={{
borderColor: '#595959',
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '5px',
//backgroundColor: '#ff4567',
width: '15rem',
height: '3.5rem',
display: 'flex',
'&:hover' : {
borderColor: '#ffffff',
},
}}>
<InputBase sx={{
ml: 2,
flex: 1,
}}
disabled={store.downloadOptions.all} value={store.downloadOptions.e} required onChange={e => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, e: e.target.value }
});
}} placeholder='Episode Select'/>
<Divider orientation='vertical'/>
<LoadingButton loading={loading} disableElevation disableFocusRipple disableRipple disableTouchRipple onClick={listEpisodes} variant='text' sx={{ textTransform: 'none'}}><Typography>List<br/>Episodes</Typography></LoadingButton>
</Box>
</Box>
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, all: !store.downloadOptions.all } })} variant={store.downloadOptions.all ? 'contained' : 'outlined'}>Download All</Button>
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, but: !store.downloadOptions.but } })} variant={store.downloadOptions.but ? 'contained' : 'outlined'}>Download All but</Button>
</Box>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.7rem',
//backgroundColor: '#00ff0020'
}}> }}>
<Typography sx={{fontSize: '1.4rem'}}>
Language Options
</Typography>
<MultiSelect
title='Dub Languages'
values={availableDubs}
selected={store.downloadOptions.dubLang}
onChange={(e) => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, dubLang: e }
});
}}
allOption
/>
<MultiSelect
title='Sub Languages'
values={availableSubs}
selected={store.downloadOptions.dlsubs}
onChange={(e) => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, dlsubs: e }
});
}}
/>
<Tooltip title={store.service == 'crunchy' ? '' :
<Typography>
Hardsubs are only supported on Crunchyroll
</Typography>
}
arrow placement='top'>
<Box sx={{ <Box sx={{
display: 'flex', width: '50rem',
alignItems: 'center', height: '21rem',
justifyContent: 'center', margin: '10px',
width: '100%',
gap: '1rem'
}}>
<Box sx={{
borderRadius: '5px',
//backgroundColor: '#ff4567',
width: '15rem',
height: '3.5rem',
display: 'flex', display: 'flex',
}}> justifyContent: 'space-between',
<FormControl fullWidth> //backgroundColor: '#ffffff30',
<InputLabel id='hsLabel'>Hardsub Language</InputLabel> }}>
<Select <Box sx={{
MenuProps={{ display: 'flex',
PaperProps: { flexDirection: 'column',
style: { alignItems: 'center',
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, gap: '0.7rem',
width: 250 //backgroundColor: '#ff000030'
} }}>
} <Typography sx={{fontSize: '1.4rem'}}>
}} General Options
labelId='hsLabel' </Typography>
label='Hardsub Language' <TextField value={store.downloadOptions.id} required onChange={e => {
disabled={store.service != 'crunchy'} dispatch({
value={store.downloadOptions.hslang} type: 'downloadOptions',
onChange={(e) => { payload: { ...store.downloadOptions, id: e.target.value }
dispatch({ });
type: 'downloadOptions', }} label='Show ID'/>
payload: { ...store.downloadOptions, hslang: (e.target.value as string) === '' ? undefined : e.target.value as string } <TextField type='number' value={store.downloadOptions.q} required onChange={e => {
}); const parsed = parseInt(e.target.value);
}} if (isNaN(parsed) || parsed < 0 || parsed > 10)
> return;
<MenuItem value=''>No Hardsub</MenuItem> dispatch({
{availableSubs.map((lang) => { type: 'downloadOptions',
if(lang === 'all' || lang === 'none') payload: { ...store.downloadOptions, q: parsed }
return undefined; });
return <MenuItem value={lang}>{lang}</MenuItem>; }} label='Quality Level (0 for max)'/>
})} <Box sx={{ display: 'flex', gap: '5px' }}>
</Select> <Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, noaudio: !store.downloadOptions.noaudio } })} variant={store.downloadOptions.noaudio ? 'contained' : 'outlined'}>Skip Audio</Button>
</FormControl> <Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, novids: !store.downloadOptions.novids } })} variant={store.downloadOptions.novids ? 'contained' : 'outlined'}>Skip Video</Button>
</Box> </Box>
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, dlVideoOnce: !store.downloadOptions.dlVideoOnce } })} variant={store.downloadOptions.dlVideoOnce ? 'contained' : 'outlined'}>Skip Unnecessary</Button>
<Tooltip title={store.service == 'hidive' ? '' :
<Typography>
Simulcast is only supported on Hidive
</Typography>}
arrow placement='top'
>
<Box>
<Button sx={{ textTransform: 'none'}} disabled={store.service != 'hidive'} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, simul: !store.downloadOptions.simul } })} variant={store.downloadOptions.simul ? 'contained' : 'outlined'}>Download Simulcast ver.</Button>
</Box>
</Tooltip>
</Box>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.7rem',
//backgroundColor: '#00000020'
}}>
<Typography sx={{fontSize: '1.4rem'}}>
Episode Options
</Typography>
<Box sx={{
display: 'flex',
flexDirection: 'column',
gap: '1px'
}}>
<Box sx={{
borderColor: '#595959',
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '5px',
//backgroundColor: '#ff4567',
width: '15rem',
height: '3.5rem',
display: 'flex',
'&:hover' : {
borderColor: '#ffffff',
},
}}>
<InputBase sx={{
ml: 2,
flex: 1,
}}
disabled={store.downloadOptions.all} value={store.downloadOptions.e} required onChange={e => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, e: e.target.value }
});
}} placeholder='Episode Select'/>
<Divider orientation='vertical'/>
<LoadingButton loading={loading} disableElevation disableFocusRipple disableRipple disableTouchRipple onClick={listEpisodes} variant='text' sx={{ textTransform: 'none'}}><Typography>List<br/>Episodes</Typography></LoadingButton>
</Box>
</Box>
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, all: !store.downloadOptions.all } })} variant={store.downloadOptions.all ? 'contained' : 'outlined'}>Download All</Button>
<Button sx={{ textTransform: 'none'}} onClick={() => dispatch({ type: 'downloadOptions', payload: { ...store.downloadOptions, but: !store.downloadOptions.but } })} variant={store.downloadOptions.but ? 'contained' : 'outlined'}>Download All but</Button>
</Box>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.7rem',
//backgroundColor: '#00ff0020'
}}>
<Typography sx={{fontSize: '1.4rem'}}>
Language Options
</Typography>
<MultiSelect
title='Dub Languages'
values={availableDubs}
selected={store.downloadOptions.dubLang}
onChange={(e) => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, dubLang: e }
});
}}
allOption
/>
<MultiSelect
title='Sub Languages'
values={availableSubs}
selected={store.downloadOptions.dlsubs}
onChange={(e) => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, dlsubs: e }
});
}}
/>
<Tooltip title={store.service == 'crunchy' ? '' :
<Typography>
Hardsubs are only supported on Crunchyroll
</Typography>
}
arrow placement='top'>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
gap: '1rem'
}}>
<Box sx={{
borderRadius: '5px',
//backgroundColor: '#ff4567',
width: '15rem',
height: '3.5rem',
display: 'flex',
}}>
<FormControl fullWidth>
<InputLabel id='hsLabel'>Hardsub Language</InputLabel>
<Select
MenuProps={{
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
}
}}
labelId='hsLabel'
label='Hardsub Language'
disabled={store.service != 'crunchy'}
value={store.downloadOptions.hslang}
onChange={(e) => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, hslang: (e.target.value as string) === '' ? undefined : e.target.value as string }
});
}}
>
<MenuItem value=''>No Hardsub</MenuItem>
{availableSubs.map((lang) => {
if(lang === 'all' || lang === 'none')
return undefined;
return <MenuItem value={lang}>{lang}</MenuItem>;
})}
</Select>
</FormControl>
</Box>
<Tooltip title={ <Tooltip title={
<Typography> <Typography>
Downloads the hardsub version of the selected subtitle.<br/>Subtitles are displayed <b>PERMANENTLY!</b><br/>You can choose only <b>1</b> subtitle per video! Downloads the hardsub version of the selected subtitle.<br/>Subtitles are displayed <b>PERMANENTLY!</b><br/>You can choose only <b>1</b> subtitle per video!
</Typography> </Typography>
} arrow placement='top'> } arrow placement='top'>
<InfoOutlinedIcon sx={{ <InfoOutlinedIcon sx={{
transition: '100ms', transition: '100ms',
ml: '0.35rem', ml: '0.35rem',
mr: '0.65rem', mr: '0.65rem',
'&:hover' : { '&:hover' : {
color: '#ffffff30', color: '#ffffff30',
} }
}} /> }} />
</Tooltip> </Tooltip>
</Box>
</Tooltip>
</Box>
</Box> </Box>
</Tooltip> <Box sx={{width: '95%', height: '0.3rem', backgroundColor: '#ffffff50', borderRadius: '10px', marginBottom: '20px'}}/>
</Box> <Box sx={{
</Box> display: 'flex',
<Box sx={{width: '95%', height: '0.3rem', backgroundColor: '#ffffff50', borderRadius: '10px', marginBottom: '20px'}}/> alignItems: 'center',
<Box sx={{ justifyContent: 'center',
display: 'flex', width: '100%',
alignItems: 'center', gap: '15px'
justifyContent: 'center', }}>
width: '100%', <TextField value={store.downloadOptions.fileName} onChange={e => {
gap: '15px' dispatch({
}}> type: 'downloadOptions',
<TextField value={store.downloadOptions.fileName} onChange={e => { payload: { ...store.downloadOptions, fileName: e.target.value }
dispatch({ });
type: 'downloadOptions', }} sx={{ width: '87%' }} label='Filename Overwrite' />
payload: { ...store.downloadOptions, fileName: e.target.value } <Tooltip title={
}); <Typography>
}} sx={{ width: '87%' }} label='Filename Overwrite' />
<Tooltip title={
<Typography>
Click here to see the documentation Click here to see the documentation
</Typography> </Typography>
} arrow placement='top'> } arrow placement='top'>
<Link href='https://github.com/anidl/multi-downloader-nx/blob/master/docs/DOCUMENTATION.md#filename-template' rel="noopener noreferrer" target="_blank"> <Link href='https://github.com/anidl/multi-downloader-nx/blob/master/docs/DOCUMENTATION.md#filename-template' rel="noopener noreferrer" target="_blank">
<InfoOutlinedIcon sx={{ <InfoOutlinedIcon sx={{
transition: '100ms', transition: '100ms',
'&:hover' : { '&:hover' : {
color: '#ffffff30', color: '#ffffff30',
} }
}} /> }} />
</Link> </Link>
</Tooltip> </Tooltip>
</Box> </Box>
</Box> </Box>
<Box sx={{width: '95%', height: '0.3rem', backgroundColor: '#ffffff50', borderRadius: '10px', marginTop: '10px'}}/> <Box sx={{width: '95%', height: '0.3rem', backgroundColor: '#ffffff50', borderRadius: '10px', marginTop: '10px'}}/>
<LoadingButton sx={{ margin: '15px', textTransform: 'none' }} loading={loading} onClick={addToQueue} variant='contained'>Add to Queue</LoadingButton> <LoadingButton sx={{ margin: '15px', textTransform: 'none' }} loading={loading} onClick={addToQueue} variant='contained'>Add to Queue</LoadingButton>
</Box>; </Box>;
}; };
export default DownloadSelector; export default DownloadSelector;

View file

@ -7,185 +7,185 @@ import { useSnackbar } from 'notistack';
const EpisodeListing: React.FC = () => { const EpisodeListing: React.FC = () => {
const [store, dispatch] = useStore(); const [store, dispatch] = useStore();
const [season, setSeason] = React.useState<'all'|string>('all'); const [season, setSeason] = React.useState<'all'|string>('all');
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const seasons = React.useMemo(() => { const seasons = React.useMemo(() => {
const s: string[] = []; const s: string[] = [];
for (const {season} of store.episodeListing) { for (const {season} of store.episodeListing) {
if (s.includes(season)) if (s.includes(season))
continue; continue;
s.push(season); s.push(season);
} }
return s; return s;
}, [ store.episodeListing ]); }, [ store.episodeListing ]);
const [selected, setSelected] = React.useState<string[]>([]); const [selected, setSelected] = React.useState<string[]>([]);
React.useEffect(() => { React.useEffect(() => {
setSelected(parseSelect(store.downloadOptions.e)); setSelected(parseSelect(store.downloadOptions.e));
}, [ store.episodeListing ]); }, [ store.episodeListing ]);
const close = () => { const close = () => {
dispatch({ dispatch({
type: 'episodeListing', type: 'episodeListing',
payload: [] payload: []
}); });
dispatch({ dispatch({
type: 'downloadOptions', type: 'downloadOptions',
payload: { payload: {
...store.downloadOptions, ...store.downloadOptions,
e: `${([...new Set([...parseSelect(store.downloadOptions.e), ...selected])]).join(',')}` e: `${([...new Set([...parseSelect(store.downloadOptions.e), ...selected])]).join(',')}`
} }
}); });
}; };
const getEpisodesForSeason = (season: string|'all') => { const getEpisodesForSeason = (season: string|'all') => {
return store.episodeListing.filter((a) => season === 'all' ? true : a.season === season); return store.episodeListing.filter((a) => season === 'all' ? true : a.season === season);
}; };
return <Dialog open={store.episodeListing.length > 0} onClose={close} scroll='paper' maxWidth='xl' sx={{ p: 2 }}> return <Dialog open={store.episodeListing.length > 0} onClose={close} scroll='paper' maxWidth='xl' sx={{ p: 2 }}>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 200px 20px' }}> <Box sx={{ display: 'grid', gridTemplateColumns: '1fr 200px 20px' }}>
<Typography color='text.primary' variant="h5" sx={{ textAlign: 'center', alignItems: 'center', justifyContent: 'center', display: 'flex' }}> <Typography color='text.primary' variant="h5" sx={{ textAlign: 'center', alignItems: 'center', justifyContent: 'center', display: 'flex' }}>
Episodes Episodes
</Typography>
<FormControl sx={{ mr: 2, mt: 2 }}>
<InputLabel id='seasonSelectLabel'>Season</InputLabel>
<Select labelId="seasonSelectLabel" label='Season' value={season} onChange={(e) => setSeason(e.target.value)}>
<MenuItem value='all'>Show all Epsiodes</MenuItem>
{seasons.map((a, index) => {
return <MenuItem value={a} key={`MenuItem_SeasonSelect_${index}`}>
{a}
</MenuItem>;
})}
</Select>
</FormControl>
</Box>
<List>
<ListItem sx={{ display: 'grid', gridTemplateColumns: '25px 1fr 5fr' }}>
<Checkbox
indeterminate={store.episodeListing.some(a => selected.includes(a.e)) && !store.episodeListing.every(a => selected.includes(a.e))}
checked={store.episodeListing.every(a => selected.includes(a.e))}
onChange={() => {
if (selected.length > 0) {
setSelected([]);
} else {
setSelected(getEpisodesForSeason(season).map(a => a.e));
}
}}
/>
</ListItem>
{getEpisodesForSeason(season).map((item, index, { length }) => {
const e = isNaN(parseInt(item.e)) ? item.e : parseInt(item.e);
const idStr = `S${item.season}E${e}`;
const isSelected = selected.includes(e.toString());
const imageRef = React.createRef<HTMLImageElement>();
const summaryRef = React.createRef<HTMLParagraphElement>();
return <Box {...{ mouseData: isSelected }} key={`Episode_List_Item_${index}`}>
<ListItem sx={{backdropFilter: isSelected ? 'brightness(1.5)' : '', '&:hover': {backdropFilter: 'brightness(1.5)'}, display: 'grid', gridTemplateColumns: '25px 50px 1fr 5fr' }}
onClick={() => {
let arr: string[] = [];
if (isSelected) {
arr = [...selected.filter(a => a !== e.toString())];
} else {
arr = [...selected, e.toString()];
}
setSelected(arr.filter(a => a.length > 0));
}}>
{ isSelected ? <CheckBox /> : <CheckBoxOutlineBlank /> }
<Typography color='text.primary' sx={{ textAlign: 'center' }}>
{idStr}
</Typography> </Typography>
<img ref={imageRef} style={{ width: 'inherit', maxHeight: '200px', minWidth: '150px' }} src={item.img} alt="thumbnail" /> <FormControl sx={{ mr: 2, mt: 2 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', pl: 1 }}> <InputLabel id='seasonSelectLabel'>Season</InputLabel>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr min-content' }}> <Select labelId="seasonSelectLabel" label='Season' value={season} onChange={(e) => setSeason(e.target.value)}>
<Typography color='text.primary' variant="h5"> <MenuItem value='all'>Show all Epsiodes</MenuItem>
{item.name} {seasons.map((a, index) => {
</Typography> return <MenuItem value={a} key={`MenuItem_SeasonSelect_${index}`}>
<Typography color='text.primary'> {a}
{item.time.startsWith('00:') ? item.time.slice(3) : item.time} </MenuItem>;
</Typography> })}
</Box> </Select>
<Typography color='text.primary' ref={summaryRef}> </FormControl>
{item.description} </Box>
</Typography> <List>
<Box sx={{ display: 'grid', gridTemplateColumns: 'fit-content 1fr' }}> <ListItem sx={{ display: 'grid', gridTemplateColumns: '25px 1fr 5fr' }}>
<Typography> <Checkbox
<br /> indeterminate={store.episodeListing.some(a => selected.includes(a.e)) && !store.episodeListing.every(a => selected.includes(a.e))}
checked={store.episodeListing.every(a => selected.includes(a.e))}
onChange={() => {
if (selected.length > 0) {
setSelected([]);
} else {
setSelected(getEpisodesForSeason(season).map(a => a.e));
}
}}
/>
</ListItem>
{getEpisodesForSeason(season).map((item, index, { length }) => {
const e = isNaN(parseInt(item.e)) ? item.e : parseInt(item.e);
const idStr = `S${item.season}E${e}`;
const isSelected = selected.includes(e.toString());
const imageRef = React.createRef<HTMLImageElement>();
const summaryRef = React.createRef<HTMLParagraphElement>();
return <Box {...{ mouseData: isSelected }} key={`Episode_List_Item_${index}`}>
<ListItem sx={{backdropFilter: isSelected ? 'brightness(1.5)' : '', '&:hover': {backdropFilter: 'brightness(1.5)'}, display: 'grid', gridTemplateColumns: '25px 50px 1fr 5fr' }}
onClick={() => {
let arr: string[] = [];
if (isSelected) {
arr = [...selected.filter(a => a !== e.toString())];
} else {
arr = [...selected, e.toString()];
}
setSelected(arr.filter(a => a.length > 0));
}}>
{ isSelected ? <CheckBox /> : <CheckBoxOutlineBlank /> }
<Typography color='text.primary' sx={{ textAlign: 'center' }}>
{idStr}
</Typography>
<img ref={imageRef} style={{ width: 'inherit', maxHeight: '200px', minWidth: '150px' }} src={item.img} alt="thumbnail" />
<Box sx={{ display: 'flex', flexDirection: 'column', pl: 1 }}>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr min-content' }}>
<Typography color='text.primary' variant="h5">
{item.name}
</Typography>
<Typography color='text.primary'>
{item.time.startsWith('00:') ? item.time.slice(3) : item.time}
</Typography>
</Box>
<Typography color='text.primary' ref={summaryRef}>
{item.description}
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: 'fit-content 1fr' }}>
<Typography>
<br />
Available audio languages: {item.lang.join(', ')} Available audio languages: {item.lang.join(', ')}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
</ListItem> </ListItem>
<ContextMenu options={[ { text: 'Copy image URL', onClick: async () => { <ContextMenu options={[ { text: 'Copy image URL', onClick: async () => {
await navigator.clipboard.writeText(item.img); await navigator.clipboard.writeText(item.img);
enqueueSnackbar('Copied URL to clipboard', { enqueueSnackbar('Copied URL to clipboard', {
variant: 'info' variant: 'info'
}); });
}}, }},
{ {
text: 'Open image in new tab', text: 'Open image in new tab',
onClick: () => { onClick: () => {
window.open(item.img); window.open(item.img);
} }
} ]} popupItem={imageRef as RefObject<HTMLElement>} /> } ]} popupItem={imageRef as RefObject<HTMLElement>} />
<ContextMenu options={[ <ContextMenu options={[
{ {
onClick: async () => { onClick: async () => {
await navigator.clipboard.writeText(item.description!); await navigator.clipboard.writeText(item.description!);
enqueueSnackbar('Copied summary to clipboard', { enqueueSnackbar('Copied summary to clipboard', {
variant: 'info' variant: 'info'
}); });
}, },
text: 'Copy summary to clipboard' text: 'Copy summary to clipboard'
} }
]} popupItem={summaryRef as RefObject<HTMLElement>} /> ]} popupItem={summaryRef as RefObject<HTMLElement>} />
{index < length - 1 && <Divider />} {index < length - 1 && <Divider />}
</Box>; </Box>;
})} })}
</List> </List>
</Dialog>; </Dialog>;
}; };
const parseSelect = (s: string): string[] => { const parseSelect = (s: string): string[] => {
const ret: string[] = []; const ret: string[] = [];
s.split(',').forEach(item => { s.split(',').forEach(item => {
if (item.includes('-')) { if (item.includes('-')) {
const split = item.split('-'); const split = item.split('-');
if (split.length !== 2) if (split.length !== 2)
return; return;
const match = split[0].match(/[A-Za-z]+/); const match = split[0].match(/[A-Za-z]+/);
if (match && match.length > 0) { if (match && match.length > 0) {
if (match.index && match.index !== 0) { if (match.index && match.index !== 0) {
return; return;
} }
const letters = split[0].substring(0, match[0].length); const letters = split[0].substring(0, match[0].length);
const number = parseInt(split[0].substring(match[0].length)); const number = parseInt(split[0].substring(match[0].length));
const b = parseInt(split[1]); const b = parseInt(split[1]);
if (isNaN(number) || isNaN(b)) { if (isNaN(number) || isNaN(b)) {
return; return;
} }
for (let i = number; i <= b; i++) { for (let i = number; i <= b; i++) {
ret.push(`${letters}${i}`); ret.push(`${letters}${i}`);
} }
} else { } else {
const a = parseInt(split[0]); const a = parseInt(split[0]);
const b = parseInt(split[1]); const b = parseInt(split[1]);
if (isNaN(a) || isNaN(b)) { if (isNaN(a) || isNaN(b)) {
return; return;
}
for (let i = a; i <= b; i++) {
ret.push(`${i}`);
}
}
} else {
ret.push(item);
} }
for (let i = a; i <= b; i++) { });
ret.push(`${i}`); return [...new Set(ret)];
}
}
} else {
ret.push(item);
}
});
return [...new Set(ret)];
}; };
export default EpisodeListing; export default EpisodeListing;

View file

@ -8,112 +8,112 @@ import ContextMenu from '../../reusable/ContextMenu';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
const SearchBox: React.FC = () => { const SearchBox: React.FC = () => {
const messageHandler = React.useContext(messageChannelContext); const messageHandler = React.useContext(messageChannelContext);
const [store, dispatch] = useStore(); const [store, dispatch] = useStore();
const [search, setSearch] = React.useState(''); const [search, setSearch] = React.useState('');
const [focus, setFocus] = React.useState(false); const [focus, setFocus] = React.useState(false);
const [searchResult, setSearchResult] = React.useState<undefined|SearchResponse>(); const [searchResult, setSearchResult] = React.useState<undefined|SearchResponse>();
const anchor = React.useRef<HTMLDivElement>(null); const anchor = React.useRef<HTMLDivElement>(null);
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const selectItem = (id: string) => { const selectItem = (id: string) => {
dispatch({ dispatch({
type: 'downloadOptions', type: 'downloadOptions',
payload: { payload: {
...store.downloadOptions, ...store.downloadOptions,
id id
} }
}); });
}; };
React.useEffect(() => { React.useEffect(() => {
if (search.trim().length === 0) if (search.trim().length === 0)
return setSearchResult({ isOk: true, value: [] }); return setSearchResult({ isOk: true, value: [] });
const timeOutId = setTimeout(async () => { const timeOutId = setTimeout(async () => {
if (search.trim().length > 3) { if (search.trim().length > 3) {
const s = await messageHandler?.search({search}); const s = await messageHandler?.search({search});
if (s && s.isOk) if (s && s.isOk)
s.value = s.value.slice(0, 10); s.value = s.value.slice(0, 10);
setSearchResult(s); setSearchResult(s);
} }
}, 500); }, 500);
return () => clearTimeout(timeOutId); return () => clearTimeout(timeOutId);
}, [search]); }, [search]);
const anchorBounding = anchor.current?.getBoundingClientRect(); const anchorBounding = anchor.current?.getBoundingClientRect();
return <ClickAwayListener onClickAway={() => setFocus(false)}> return <ClickAwayListener onClickAway={() => setFocus(false)}>
<Box sx={{ m: 2 }}> <Box sx={{ m: 2 }}>
<TextField ref={anchor} value={search} onClick={() => setFocus(true)} onChange={e => setSearch(e.target.value)} variant='outlined' label='Search' fullWidth /> <TextField ref={anchor} value={search} onClick={() => setFocus(true)} onChange={e => setSearch(e.target.value)} variant='outlined' label='Search' fullWidth />
{searchResult !== undefined && searchResult.isOk && searchResult.value.length > 0 && focus && {searchResult !== undefined && searchResult.isOk && searchResult.value.length > 0 && focus &&
<Paper sx={{ position: 'fixed', maxHeight: '50%', width: `${anchorBounding?.width}px`, <Paper sx={{ position: 'fixed', maxHeight: '50%', width: `${anchorBounding?.width}px`,
left: anchorBounding?.x, top: (anchorBounding?.y ?? 0) + (anchorBounding?.height ?? 0), zIndex: 99, overflowY: 'scroll'}}> left: anchorBounding?.x, top: (anchorBounding?.y ?? 0) + (anchorBounding?.height ?? 0), zIndex: 99, overflowY: 'scroll'}}>
<List> <List>
{searchResult && searchResult.isOk ? {searchResult && searchResult.isOk ?
searchResult.value.map((a, ind, arr) => { searchResult.value.map((a, ind, arr) => {
const imageRef = React.createRef<HTMLImageElement>(); const imageRef = React.createRef<HTMLImageElement>();
const summaryRef = React.createRef<HTMLParagraphElement>(); const summaryRef = React.createRef<HTMLParagraphElement>();
return <Box key={a.id}> return <Box key={a.id}>
<ListItem className='listitem-hover' onClick={() => { <ListItem className='listitem-hover' onClick={() => {
selectItem(a.id); selectItem(a.id);
setFocus(false); setFocus(false);
}}> }}>
<Box sx={{ display: 'flex' }}> <Box sx={{ display: 'flex' }}>
<Box sx={{ width: '20%', height: '100%', pr: 2 }}> <Box sx={{ width: '20%', height: '100%', pr: 2 }}>
<img ref={imageRef} src={a.image} style={{ width: '100%', height: 'auto' }} alt="thumbnail"/> <img ref={imageRef} src={a.image} style={{ width: '100%', height: 'auto' }} alt="thumbnail"/>
</Box> </Box>
<Box sx={{ display: 'flex', flexDirection: 'column', maxWidth: '70%' }}> <Box sx={{ display: 'flex', flexDirection: 'column', maxWidth: '70%' }}>
<Typography variant='h6' component='h6' color='text.primary' sx={{ }}> <Typography variant='h6' component='h6' color='text.primary' sx={{ }}>
{a.name} {a.name}
</Typography> </Typography>
{a.desc && <Typography variant='caption' component='p' color='text.primary' sx={{ pt: 1, pb: 1 }} ref={summaryRef}> {a.desc && <Typography variant='caption' component='p' color='text.primary' sx={{ pt: 1, pb: 1 }} ref={summaryRef}>
{a.desc} {a.desc}
</Typography>} </Typography>}
{a.lang && <Typography variant='caption' component='p' color='text.primary' sx={{ }}> {a.lang && <Typography variant='caption' component='p' color='text.primary' sx={{ }}>
Languages: {a.lang.join(', ')} Languages: {a.lang.join(', ')}
</Typography>} </Typography>}
<Typography variant='caption' component='p' color='text.primary' sx={{ }}> <Typography variant='caption' component='p' color='text.primary' sx={{ }}>
ID: {a.id} ID: {a.id}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
</ListItem> </ListItem>
<ContextMenu options={[ { text: 'Copy image URL', onClick: async () => { <ContextMenu options={[ { text: 'Copy image URL', onClick: async () => {
await navigator.clipboard.writeText(a.image); await navigator.clipboard.writeText(a.image);
enqueueSnackbar('Copied URL to clipboard', { enqueueSnackbar('Copied URL to clipboard', {
variant: 'info' variant: 'info'
}); });
}}, }},
{ {
text: 'Open image in new tab', text: 'Open image in new tab',
onClick: () => { onClick: () => {
window.open(a.image); window.open(a.image);
} }
} ]} popupItem={imageRef as RefObject<HTMLElement>} /> } ]} popupItem={imageRef as RefObject<HTMLElement>} />
{a.desc && {a.desc &&
<ContextMenu options={[ <ContextMenu options={[
{ {
onClick: async () => { onClick: async () => {
await navigator.clipboard.writeText(a.desc!); await navigator.clipboard.writeText(a.desc!);
enqueueSnackbar('Copied summary to clipboard', { enqueueSnackbar('Copied summary to clipboard', {
variant: 'info' variant: 'info'
}); });
}, },
text: 'Copy summary to clipboard' text: 'Copy summary to clipboard'
} }
]} popupItem={summaryRef as RefObject<HTMLElement>} /> ]} popupItem={summaryRef as RefObject<HTMLElement>} />
} }
{(ind < arr.length - 1) && <Divider />} {(ind < arr.length - 1) && <Divider />}
</Box>; </Box>;
}) })
: <></>} : <></>}
</List> </List>
</Paper>} </Paper>}
</Box> </Box>
</ClickAwayListener>; </ClickAwayListener>;
}; };
export default SearchBox; export default SearchBox;

View file

@ -6,107 +6,107 @@ import Require from './Require';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
const AuthButton: React.FC = () => { const AuthButton: React.FC = () => {
const snackbar = useSnackbar(); const snackbar = useSnackbar();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [username, setUsername] = React.useState(''); const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState(''); const [password, setPassword] = React.useState('');
const [usernameError, setUsernameError] = React.useState(false); const [usernameError, setUsernameError] = React.useState(false);
const [passwordError, setPasswordError] = React.useState(false); const [passwordError, setPasswordError] = React.useState(false);
const messageChannel = React.useContext(messageChannelContext); const messageChannel = React.useContext(messageChannelContext);
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<Error|undefined>(undefined); const [error, setError] = React.useState<Error|undefined>(undefined);
const [authed, setAuthed] = React.useState(false); const [authed, setAuthed] = React.useState(false);
const checkAuth = async () => { const checkAuth = async () => {
setAuthed((await messageChannel?.checkToken())?.isOk ?? false); setAuthed((await messageChannel?.checkToken())?.isOk ?? false);
}; };
React.useEffect(() => { checkAuth(); }, []); React.useEffect(() => { checkAuth(); }, []);
const handleSubmit = async () => { const handleSubmit = async () => {
if (!messageChannel) if (!messageChannel)
throw new Error('Invalid state'); //The components to confirm only render if the messageChannel is not undefinded throw new Error('Invalid state'); //The components to confirm only render if the messageChannel is not undefinded
if (username.trim().length === 0) if (username.trim().length === 0)
return setUsernameError(true); return setUsernameError(true);
if (password.trim().length === 0) if (password.trim().length === 0)
return setPasswordError(true); return setPasswordError(true);
setUsernameError(false); setUsernameError(false);
setPasswordError(false); setPasswordError(false);
setLoading(true); setLoading(true);
const res = await messageChannel.auth({ username, password }); const res = await messageChannel.auth({ username, password });
if (res.isOk) { if (res.isOk) {
setOpen(false); setOpen(false);
snackbar.enqueueSnackbar('Logged in', { snackbar.enqueueSnackbar('Logged in', {
variant: 'success' variant: 'success'
}); });
setUsername(''); setUsername('');
setPassword(''); setPassword('');
} else { } else {
setError(res.reason); setError(res.reason);
} }
await checkAuth(); await checkAuth();
setLoading(false); setLoading(false);
}; };
return <Require value={messageChannel}> return <Require value={messageChannel}>
<Dialog open={open}> <Dialog open={open}>
<Dialog open={!!error}> <Dialog open={!!error}>
<DialogTitle>Error during Authentication</DialogTitle> <DialogTitle>Error during Authentication</DialogTitle>
<DialogContentText> <DialogContentText>
{error?.name} {error?.name}
{error?.message} {error?.message}
</DialogContentText> </DialogContentText>
<DialogActions> <DialogActions>
<Button onClick={() => setError(undefined)}>Close</Button> <Button onClick={() => setError(undefined)}>Close</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
<DialogTitle sx={{ flexGrow: 1 }}>Authentication</DialogTitle> <DialogTitle sx={{ flexGrow: 1 }}>Authentication</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText> <DialogContentText>
Here, you need to enter your username (most likely your Email) and your password.<br /> Here, you need to enter your username (most likely your Email) and your password.<br />
These information are not stored anywhere and are only used to authenticate with the service once. These information are not stored anywhere and are only used to authenticate with the service once.
</DialogContentText> </DialogContentText>
<TextField <TextField
error={usernameError} error={usernameError}
helperText={usernameError ? 'Please enter something before submiting' : undefined} helperText={usernameError ? 'Please enter something before submiting' : undefined}
margin="dense" margin="dense"
id="username" id="username"
label="Username" label="Username"
type="text" type="text"
fullWidth fullWidth
variant="standard" variant="standard"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
disabled={loading} disabled={loading}
/> />
<TextField <TextField
error={passwordError} error={passwordError}
helperText={passwordError ? 'Please enter something before submiting' : undefined} helperText={passwordError ? 'Please enter something before submiting' : undefined}
margin="dense" margin="dense"
id="password" id="password"
label="Password" label="Password"
type="password" type="password"
fullWidth fullWidth
variant="standard" variant="standard"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
disabled={loading} disabled={loading}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
{loading && <CircularProgress size={30}/>} {loading && <CircularProgress size={30}/>}
<Button disabled={loading} onClick={() => setOpen(false)}>Close</Button> <Button disabled={loading} onClick={() => setOpen(false)}>Close</Button>
<Button disabled={loading} onClick={() => handleSubmit()}>Authenticate</Button> <Button disabled={loading} onClick={() => handleSubmit()}>Authenticate</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
<Button startIcon={authed ? <Check />: <Close />} variant="contained" onClick={() => setOpen(true)}>Authenticate</Button> <Button startIcon={authed ? <Check />: <Close />} variant="contained" onClick={() => setOpen(true)}>Authenticate</Button>
</Require>; </Require>;
}; };
export default AuthButton; export default AuthButton;

View file

@ -6,31 +6,31 @@ import { messageChannelContext } from '../provider/MessageChannel';
import Require from './Require'; import Require from './Require';
const LogoutButton: React.FC = () => { const LogoutButton: React.FC = () => {
const messageChannel = React.useContext(messageChannelContext); const messageChannel = React.useContext(messageChannelContext);
const [, dispatch] = useStore(); const [, dispatch] = useStore();
const logout = async () => { const logout = async () => {
if (await messageChannel?.isDownloading()) if (await messageChannel?.isDownloading())
return alert('You are currently downloading. Please finish the download first.'); return alert('You are currently downloading. Please finish the download first.');
if (await messageChannel?.logout()) if (await messageChannel?.logout())
dispatch({ dispatch({
type: 'service', type: 'service',
payload: undefined payload: undefined
}); });
else else
alert('Unable to change service'); alert('Unable to change service');
}; };
return <Require value={messageChannel}> return <Require value={messageChannel}>
<Button <Button
startIcon={<ExitToApp />} startIcon={<ExitToApp />}
variant='contained' variant='contained'
onClick={logout} onClick={logout}
sx={{ maxHeight: '2.3rem' }} sx={{ maxHeight: '2.3rem' }}
> >
Service select Service select
</Button> </Button>
</Require>; </Require>;
}; };

View file

@ -4,37 +4,37 @@ import { RandomEvent } from '../../../../../../@types/randomEvents';
import { messageChannelContext } from '../../../provider/MessageChannel'; import { messageChannelContext } from '../../../provider/MessageChannel';
const useDownloadManager = () => { const useDownloadManager = () => {
const messageHandler = React.useContext(messageChannelContext); const messageHandler = React.useContext(messageChannelContext);
const [progressData, setProgressData] = React.useState<ExtendedProgress|undefined>(); const [progressData, setProgressData] = React.useState<ExtendedProgress|undefined>();
const [current, setCurrent] = React.useState<undefined|QueueItem>(); const [current, setCurrent] = React.useState<undefined|QueueItem>();
React.useEffect(() => { React.useEffect(() => {
const handler = (ev: RandomEvent<'progress'>) => { const handler = (ev: RandomEvent<'progress'>) => {
console.log(ev.data); console.log(ev.data);
setProgressData(ev.data); setProgressData(ev.data);
}; };
const currentHandler = (ev: RandomEvent<'current'>) => { const currentHandler = (ev: RandomEvent<'current'>) => {
setCurrent(ev.data); setCurrent(ev.data);
}; };
const finishHandler = () => { const finishHandler = () => {
setProgressData(undefined); setProgressData(undefined);
}; };
messageHandler?.randomEvents.on('progress', handler); messageHandler?.randomEvents.on('progress', handler);
messageHandler?.randomEvents.on('current', currentHandler); messageHandler?.randomEvents.on('current', currentHandler);
messageHandler?.randomEvents.on('finish', finishHandler); messageHandler?.randomEvents.on('finish', finishHandler);
return () => { return () => {
messageHandler?.randomEvents.removeListener('progress', handler); messageHandler?.randomEvents.removeListener('progress', handler);
messageHandler?.randomEvents.removeListener('finish', finishHandler); messageHandler?.randomEvents.removeListener('finish', finishHandler);
messageHandler?.randomEvents.removeListener('current', currentHandler); messageHandler?.randomEvents.removeListener('current', currentHandler);
}; };
}, [messageHandler]); }, [messageHandler]);
return { data: progressData, current}; return { data: progressData, current};
}; };
export default useDownloadManager; export default useDownloadManager;

View file

@ -3,9 +3,9 @@ import React from 'react';
import Queue from './Queue/Queue'; import Queue from './Queue/Queue';
const MainFrame: React.FC = () => { const MainFrame: React.FC = () => {
return <Box sx={{ }}> return <Box sx={{ }}>
<Queue /> <Queue />
</Box>; </Box>;
}; };
export default MainFrame; export default MainFrame;

View file

@ -7,414 +7,414 @@ import DeleteIcon from '@mui/icons-material/Delete';
import useDownloadManager from '../DownloadManager/DownloadManager'; import useDownloadManager from '../DownloadManager/DownloadManager';
const Queue: React.FC = () => { const Queue: React.FC = () => {
const { data, current } = useDownloadManager(); const { data, current } = useDownloadManager();
const queue = React.useContext(queueContext); const queue = React.useContext(queueContext);
const msg = React.useContext(messageChannelContext); const msg = React.useContext(messageChannelContext);
if (!msg) if (!msg)
return <>Never</>; return <>Never</>;
return data || queue.length > 0 ? <> return data || queue.length > 0 ? <>
{data && <> {data && <>
<Box sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
alignItems: 'center',
}}>
<Box sx={{
marginTop: '2rem',
marginBottom: '1rem',
height: '12rem',
width: '93vw',
maxWidth: '93rem',
backgroundColor: '#282828',
boxShadow: '0px 0px 50px #00000090',
borderRadius: '10px',
display: 'flex',
transition: '250ms'
}}>
<img style={{
borderRadius: '5px',
margin: '5px',
boxShadow: '0px 0px 10px #00000090',
userSelect: 'none',
}}
src={data.downloadInfo.image} height='auto' width='auto' alt="Thumbnail" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'center'
}}>
<Box sx={{ <Box sx={{
display: 'flex',
}}>
<Box sx={{
//backgroundColor: '#ff0000',
width: '70%',
marginLeft: '10px'
}}>
<Box sx={{
flexDirection: 'column',
display: 'flex',
justifyContent: 'space-between',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
{data.downloadInfo.parent.title}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.2rem',
}}>
{data.downloadInfo.title}
</Typography>
</Box>
</Box>
<Box sx={{
//backgroundColor: '#00ff00',
width: '30%',
display: 'flex', display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
Downloading: {data.downloadInfo.language.name}
</Typography>
</Box>
</Box>
<Box sx={{
height: '50%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
//backgroundColor: '#0000ff',
}}>
<LinearProgress variant='determinate'
sx={{
height: '20px',
width: '97.53%',
margin: '10px',
boxShadow: '0px 0px 10px #00000090',
borderRadius: '10px',
}} value={(typeof data.progress.percent === 'string' ? parseInt(data.progress.percent) : data.progress.percent)}
/>
<Box>
<Typography color='text.primary'
sx={{
fontSize: '1.3rem',
}}>
{data.progress.cur} / {(data.progress.total)} parts ({data.progress.percent}% | {formatTime(data.progress.time)} | {(data.progress.downloadSpeed / 1024 / 1024).toFixed(2)} MB/s | {(data.progress.bytes / 1024 / 1024).toFixed(2)}MB)
</Typography>
</Box>
</Box>
</Box>
</Box>
</Box>
</>
}
{
current && !data && <>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}>
<Box sx={{
marginTop: '2rem',
marginBottom: '1rem',
height: '12rem',
width: '93vw',
maxWidth: '93rem',
backgroundColor: '#282828',
boxShadow: '0px 0px 50px #00000090',
borderRadius: '10px',
display: 'flex',
overflow: 'hidden',
transition: '250ms'
}}>
<img style={{
borderRadius: '5px',
margin: '5px',
boxShadow: '0px 0px 10px #00000090',
userSelect: 'none',
maxWidth: '20.5rem',
}}
src={current.image} height='auto' width='auto' alt="Thumbnail" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%', width: '100%',
justifyContent: 'center',
//backgroundColor: '#ffffff0f'
}}>
<Box sx={{
display: 'flex',
}}>
<Box sx={{
width: '70%',
marginLeft: '10px'
}}>
<Box sx={{
flexDirection: 'column',
display: 'flex',
justifyContent: 'space-between',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
{current.parent.title}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.2rem',
}}>
{current.title}
</Typography>
</Box>
</Box>
<Box sx={{
//backgroundColor: '#00ff00',
width: '30%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
position: 'relative',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
Downloading:
</Typography>
<CircularProgress variant="indeterminate" sx={{
marginLeft: '2rem',
}}/>
</Box>
</Box>
</Box>
<Box sx={{
height: '50%',
display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
//backgroundColor: '#0000ff', }}>
}}> <Box sx={{
<LinearProgress variant='indeterminate' marginTop: '2rem',
sx={{ marginBottom: '1rem',
height: '20px', height: '12rem',
width: '97.53%', width: '93vw',
margin: '10px', maxWidth: '93rem',
backgroundColor: '#282828',
boxShadow: '0px 0px 50px #00000090',
borderRadius: '10px',
display: 'flex',
transition: '250ms'
}}>
<img style={{
borderRadius: '5px',
margin: '5px',
boxShadow: '0px 0px 10px #00000090',
userSelect: 'none',
}}
src={data.downloadInfo.image} height='auto' width='auto' alt="Thumbnail" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'center'
}}>
<Box sx={{
display: 'flex',
}}>
<Box sx={{
//backgroundColor: '#ff0000',
width: '70%',
marginLeft: '10px'
}}>
<Box sx={{
flexDirection: 'column',
display: 'flex',
justifyContent: 'space-between',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
{data.downloadInfo.parent.title}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.2rem',
}}>
{data.downloadInfo.title}
</Typography>
</Box>
</Box>
<Box sx={{
//backgroundColor: '#00ff00',
width: '30%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
Downloading: {data.downloadInfo.language.name}
</Typography>
</Box>
</Box>
<Box sx={{
height: '50%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
//backgroundColor: '#0000ff',
}}>
<LinearProgress variant='determinate'
sx={{
height: '20px',
width: '97.53%',
margin: '10px',
boxShadow: '0px 0px 10px #00000090',
borderRadius: '10px',
}} value={(typeof data.progress.percent === 'string' ? parseInt(data.progress.percent) : data.progress.percent)}
/>
<Box>
<Typography color='text.primary'
sx={{
fontSize: '1.3rem',
}}>
{data.progress.cur} / {(data.progress.total)} parts ({data.progress.percent}% | {formatTime(data.progress.time)} | {(data.progress.downloadSpeed / 1024 / 1024).toFixed(2)} MB/s | {(data.progress.bytes / 1024 / 1024).toFixed(2)}MB)
</Typography>
</Box>
</Box>
</Box>
</Box>
</Box>
</>
}
{
current && !data && <>
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}>
<Box sx={{
marginTop: '2rem',
marginBottom: '1rem',
height: '12rem',
width: '93vw',
maxWidth: '93rem',
backgroundColor: '#282828',
boxShadow: '0px 0px 50px #00000090',
borderRadius: '10px',
display: 'flex',
overflow: 'hidden',
transition: '250ms'
}}>
<img style={{
borderRadius: '5px',
margin: '5px',
boxShadow: '0px 0px 10px #00000090',
userSelect: 'none',
maxWidth: '20.5rem',
}}
src={current.image} height='auto' width='auto' alt="Thumbnail" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'center',
//backgroundColor: '#ffffff0f'
}}>
<Box sx={{
display: 'flex',
}}>
<Box sx={{
width: '70%',
marginLeft: '10px'
}}>
<Box sx={{
flexDirection: 'column',
display: 'flex',
justifyContent: 'space-between',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
{current.parent.title}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.2rem',
}}>
{current.title}
</Typography>
</Box>
</Box>
<Box sx={{
//backgroundColor: '#00ff00',
width: '30%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
position: 'relative',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
Downloading:
</Typography>
<CircularProgress variant="indeterminate" sx={{
marginLeft: '2rem',
}}/>
</Box>
</Box>
</Box>
<Box sx={{
height: '50%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
//backgroundColor: '#0000ff',
}}>
<LinearProgress variant='indeterminate'
sx={{
height: '20px',
width: '97.53%',
margin: '10px',
boxShadow: '0px 0px 10px #00000090',
borderRadius: '10px',
}}
/>
<Box>
<Typography color='text.primary'
sx={{
fontSize: '1.3rem',
}}>
0 / ? parts (0% | XX:XX | 0 MB/s | 0MB)
</Typography>
</Box>
</Box>
</Box>
</Box>
</Box>
</>
}
{queue.map((queueItem, index, { length }) => {
return <Box key={`queue_item_${index}`} sx={{
display: 'flex',
mb: '-1.5rem',
flexDirection: 'column',
alignItems: 'center',
}}>
<Box sx={{
marginTop: '1.5rem',
marginBottom: '1.5rem',
height: '11rem',
width: '90vw',
maxWidth: '90rem',
backgroundColor: '#282828',
boxShadow: '0px 0px 10px #00000090', boxShadow: '0px 0px 10px #00000090',
borderRadius: '10px', borderRadius: '10px',
}} display: 'flex',
/> overflow: 'hidden',
<Box> }}>
<Typography color='text.primary' <img style={{
sx={{ borderRadius: '5px',
fontSize: '1.3rem', margin: '5px',
boxShadow: '0px 0px 5px #00000090',
userSelect: 'none',
maxWidth: '18.5rem'
}}
src={queueItem.image} height='auto' width='auto' alt="Thumbnail" />
<Box sx={{
margin: '5px',
display: 'flex',
width: '100%',
justifyContent: 'space-between',
}}> }}>
0 / ? parts (0% | XX:XX | 0 MB/s | 0MB) <Box sx={{
</Typography> width: '30%',
marginRight: '5px',
marginLeft: '5px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}>
{queueItem.parent.title}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.6rem',
marginTop: '-0.4rem',
marginBottom: '0.4rem',
}}>
S{queueItem.parent.season}E{queueItem.episode}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.2rem',
marginTop: '-0.4rem',
marginBottom: '0.4rem',
textOverflow: 'ellipsis',
}}>
{queueItem.title}
</Typography>
</Box>
<Box sx={{
width: '40%',
marginRight: '5px',
marginLeft: '5px',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
whiteSpace: 'nowrap',
justifyContent: 'space-between',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}>
Dub(s): {queueItem.dubLang.join(', ')}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}>
Sub(s): {queueItem.dlsubs.join(', ')}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
Quality: {queueItem.q}
</Typography>
</Box>
<Box sx={{
marginRight: '5px',
marginLeft: '5px',
width: '30%',
justifyContent: 'center',
alignItems: 'center',
display: 'flex'
}}>
<Tooltip title="Delete from queue" arrow placement='top'>
<IconButton
onClick={() => {
msg.removeFromQueue(index);
}}
sx={{
backgroundColor: '#ff573a25',
height: '40px',
transition: '250ms',
'&:hover' : {
backgroundColor: '#ff573a',
}
}}>
<DeleteIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
</Box> </Box>
</Box>
</Box> </Box>
</Box> ;
</Box> })}
</> </> : <Box sx={{
}
{queue.map((queueItem, index, { length }) => {
return <Box key={`queue_item_${index}`} sx={{
display: 'flex', display: 'flex',
mb: '-1.5rem', width: '100%',
height: '12rem',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
}}> }}>
<Box sx={{ <Typography color='text.primary' sx={{
marginTop: '1.5rem', fontSize: '2rem',
marginBottom: '1.5rem', margin: '10px'
height: '11rem',
width: '90vw',
maxWidth: '90rem',
backgroundColor: '#282828',
boxShadow: '0px 0px 10px #00000090',
borderRadius: '10px',
display: 'flex',
overflow: 'hidden',
}}> }}>
<img style={{
borderRadius: '5px',
margin: '5px',
boxShadow: '0px 0px 5px #00000090',
userSelect: 'none',
maxWidth: '18.5rem'
}}
src={queueItem.image} height='auto' width='auto' alt="Thumbnail" />
<Box sx={{
margin: '5px',
display: 'flex',
width: '100%',
justifyContent: 'space-between',
}}>
<Box sx={{
width: '30%',
marginRight: '5px',
marginLeft: '5px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}>
{queueItem.parent.title}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.6rem',
marginTop: '-0.4rem',
marginBottom: '0.4rem',
}}>
S{queueItem.parent.season}E{queueItem.episode}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.2rem',
marginTop: '-0.4rem',
marginBottom: '0.4rem',
textOverflow: 'ellipsis',
}}>
{queueItem.title}
</Typography>
</Box>
<Box sx={{
width: '40%',
marginRight: '5px',
marginLeft: '5px',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
whiteSpace: 'nowrap',
justifyContent: 'space-between',
}}>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}>
Dub(s): {queueItem.dubLang.join(', ')}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}>
Sub(s): {queueItem.dlsubs.join(', ')}
</Typography>
<Typography color='text.primary' sx={{
fontSize: '1.8rem',
}}>
Quality: {queueItem.q}
</Typography>
</Box>
<Box sx={{
marginRight: '5px',
marginLeft: '5px',
width: '30%',
justifyContent: 'center',
alignItems: 'center',
display: 'flex'
}}>
<Tooltip title="Delete from queue" arrow placement='top'>
<IconButton
onClick={() => {
msg.removeFromQueue(index);
}}
sx={{
backgroundColor: '#ff573a25',
height: '40px',
transition: '250ms',
'&:hover' : {
backgroundColor: '#ff573a',
}
}}>
<DeleteIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
</Box>
</Box>
;
})}
</> : <Box sx={{
display: 'flex',
width: '100%',
height: '12rem',
flexDirection: 'column',
alignItems: 'center',
}}>
<Typography color='text.primary' sx={{
fontSize: '2rem',
margin: '10px'
}}>
Selected episodes will be shown here Selected episodes will be shown here
</Typography> </Typography>
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
margin: '10px' margin: '10px'
}}> }}>
<Skeleton variant='rectangular' height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> <Skeleton variant='rectangular' height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }}/>
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
}}> }}>
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> <Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/>
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> <Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/>
</Box> </Box>
</Box> </Box>
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
margin: '10px' margin: '10px'
}}> }}>
<Skeleton variant='rectangular' height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> <Skeleton variant='rectangular' height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }}/>
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
}}> }}>
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> <Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/>
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> <Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/>
</Box> </Box>
</Box> </Box>
</Box>; </Box>;
}; };
const formatTime = (time: number) => { const formatTime = (time: number) => {
time = Math.floor(time / 1000); time = Math.floor(time / 1000);
const minutes = Math.floor(time / 60); const minutes = Math.floor(time / 60);
time = time % 60; time = time % 60;
return `${minutes.toFixed(0).length < 2 ? `0${minutes}` : minutes}m${time.toFixed(0).length < 2 ? `0${time}` : time}s`; return `${minutes.toFixed(0).length < 2 ? `0${minutes}` : minutes}m${time.toFixed(0).length < 2 ? `0${time}` : time}s`;
}; };
export default Queue; export default Queue;

View file

@ -5,120 +5,120 @@ import useStore from '../../hooks/useStore';
import { StoreState } from '../../provider/Store'; import { StoreState } from '../../provider/Store';
const MenuBar: React.FC = () => { const MenuBar: React.FC = () => {
const [ openMenu, setMenuOpen ] = React.useState<'settings'|'help'|undefined>(); const [ openMenu, setMenuOpen ] = React.useState<'settings'|'help'|undefined>();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [store, dispatch] = useStore(); const [store, dispatch] = useStore();
const messageChannel = React.useContext(messageChannelContext); const messageChannel = React.useContext(messageChannelContext);
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
if (!messageChannel || store.version !== '') if (!messageChannel || store.version !== '')
return; return;
dispatch({ dispatch({
type: 'version', type: 'version',
payload: await messageChannel.version() payload: await messageChannel.version()
}); });
})(); })();
}, [messageChannel]); }, [messageChannel]);
const transformService = (service: StoreState['service']) => { const transformService = (service: StoreState['service']) => {
switch(service) { switch(service) {
case 'crunchy': case 'crunchy':
return 'Crunchyroll'; return 'Crunchyroll';
case 'hidive': case 'hidive':
return 'Hidive'; return 'Hidive';
case 'ao': case 'ao':
return 'AnimeOnegai'; return 'AnimeOnegai';
case 'adn': case 'adn':
return 'AnimationDigitalNetwork'; return 'AnimationDigitalNetwork';
} }
}; };
const msg = React.useContext(messageChannelContext); const msg = React.useContext(messageChannelContext);
const handleClick = (event: React.MouseEvent<HTMLElement>, n: 'settings'|'help') => { const handleClick = (event: React.MouseEvent<HTMLElement>, n: 'settings'|'help') => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
setMenuOpen(n); setMenuOpen(n);
}; };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
setMenuOpen(undefined); setMenuOpen(undefined);
}; };
if (!msg) if (!msg)
return <></>; return <></>;
return <Box sx={{ display: 'flex', marginBottom: '1rem', width: '100%', alignItems: 'center' }}> return <Box sx={{ display: 'flex', marginBottom: '1rem', width: '100%', alignItems: 'center' }}>
<Box sx={{ position: 'relative', left: '0%', width: '50%'}}> <Box sx={{ position: 'relative', left: '0%', width: '50%'}}>
<Button onClick={(e) => handleClick(e, 'settings')}> <Button onClick={(e) => handleClick(e, 'settings')}>
Settings Settings
</Button> </Button>
<Button onClick={(e) => handleClick(e, 'help')}> <Button onClick={(e) => handleClick(e, 'help')}>
Help Help
</Button> </Button>
</Box> </Box>
<Menu open={openMenu === 'settings'} anchorEl={anchorEl} onClose={handleClose}> <Menu open={openMenu === 'settings'} anchorEl={anchorEl} onClose={handleClose}>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openFolder('config'); msg.openFolder('config');
handleClose(); handleClose();
}}> }}>
Open settings folder Open settings folder
</MenuItem> </MenuItem>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openFile(['config', 'bin-path.yml']); msg.openFile(['config', 'bin-path.yml']);
handleClose(); handleClose();
}}> }}>
Open FFmpeg/Mkvmerge file Open FFmpeg/Mkvmerge file
</MenuItem> </MenuItem>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openFile(['config', 'cli-defaults.yml']); msg.openFile(['config', 'cli-defaults.yml']);
handleClose(); handleClose();
}}> }}>
Open advanced options Open advanced options
</MenuItem> </MenuItem>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openFolder('content'); msg.openFolder('content');
handleClose(); handleClose();
}}> }}>
Open output path Open output path
</MenuItem> </MenuItem>
</Menu> </Menu>
<Menu open={openMenu === 'help'} anchorEl={anchorEl} onClose={handleClose}> <Menu open={openMenu === 'help'} anchorEl={anchorEl} onClose={handleClose}>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openURL('https://github.com/anidl/multi-downloader-nx'); msg.openURL('https://github.com/anidl/multi-downloader-nx');
handleClose(); handleClose();
}}> }}>
GitHub GitHub
</MenuItem> </MenuItem>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openURL('https://github.com/anidl/multi-downloader-nx/issues/new?assignees=AnimeDL,AnidlSupport&labels=bug&template=bug.yml&title=BUG'); msg.openURL('https://github.com/anidl/multi-downloader-nx/issues/new?assignees=AnimeDL,AnidlSupport&labels=bug&template=bug.yml&title=BUG');
handleClose(); handleClose();
}}> }}>
Report a bug Report a bug
</MenuItem> </MenuItem>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openURL('https://github.com/anidl/multi-downloader-nx/graphs/contributors'); msg.openURL('https://github.com/anidl/multi-downloader-nx/graphs/contributors');
handleClose(); handleClose();
}}> }}>
Contributors Contributors
</MenuItem> </MenuItem>
<MenuItem onClick={() => { <MenuItem onClick={() => {
msg.openURL('https://discord.gg/qEpbWen5vq'); msg.openURL('https://discord.gg/qEpbWen5vq');
handleClose(); handleClose();
}}> }}>
Discord Discord
</MenuItem> </MenuItem>
<MenuItem onClick={() => { <MenuItem onClick={() => {
handleClose(); handleClose();
}}> }}>
Version: {store.version} Version: {store.version}
</MenuItem> </MenuItem>
</Menu> </Menu>
<Typography variant="h5" color="text.primary"> <Typography variant="h5" color="text.primary">
{transformService(store.service)} {transformService(store.service)}
</Typography> </Typography>
</Box>; </Box>;
}; };
export default MenuBar; export default MenuBar;

View file

@ -6,9 +6,9 @@ export type RequireType<T> = {
} }
const Require = <T, >(props: React.PropsWithChildren<RequireType<T>>) => { const Require = <T, >(props: React.PropsWithChildren<RequireType<T>>) => {
return props.value === undefined ? <Backdrop open> return props.value === undefined ? <Backdrop open>
<CircularProgress /> <CircularProgress />
</Backdrop> : <Box>{props.children}</Box>; </Backdrop> : <Box>{props.children}</Box>;
}; };
export default Require; export default Require;

View file

@ -5,37 +5,37 @@ import { messageChannelContext } from '../provider/MessageChannel';
import Require from './Require'; import Require from './Require';
const StartQueueButton: React.FC = () => { const StartQueueButton: React.FC = () => {
const messageChannel = React.useContext(messageChannelContext); const messageChannel = React.useContext(messageChannelContext);
const [start, setStart] = React.useState(false); const [start, setStart] = React.useState(false);
const msg = React.useContext(messageChannelContext); const msg = React.useContext(messageChannelContext);
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
if (!msg) if (!msg)
return alert('Invalid state: msg not found'); return alert('Invalid state: msg not found');
setStart(await msg.getDownloadQueue()); setStart(await msg.getDownloadQueue());
})(); })();
}, []); }, []);
const change = async () => { const change = async () => {
if (await messageChannel?.isDownloading()) if (await messageChannel?.isDownloading())
alert('The current download will be finished before the queue stops'); alert('The current download will be finished before the queue stops');
msg?.setDownloadQueue(!start); msg?.setDownloadQueue(!start);
setStart(!start); setStart(!start);
}; };
return <Require value={messageChannel}> return <Require value={messageChannel}>
<Button <Button
startIcon={start ? <PauseCircleFilled /> : <PlayCircleFilled /> } startIcon={start ? <PauseCircleFilled /> : <PlayCircleFilled /> }
variant='contained' variant='contained'
onClick={change} onClick={change}
sx={{ maxHeight: '2.3rem' }} sx={{ maxHeight: '2.3rem' }}
> >
{ {
start ? 'Stop Queue' : 'Start Queue' start ? 'Stop Queue' : 'Start Queue'
} }
</Button> </Button>
</Require>; </Require>;
}; };

View file

@ -12,54 +12,54 @@ export type ContextMenuProps<T extends HTMLElement> = {
} }
const buttonSx: SxProps = { const buttonSx: SxProps = {
'&:hover': { '&:hover': {
background: 'rgb(0, 30, 60)' background: 'rgb(0, 30, 60)'
}, },
fontSize: '0.7rem', fontSize: '0.7rem',
minHeight: '30px', minHeight: '30px',
justifyContent: 'center', justifyContent: 'center',
p: 0 p: 0
}; };
function ContextMenu<T extends HTMLElement, >(props: ContextMenuProps<T>) { function ContextMenu<T extends HTMLElement, >(props: ContextMenuProps<T>) {
const [anchor, setAnchor] = React.useState( { x: 0, y: 0 } ); const [anchor, setAnchor] = React.useState( { x: 0, y: 0 } );
const [show, setShow] = React.useState(false); const [show, setShow] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
const { popupItem: ref } = props; const { popupItem: ref } = props;
if (ref.current === null) if (ref.current === null)
return; return;
const listener = (ev: MouseEvent) => { const listener = (ev: MouseEvent) => {
ev.preventDefault(); ev.preventDefault();
setAnchor({ x: ev.x + 10, y: ev.y + 10 }); setAnchor({ x: ev.x + 10, y: ev.y + 10 });
setShow(true); setShow(true);
}; };
ref.current.addEventListener('contextmenu', listener); ref.current.addEventListener('contextmenu', listener);
return () => { return () => {
if (ref.current) if (ref.current)
ref.current.removeEventListener('contextmenu', listener); ref.current.removeEventListener('contextmenu', listener);
}; };
}, [ props.popupItem ]); }, [ props.popupItem ]);
return show ? <Box sx={{ zIndex: 1400, p: 1, background: 'rgba(0, 0, 0, 0.75)', backdropFilter: 'blur(5px)', position: 'fixed', left: anchor.x, top: anchor.y }}> return show ? <Box sx={{ zIndex: 1400, p: 1, background: 'rgba(0, 0, 0, 0.75)', backdropFilter: 'blur(5px)', position: 'fixed', left: anchor.x, top: anchor.y }}>
<List sx={{ p: 0, m: 0, display: 'flex', flexDirection: 'column' }}> <List sx={{ p: 0, m: 0, display: 'flex', flexDirection: 'column' }}>
{props.options.map((item, i) => { {props.options.map((item, i) => {
return item === 'divider' ? <Divider key={`ContextMenu_Divider_${i}_${item}`}/> : return item === 'divider' ? <Divider key={`ContextMenu_Divider_${i}_${item}`}/> :
<Button color='inherit' key={`ContextMenu_Value_${i}_${item}`} onClick={() => { <Button color='inherit' key={`ContextMenu_Value_${i}_${item}`} onClick={() => {
item.onClick(); item.onClick();
setShow(false); setShow(false);
}} sx={buttonSx}> }} sx={buttonSx}>
{item.text} {item.text}
</Button>; </Button>;
})} })}
<Divider /> <Divider />
<Button fullWidth color='inherit' onClick={() => setShow(false)} sx={buttonSx} > <Button fullWidth color='inherit' onClick={() => setShow(false)} sx={buttonSx} >
Close Close
</Button> </Button>
</List> </List>
</Box> : <></>; </Box> : <></>;
} }
export default ContextMenu; export default ContextMenu;

View file

@ -7,18 +7,18 @@ import React from 'react';
export type LinearProgressWithLabelProps = LinearProgressProps & { value: number }; export type LinearProgressWithLabelProps = LinearProgressProps & { value: number };
const LinearProgressWithLabel: React.FC<LinearProgressWithLabelProps> = (props) => { const LinearProgressWithLabel: React.FC<LinearProgressWithLabelProps> = (props) => {
return ( return (
<Box sx={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}> <Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" {...props} /> <LinearProgress variant="determinate" {...props} />
</Box> </Box>
<Box sx={{ minWidth: 35 }}> <Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round( <Typography variant="body2" color="text.secondary">{`${Math.round(
props.value, props.value,
)}%`}</Typography> )}%`}</Typography>
</Box> </Box>
</Box> </Box>
); );
}; };
export default LinearProgressWithLabel; export default LinearProgressWithLabel;

View file

@ -12,63 +12,63 @@ export type MultiSelectProps = {
const ITEM_HEIGHT = 48; const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8; const ITEM_PADDING_TOP = 8;
const MenuProps = { const MenuProps = {
PaperProps: { PaperProps: {
style: { style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250 width: 250
}
} }
}
}; };
function getStyles(name: string, personName: readonly string[], theme: Theme) { function getStyles(name: string, personName: readonly string[], theme: Theme) {
return { return {
fontWeight: fontWeight:
(personName ?? []).indexOf(name) === -1 (personName ?? []).indexOf(name) === -1
? theme.typography.fontWeightRegular ? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium : theme.typography.fontWeightMedium
}; };
} }
const MultiSelect: React.FC<MultiSelectProps> = (props) => { const MultiSelect: React.FC<MultiSelectProps> = (props) => {
const theme = useTheme(); const theme = useTheme();
return <div> return <div>
<FormControl sx={{ width: 300 }}> <FormControl sx={{ width: 300 }}>
<InputLabel id="multi-select-label">{props.title}</InputLabel> <InputLabel id="multi-select-label">{props.title}</InputLabel>
<Select <Select
labelId="multi-select-label" labelId="multi-select-label"
id="multi-select" id="multi-select"
multiple multiple
value={(props.selected ?? [])} value={(props.selected ?? [])}
onChange={e => { onChange={e => {
const val = typeof e.target.value === 'string' ? e.target.value.split(',') : e.target.value; const val = typeof e.target.value === 'string' ? e.target.value.split(',') : e.target.value;
if (props.allOption && val.includes('all')) { if (props.allOption && val.includes('all')) {
if (props.values.length === val.length - 1) if (props.values.length === val.length - 1)
props.onChange([]); props.onChange([]);
else else
props.onChange(props.values); props.onChange(props.values);
} else { } else {
props.onChange(val); props.onChange(val);
} }
}} }}
input={<OutlinedInput id="select-multiple-chip" label={props.title} />} input={<OutlinedInput id="select-multiple-chip" label={props.title} />}
renderValue={(selected) => ( renderValue={(selected) => (
selected.join(', ') selected.join(', ')
)} )}
MenuProps={MenuProps} MenuProps={MenuProps}
> >
{props.values.concat(props.allOption ? 'all' : []).map((name) => ( {props.values.concat(props.allOption ? 'all' : []).map((name) => (
<MenuItem <MenuItem
key={`${props.title}_${name}`} key={`${props.title}_${name}`}
value={name} value={name}
style={getStyles(name, props.selected, theme)} style={getStyles(name, props.selected, theme)}
> >
{name} {name}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
</FormControl> </FormControl>
</div>; </div>;
}; };
export default MultiSelect; export default MultiSelect;

View file

@ -2,11 +2,11 @@ import React from 'react';
import { StoreAction, StoreContext, StoreState } from '../provider/Store'; import { StoreAction, StoreContext, StoreState } from '../provider/Store';
const useStore = () => { const useStore = () => {
const context = React.useContext(StoreContext as unknown as React.Context<[StoreState, React.Dispatch<StoreAction<keyof StoreState>>]>); const context = React.useContext(StoreContext as unknown as React.Context<[StoreState, React.Dispatch<StoreAction<keyof StoreState>>]>);
if (!context) { if (!context) {
throw new Error('useStore must be used under Store'); throw new Error('useStore must be used under Store');
} }
return context; return context;
}; };
export default useStore; export default useStore;

View file

@ -17,35 +17,35 @@ document.body.style.justifyContent = 'center';
const notistackRef = React.createRef<SnackbarProvider>(); const notistackRef = React.createRef<SnackbarProvider>();
const onClickDismiss = (key: SnackbarKey | undefined) => () => { const onClickDismiss = (key: SnackbarKey | undefined) => () => {
if (notistackRef.current) if (notistackRef.current)
notistackRef.current.closeSnackbar(key); notistackRef.current.closeSnackbar(key);
}; };
const container = document.getElementById('root'); const container = document.getElementById('root');
const root = createRoot(container as HTMLElement); const root = createRoot(container as HTMLElement);
root.render( root.render(
<ErrorHandler> <ErrorHandler>
<Store> <Store>
<SnackbarProvider <SnackbarProvider
ref={notistackRef} ref={notistackRef}
action={(key) => ( action={(key) => (
<IconButton onClick={onClickDismiss(key)} color="inherit"> <IconButton onClick={onClickDismiss(key)} color="inherit">
<CloseOutlined /> <CloseOutlined />
</IconButton> </IconButton>
)} )}
> >
<Style> <Style>
<MessageChannel> <MessageChannel>
<ServiceProvider> <ServiceProvider>
<QueueProvider> <QueueProvider>
<Box> <Box>
<App /> <App />
</Box> </Box>
</QueueProvider> </QueueProvider>
</ServiceProvider> </ServiceProvider>
</MessageChannel> </MessageChannel>
</Style> </Style>
</SnackbarProvider> </SnackbarProvider>
</Store> </Store>
</ErrorHandler> </ErrorHandler>
); );

View file

@ -10,30 +10,30 @@ export default class ErrorHandler extends React.Component<{
} }
}> { }> {
constructor(props: { constructor(props: {
children: React.ReactNode|React.ReactNode[] children: React.ReactNode|React.ReactNode[]
}) { }) {
super(props); super(props);
this.state = { error: undefined }; this.state = { error: undefined };
} }
componentDidCatch(er: Error, stack: React.ErrorInfo) { componentDidCatch(er: Error, stack: React.ErrorInfo) {
this.setState({ error: { er, stack } }); this.setState({ error: { er, stack } });
} }
render(): React.ReactNode { render(): React.ReactNode {
return this.state.error ? return this.state.error ?
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', p: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', p: 2 }}>
<Typography variant='body1' color='red'> <Typography variant='body1' color='red'>
{`${this.state.error.er.name}: ${this.state.error.er.message}`} {`${this.state.error.er.name}: ${this.state.error.er.message}`}
<br/> <br/>
{this.state.error.stack.componentStack?.split('\n').map(a => { {this.state.error.stack.componentStack?.split('\n').map(a => {
return <> return <>
{a} {a}
<br/> <br/>
</>; </>;
})} })}
</Typography> </Typography>
</Box> : this.props.children; </Box> : this.props.children;
} }
} }

View file

@ -12,233 +12,233 @@ import { GUIConfig } from '../../../../modules/module.cfg-loader';
export type FrontEndMessages = (MessageHandler & { randomEvents: RandomEventHandler, logout: () => Promise<boolean> }); export type FrontEndMessages = (MessageHandler & { randomEvents: RandomEventHandler, logout: () => Promise<boolean> });
export class RandomEventHandler { export class RandomEventHandler {
private handler: { private handler: {
[eventName in keyof RandomEvents]: Handler<eventName>[] [eventName in keyof RandomEvents]: Handler<eventName>[]
} = { } = {
progress: [], progress: [],
finish: [], finish: [],
queueChange: [], queueChange: [],
current: [] current: []
}; };
public on<T extends keyof RandomEvents>(name: T, listener: Handler<T>) { public on<T extends keyof RandomEvents>(name: T, listener: Handler<T>) {
if (Object.prototype.hasOwnProperty.call(this.handler, name)) { if (Object.prototype.hasOwnProperty.call(this.handler, name)) {
this.handler[name].push(listener as any); this.handler[name].push(listener as any);
} else { } else {
this.handler[name] = [ listener as any ]; this.handler[name] = [ listener as any ];
}
} }
}
public emit<T extends keyof RandomEvents>(name: keyof RandomEvents, data: RandomEvent<T>) { public emit<T extends keyof RandomEvents>(name: keyof RandomEvents, data: RandomEvent<T>) {
(this.handler[name] ?? []).forEach(handler => handler(data as any)); (this.handler[name] ?? []).forEach(handler => handler(data as any));
} }
public removeListener<T extends keyof RandomEvents>(name: T, listener: Handler<T>) { public removeListener<T extends keyof RandomEvents>(name: T, listener: Handler<T>) {
this.handler[name] = (this.handler[name] as Handler<T>[]).filter(a => a !== listener) as any; this.handler[name] = (this.handler[name] as Handler<T>[]).filter(a => a !== listener) as any;
} }
} }
export const messageChannelContext = React.createContext<FrontEndMessages|undefined>(undefined); export const messageChannelContext = React.createContext<FrontEndMessages|undefined>(undefined);
async function messageAndResponse<T extends keyof MessageTypes>(socket: WebSocket, msg: WSMessage<T>): Promise<WSMessage<T, 1>> { async function messageAndResponse<T extends keyof MessageTypes>(socket: WebSocket, msg: WSMessage<T>): Promise<WSMessage<T, 1>> {
const id = v4(); const id = v4();
const ret = new Promise<WSMessage<T, 1>>((resolve) => { const ret = new Promise<WSMessage<T, 1>>((resolve) => {
const handler = function({ data }: MessageEvent) { const handler = function({ data }: MessageEvent) {
const parsed = JSON.parse(data.toString()) as WSMessageWithID<T, 1>; const parsed = JSON.parse(data.toString()) as WSMessageWithID<T, 1>;
if (parsed.id === id) { if (parsed.id === id) {
socket.removeEventListener('message', handler); socket.removeEventListener('message', handler);
resolve(parsed); resolve(parsed);
} }
}; };
socket.addEventListener('message', handler); socket.addEventListener('message', handler);
}); });
const toSend = msg as WSMessageWithID<T>; const toSend = msg as WSMessageWithID<T>;
toSend.id = id; toSend.id = id;
socket.send(JSON.stringify(toSend)); socket.send(JSON.stringify(toSend));
return ret; return ret;
} }
const MessageChannelProvider: FCWithChildren = ({ children }) => { const MessageChannelProvider: FCWithChildren = ({ children }) => {
const [store, dispatch] = useStore(); const [store, dispatch] = useStore();
const [socket, setSocket] = React.useState<undefined|WebSocket>(); const [socket, setSocket] = React.useState<undefined|WebSocket>();
const [publicWS, setPublicWS] = React.useState<undefined|WebSocket>(); const [publicWS, setPublicWS] = React.useState<undefined|WebSocket>();
const [usePassword, setUsePassword] = React.useState<'waiting'|'yes'|'no'>('waiting'); const [usePassword, setUsePassword] = React.useState<'waiting'|'yes'|'no'>('waiting');
const [isSetup, setIsSetup] = React.useState<'waiting'|'yes'|'no'>('waiting'); const [isSetup, setIsSetup] = React.useState<'waiting'|'yes'|'no'>('waiting');
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
React.useEffect(() => { React.useEffect(() => {
const wss = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/public`); const wss = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/public`);
wss.addEventListener('open', () => { wss.addEventListener('open', () => {
setPublicWS(wss); setPublicWS(wss);
});
wss.addEventListener('error', () => {
enqueueSnackbar('Unable to connect to server. Please reload the page to try again.', { variant: 'error' });
});
}, []);
React.useEffect(() => {
(async () => {
if (!publicWS)
return;
setUsePassword((await messageAndResponse(publicWS, { name: 'requirePassword', data: undefined })).data ? 'yes' : 'no');
setIsSetup((await messageAndResponse(publicWS, { name: 'isSetup', data: undefined })).data ? 'yes' : 'no');
})();
}, [publicWS]);
const connect = (ev?: React.FormEvent<HTMLFormElement>) => {
let search = new URLSearchParams();
if (ev) {
ev.preventDefault();
const formData = new FormData(ev.currentTarget);
const password = formData.get('password')?.toString();
if (!password)
return enqueueSnackbar('Please provide both a username and password', {
variant: 'error'
}); });
search = new URLSearchParams({ wss.addEventListener('error', () => {
password enqueueSnackbar('Unable to connect to server. Please reload the page to try again.', { variant: 'error' });
}); });
} }, []);
const wws = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/private?${search}`, ); React.useEffect(() => {
wws.addEventListener('open', () => { (async () => {
console.log('[INFO] [WS] Connected'); if (!publicWS)
setSocket(wws); return;
}); setUsePassword((await messageAndResponse(publicWS, { name: 'requirePassword', data: undefined })).data ? 'yes' : 'no');
wws.addEventListener('error', (er) => { setIsSetup((await messageAndResponse(publicWS, { name: 'isSetup', data: undefined })).data ? 'yes' : 'no');
console.error('[ERROR] [WS]', er); })();
enqueueSnackbar('Unable to connect to server. Please check the password and try again.', { }, [publicWS]);
variant: 'error'
});
});
};
const setup = async (ev: React.FormEvent<HTMLFormElement>) => { const connect = (ev?: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault(); let search = new URLSearchParams();
if (!socket) if (ev) {
return enqueueSnackbar('Invalid state: socket not found', { variant: 'error' }); ev.preventDefault();
const formData = new FormData(ev.currentTarget); const formData = new FormData(ev.currentTarget);
const password = formData.get('password'); const password = formData.get('password')?.toString();
const data = { if (!password)
port: parseInt(formData.get('port')?.toString() ?? '') ?? 3000, return enqueueSnackbar('Please provide both a username and password', {
password: password ? password.toString() : undefined variant: 'error'
} as GUIConfig; });
await messageAndResponse(socket, { name: 'setupServer', data }); search = new URLSearchParams({
enqueueSnackbar(`The following settings have been set: Port=${data.port}, Password=${data.password ?? 'noPasswordRequired'}`, { password
variant: 'success', });
persist: true }
});
enqueueSnackbar('Please restart the server now.', {
variant: 'info',
persist: true
});
};
const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []); const wws = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/private?${search}`, );
wws.addEventListener('open', () => {
React.useEffect(() => { console.log('[INFO] [WS] Connected');
(async () => { setSocket(wws);
if (!socket) });
return; wws.addEventListener('error', (er) => {
const currentService = await messageAndResponse(socket, { name: 'type', data: undefined }); console.error('[ERROR] [WS]', er);
if (currentService.data !== undefined) enqueueSnackbar('Unable to connect to server. Please check the password and try again.', {
return dispatch({ type: 'service', payload: currentService.data }); variant: 'error'
if (store.service !== currentService.data) });
messageAndResponse(socket, { name: 'setup', data: store.service }); });
})();
}, [store.service, dispatch, socket]);
React.useEffect(() => {
if (!socket)
return;
/* finish is a placeholder */
const listener = (initalData: MessageEvent<string>) => {
const data = JSON.parse(initalData.data) as RandomEvent<'finish'>;
randomEventHandler.emit(data.name, data);
}; };
socket.addEventListener('message', listener);
return () => { const setup = async (ev: React.FormEvent<HTMLFormElement>) => {
socket.removeEventListener('message', listener); ev.preventDefault();
if (!socket)
return enqueueSnackbar('Invalid state: socket not found', { variant: 'error' });
const formData = new FormData(ev.currentTarget);
const password = formData.get('password');
const data = {
port: parseInt(formData.get('port')?.toString() ?? '') ?? 3000,
password: password ? password.toString() : undefined
} as GUIConfig;
await messageAndResponse(socket, { name: 'setupServer', data });
enqueueSnackbar(`The following settings have been set: Port=${data.port}, Password=${data.password ?? 'noPasswordRequired'}`, {
variant: 'success',
persist: true
});
enqueueSnackbar('Please restart the server now.', {
variant: 'info',
persist: true
});
}; };
}, [ socket ]);
if (usePassword === 'waiting') const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []);
return <></>;
if (socket === undefined) { React.useEffect(() => {
if (usePassword === 'no') { (async () => {
connect(undefined); if (!socket)
return <></>; return;
} const currentService = await messageAndResponse(socket, { name: 'type', data: undefined });
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}> if (currentService.data !== undefined)
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}> return dispatch({ type: 'service', payload: currentService.data });
<LockOutlined /> if (store.service !== currentService.data)
</Avatar> messageAndResponse(socket, { name: 'setup', data: store.service });
<Typography component="h1" variant="h5" color="text.primary"> })();
}, [store.service, dispatch, socket]);
React.useEffect(() => {
if (!socket)
return;
/* finish is a placeholder */
const listener = (initalData: MessageEvent<string>) => {
const data = JSON.parse(initalData.data) as RandomEvent<'finish'>;
randomEventHandler.emit(data.name, data);
};
socket.addEventListener('message', listener);
return () => {
socket.removeEventListener('message', listener);
};
}, [ socket ]);
if (usePassword === 'waiting')
return <></>;
if (socket === undefined) {
if (usePassword === 'no') {
connect(undefined);
return <></>;
}
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlined />
</Avatar>
<Typography component="h1" variant="h5" color="text.primary">
Login Login
</Typography> </Typography>
<Box component="form" onSubmit={connect} sx={{ mt: 1 }}> <Box component="form" onSubmit={connect} sx={{ mt: 1 }}>
<TextField name="password" margin='normal' type="password" fullWidth variant="filled" required label={'Password'} /> <TextField name="password" margin='normal' type="password" fullWidth variant="filled" required label={'Password'} />
<Button type='submit' variant='contained' sx={{ mt: 3, mb: 2 }} fullWidth>Login</Button> <Button type='submit' variant='contained' sx={{ mt: 3, mb: 2 }} fullWidth>Login</Button>
<Typography color="text.secondary" align='center' component="p" variant='body2'> <Typography color="text.secondary" align='center' component="p" variant='body2'>
You need to login in order to use this tool. You need to login in order to use this tool.
</Typography> </Typography>
</Box> </Box>
</Box>; </Box>;
} }
if (isSetup === 'no') { if (isSetup === 'no') {
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}> return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}> <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<PowerSettingsNew /> <PowerSettingsNew />
</Avatar> </Avatar>
<Typography component="h1" variant="h5" color="text.primary"> <Typography component="h1" variant="h5" color="text.primary">
Confirm Confirm
</Typography> </Typography>
<Box component="form" onSubmit={setup} sx={{ mt: 1 }}> <Box component="form" onSubmit={setup} sx={{ mt: 1 }}>
<TextField name="port" margin='normal' type="number" fullWidth variant="filled" required label={'Port'} defaultValue={3000} /> <TextField name="port" margin='normal' type="number" fullWidth variant="filled" required label={'Port'} defaultValue={3000} />
<TextField name="password" margin='normal' type="password" fullWidth variant="filled" label={'Password'} /> <TextField name="password" margin='normal' type="password" fullWidth variant="filled" label={'Password'} />
<Button type='submit' variant='contained' sx={{ mt: 3, mb: 2 }} fullWidth>Confirm</Button> <Button type='submit' variant='contained' sx={{ mt: 3, mb: 2 }} fullWidth>Confirm</Button>
<Typography color="text.secondary" align='center' component="p" variant='body2'> <Typography color="text.secondary" align='center' component="p" variant='body2'>
Please enter data that will be set to use this tool. Please enter data that will be set to use this tool.
<br /> <br />
Leave blank to use no password (NOT RECOMMENDED)! Leave blank to use no password (NOT RECOMMENDED)!
</Typography> </Typography>
</Box> </Box>
</Box>; </Box>;
} }
const messageHandler: FrontEndMessages = { const messageHandler: FrontEndMessages = {
name: 'default', name: 'default',
auth: async (data) => (await messageAndResponse(socket, { name: 'auth', data })).data, auth: async (data) => (await messageAndResponse(socket, { name: 'auth', data })).data,
version: async () => (await messageAndResponse(socket, { name: 'version', data: undefined })).data, version: async () => (await messageAndResponse(socket, { name: 'version', data: undefined })).data,
checkToken: async () => (await messageAndResponse(socket, { name: 'checkToken', data: undefined })).data, checkToken: async () => (await messageAndResponse(socket, { name: 'checkToken', data: undefined })).data,
search: async (data) => (await messageAndResponse(socket, { name: 'search', data })).data, search: async (data) => (await messageAndResponse(socket, { name: 'search', data })).data,
handleDefault: async (data) => (await messageAndResponse(socket, { name: 'default', data })).data, handleDefault: async (data) => (await messageAndResponse(socket, { name: 'default', data })).data,
availableDubCodes: async () => (await messageAndResponse(socket, { name: 'availableDubCodes', data: undefined})).data, availableDubCodes: async () => (await messageAndResponse(socket, { name: 'availableDubCodes', data: undefined})).data,
availableSubCodes: async () => (await messageAndResponse(socket, { name: 'availableSubCodes', data: undefined })).data, availableSubCodes: async () => (await messageAndResponse(socket, { name: 'availableSubCodes', data: undefined })).data,
resolveItems: async (data) => (await messageAndResponse(socket, { name: 'resolveItems', data })).data, resolveItems: async (data) => (await messageAndResponse(socket, { name: 'resolveItems', data })).data,
listEpisodes: async (data) => (await messageAndResponse(socket, { name: 'listEpisodes', data })).data, listEpisodes: async (data) => (await messageAndResponse(socket, { name: 'listEpisodes', data })).data,
randomEvents: randomEventHandler, randomEvents: randomEventHandler,
downloadItem: (data) => messageAndResponse(socket, { name: 'downloadItem', data }), downloadItem: (data) => messageAndResponse(socket, { name: 'downloadItem', data }),
isDownloading: async () => (await messageAndResponse(socket, { name: 'isDownloading', data: undefined })).data, isDownloading: async () => (await messageAndResponse(socket, { name: 'isDownloading', data: undefined })).data,
openFolder: async (data) => messageAndResponse(socket, { name: 'openFolder', data }), openFolder: async (data) => messageAndResponse(socket, { name: 'openFolder', data }),
logout: async () => (await messageAndResponse(socket, { name: 'changeProvider', data: undefined })).data, logout: async () => (await messageAndResponse(socket, { name: 'changeProvider', data: undefined })).data,
openFile: async (data) => await messageAndResponse(socket, { name: 'openFile', data }), openFile: async (data) => await messageAndResponse(socket, { name: 'openFile', data }),
openURL: async (data) => await messageAndResponse(socket, { name: 'openURL', data }), openURL: async (data) => await messageAndResponse(socket, { name: 'openURL', data }),
getQueue: async () => (await messageAndResponse(socket, { name: 'getQueue', data: undefined })).data, getQueue: async () => (await messageAndResponse(socket, { name: 'getQueue', data: undefined })).data,
removeFromQueue: async (data) => await messageAndResponse(socket, { name: 'removeFromQueue', data }), removeFromQueue: async (data) => await messageAndResponse(socket, { name: 'removeFromQueue', data }),
clearQueue: async () => await messageAndResponse(socket, { name: 'clearQueue', data: undefined }), clearQueue: async () => await messageAndResponse(socket, { name: 'clearQueue', data: undefined }),
setDownloadQueue: async (data) => await messageAndResponse(socket, { name: 'setDownloadQueue', data }), setDownloadQueue: async (data) => await messageAndResponse(socket, { name: 'setDownloadQueue', data }),
getDownloadQueue: async () => (await messageAndResponse(socket, { name: 'getDownloadQueue', data: undefined })).data, getDownloadQueue: async () => (await messageAndResponse(socket, { name: 'getDownloadQueue', data: undefined })).data,
}; };
return <messageChannelContext.Provider value={messageHandler}> return <messageChannelContext.Provider value={messageHandler}>
{children} {children}
</messageChannelContext.Provider>; </messageChannelContext.Provider>;
}; };
export default MessageChannelProvider; export default MessageChannelProvider;

View file

@ -6,30 +6,30 @@ import { RandomEvent } from '../../../../@types/randomEvents';
export const queueContext = React.createContext<QueueItem[]>([]); export const queueContext = React.createContext<QueueItem[]>([]);
const QueueProvider: FCWithChildren = ({ children }) => { const QueueProvider: FCWithChildren = ({ children }) => {
const msg = React.useContext(messageChannelContext); const msg = React.useContext(messageChannelContext);
const [ready, setReady] = React.useState(false); const [ready, setReady] = React.useState(false);
const [queue, setQueue] = React.useState<QueueItem[]>([]); const [queue, setQueue] = React.useState<QueueItem[]>([]);
React.useEffect(() => { React.useEffect(() => {
if (msg && !ready) { if (msg && !ready) {
msg.getQueue().then(data => { msg.getQueue().then(data => {
setQueue(data); setQueue(data);
setReady(true); setReady(true);
}); });
} }
const listener = (ev: RandomEvent<'queueChange'>) => { const listener = (ev: RandomEvent<'queueChange'>) => {
setQueue(ev.data); setQueue(ev.data);
}; };
msg?.randomEvents.on('queueChange', listener); msg?.randomEvents.on('queueChange', listener);
return () => { return () => {
msg?.randomEvents.removeListener('queueChange', listener); msg?.randomEvents.removeListener('queueChange', listener);
}; };
}, [ msg ]); }, [ msg ]);
return <queueContext.Provider value={queue}> return <queueContext.Provider value={queue}>
{children} {children}
</queueContext.Provider>; </queueContext.Provider>;
}; };
export default QueueProvider; export default QueueProvider;

View file

@ -8,28 +8,28 @@ type Services = 'crunchy'|'hidive'|'ao'|'adn';
export const serviceContext = React.createContext<Services|undefined>(undefined); export const serviceContext = React.createContext<Services|undefined>(undefined);
const ServiceProvider: FCWithChildren = ({ children }) => { const ServiceProvider: FCWithChildren = ({ children }) => {
const [ { service }, dispatch ] = useStore(); const [ { service }, dispatch ] = useStore();
const setService = (s: StoreState['service']) => { const setService = (s: StoreState['service']) => {
dispatch({ dispatch({
type: 'service', type: 'service',
payload: s payload: s
}); });
}; };
return service === undefined ? return service === undefined ?
<Box sx={{ justifyContent: 'center', alignItems: 'center', display: 'flex', flexDirection: 'column', position: 'relative', top: '40vh'}}> <Box sx={{ justifyContent: 'center', alignItems: 'center', display: 'flex', flexDirection: 'column', position: 'relative', top: '40vh'}}>
<Typography color="text.primary" variant='h3' sx={{ textAlign: 'center', mb: 5 }}>Please select your service</Typography> <Typography color="text.primary" variant='h3' sx={{ textAlign: 'center', mb: 5 }}>Please select your service</Typography>
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}> <Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
<Button size='large' variant="contained" onClick={() => setService('crunchy')} startIcon={<Avatar src={'https://static.crunchyroll.com/cxweb/assets/img/favicons/favicon-32x32.png'} />}>Crunchyroll</Button> <Button size='large' variant="contained" onClick={() => setService('crunchy')} startIcon={<Avatar src={'https://static.crunchyroll.com/cxweb/assets/img/favicons/favicon-32x32.png'} />}>Crunchyroll</Button>
<Button size='large' variant="contained" onClick={() => setService('hidive')} startIcon={<Avatar src={'https://static.diceplatform.com/prod/original/dce.hidive/settings/HIDIVE_AppLogo_1024x1024.0G0vK.jpg'} />}>Hidive</Button> <Button size='large' variant="contained" onClick={() => setService('hidive')} startIcon={<Avatar src={'https://static.diceplatform.com/prod/original/dce.hidive/settings/HIDIVE_AppLogo_1024x1024.0G0vK.jpg'} />}>Hidive</Button>
<Button size='large' variant="contained" onClick={() => setService('ao')} startIcon={<Avatar src={'https://www.animeonegai.com/assets/img/anime/general/ao3-favicon.png'} />}>AnimeOnegai</Button> <Button size='large' variant="contained" onClick={() => setService('ao')} startIcon={<Avatar src={'https://www.animeonegai.com/assets/img/anime/general/ao3-favicon.png'} />}>AnimeOnegai</Button>
<Button size='large' variant="contained" onClick={() => setService('adn')} startIcon={<Avatar src={'https://animationdigitalnetwork.com/favicon.ico'} />}>AnimationDigitalNetwork</Button> <Button size='large' variant="contained" onClick={() => setService('adn')} startIcon={<Avatar src={'https://animationdigitalnetwork.com/favicon.ico'} />}>AnimationDigitalNetwork</Button>
</Box> </Box>
</Box> </Box>
: <serviceContext.Provider value={service}> : <serviceContext.Provider value={service}>
{children} {children}
</serviceContext.Provider>; </serviceContext.Provider>;
}; };
export default ServiceProvider; export default ServiceProvider;

View file

@ -32,35 +32,35 @@ export type StoreAction<T extends (keyof StoreState)> = {
} }
const Reducer = <T extends keyof StoreState,>(state: StoreState, action: StoreAction<T>): StoreState => { const Reducer = <T extends keyof StoreState,>(state: StoreState, action: StoreAction<T>): StoreState => {
switch(action.type) { switch(action.type) {
default: default:
return { ...state, [action.type]: action.payload }; return { ...state, [action.type]: action.payload };
} }
}; };
const initialState: StoreState = { const initialState: StoreState = {
downloadOptions: { downloadOptions: {
id: '', id: '',
q: 0, q: 0,
e: '', e: '',
dubLang: [ 'jpn' ], dubLang: [ 'jpn' ],
dlsubs: [ 'all' ], dlsubs: [ 'all' ],
fileName: '', fileName: '',
dlVideoOnce: false, dlVideoOnce: false,
all: false, all: false,
but: false, but: false,
noaudio: false, noaudio: false,
novids: false, novids: false,
simul: false simul: false
}, },
service: undefined, service: undefined,
episodeListing: [], episodeListing: [],
version: '', version: '',
}; };
const Store: FCWithChildren = ({children}) => { const Store: FCWithChildren = ({children}) => {
const [state, dispatch] = React.useReducer(Reducer, initialState); const [state, dispatch] = React.useReducer(Reducer, initialState);
/*React.useEffect(() => { /*React.useEffect(() => {
if (!state.unsavedChanges.has) if (!state.unsavedChanges.has)
return; return;
const unsavedChanges = (ev: BeforeUnloadEvent, lang: LanguageContextType) => { const unsavedChanges = (ev: BeforeUnloadEvent, lang: LanguageContextType) => {
@ -79,11 +79,11 @@ const Store: FCWithChildren = ({children}) => {
return () => window.removeEventListener('beforeunload', windowListener); return () => window.removeEventListener('beforeunload', windowListener);
}, [state.unsavedChanges.has]);*/ }, [state.unsavedChanges.has]);*/
return ( return (
<StoreContext.Provider value={[state, dispatch]}> <StoreContext.Provider value={[state, dispatch]}>
{children} {children}
</StoreContext.Provider> </StoreContext.Provider>
); );
}; };
/* Importent Notice -- The 'queue' generic will be overriden */ /* Importent Notice -- The 'queue' generic will be overriden */

View file

@ -1,29 +1,29 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "./build", "outDir": "./build",
"target": "es5", "target": "es5",
"lib": [ "lib": [
"dom", "dom",
"dom.iterable", "dom.iterable",
"esnext" "esnext"
], ],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"module": "CommonJS", "module": "CommonJS",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
//"noEmit": true, //"noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"downlevelIteration": true "downlevelIteration": true
}, },
"include": [ "include": [
"./src", "./src",
"./webpack.config.ts" "./webpack.config.ts"
] ]
} }

View file

@ -4,55 +4,55 @@ import path from 'path';
import type { Configuration as DevServerConfig } from 'webpack-dev-server'; import type { Configuration as DevServerConfig } from 'webpack-dev-server';
const config: Configuration & DevServerConfig = { const config: Configuration & DevServerConfig = {
devServer: { devServer: {
proxy: [ proxy: [
{ {
target: 'http://localhost:3000', target: 'http://localhost:3000',
context: ['/public', '/private'], context: ['/public', '/private'],
ws: true ws: true
} }
], ],
}, },
entry: './src/index.tsx', entry: './src/index.tsx',
mode: 'production', mode: 'production',
output: { output: {
path: path.resolve(process.cwd(), './build'), path: path.resolve(process.cwd(), './build'),
filename: 'index.js', filename: 'index.js',
}, },
target: 'web', target: 'web',
resolve: { resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
}, },
performance: false, performance: false,
module: { module: {
rules: [ rules: [
{ {
test: /\.(ts|tsx)$/, test: /\.(ts|tsx)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
'loader': 'babel-loader', 'loader': 'babel-loader',
options: { options: {
presets: [ presets: [
'@babel/typescript', '@babel/typescript',
'@babel/preset-react', '@babel/preset-react',
['@babel/preset-env', { ['@babel/preset-env', {
targets: 'defaults' targets: 'defaults'
}] }]
] ]
} }
}, },
}, },
{ {
test: /\.css$/i, test: /\.css$/i,
use: ['style-loader', 'css-loader'], use: ['style-loader', 'css-loader'],
}, },
], ],
}, },
plugins: [ plugins: [
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: path.join(process.cwd(), 'public', 'index.html') template: path.join(process.cwd(), 'public', 'index.html')
}) })
] ]
}; };
export default config; export default config;

View file

@ -1,120 +1,120 @@
{ {
"name": "multi-downloader-nx", "name": "multi-downloader-nx",
"short_name": "aniDL", "short_name": "aniDL",
"version": "5.5.6", "version": "5.5.6",
"description": "Downloader for Crunchyroll, Hidive, AnimeOnegai, and AnimationDigitalNetwork with CLI and GUI", "description": "Downloader for Crunchyroll, Hidive, AnimeOnegai, and AnimationDigitalNetwork with CLI and GUI",
"keywords": [ "keywords": [
"download", "download",
"downloader", "downloader",
"hidive", "hidive",
"crunchy", "crunchy",
"crunchyroll", "crunchyroll",
"util", "util",
"utility", "utility",
"cli", "cli",
"gui" "gui"
], ],
"engines": { "engines": {
"node": ">=18", "node": ">=18",
"pnpm": ">=7" "pnpm": ">=7"
}, },
"author": "AnimeDL <AnimeDL@users.noreply.github.com>", "author": "AnimeDL <AnimeDL@users.noreply.github.com>",
"contributors": [ "contributors": [
{ {
"name": "AnimeDL <AnimeDL@users.noreply.github.com>" "name": "AnimeDL <AnimeDL@users.noreply.github.com>"
}, },
{ {
"name": "AniDL <AniDL@users.noreply.github.com>" "name": "AniDL <AniDL@users.noreply.github.com>"
}, },
{ {
"name": "AnidlSupport <AnidlSupport@users.noreply.github.com>" "name": "AnidlSupport <AnidlSupport@users.noreply.github.com>"
} }
], ],
"homepage": "https://github.com/anidl/multi-downloader-nx", "homepage": "https://github.com/anidl/multi-downloader-nx",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/anidl/multi-downloader-nx.git" "url": "https://github.com/anidl/multi-downloader-nx.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/anidl/multi-downloader-nx/issues" "url": "https://github.com/anidl/multi-downloader-nx/issues"
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@bufbuild/buf": "^1.57.2", "@bufbuild/buf": "^1.57.2",
"@bufbuild/protobuf": "^2.8.0", "@bufbuild/protobuf": "^2.8.0",
"@bufbuild/protoc-gen-es": "^2.8.0", "@bufbuild/protoc-gen-es": "^2.8.0",
"@yao-pkg/pkg": "^6.6.0", "@yao-pkg/pkg": "^6.6.0",
"binary-parser": "^2.2.1", "binary-parser": "^2.2.1",
"binary-parser-encoder": "^1.5.3", "binary-parser-encoder": "^1.5.3",
"bn.js": "^5.2.2", "bn.js": "^5.2.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"elliptic": "^6.6.1", "elliptic": "^6.6.1",
"esbuild": "^0.25.10", "esbuild": "^0.25.10",
"express": "^5.1.0", "express": "^5.1.0",
"fast-xml-parser": "^5.2.5", "fast-xml-parser": "^5.2.5",
"ffprobe": "^1.1.2", "ffprobe": "^1.1.2",
"fs-extra": "^11.3.2", "fs-extra": "^11.3.2",
"iso-639": "^0.2.2", "iso-639": "^0.2.2",
"leven": "^3.1.0", "leven": "^3.1.0",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"long": "^5.3.2", "long": "^5.3.2",
"lookpath": "^1.2.3", "lookpath": "^1.2.3",
"m3u8-parsed": "^2.0.0", "m3u8-parsed": "^2.0.0",
"mpd-parser": "^1.3.1", "mpd-parser": "^1.3.1",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"ofetch": "^1.4.1", "ofetch": "^1.4.1",
"open": "^8.4.2", "open": "^8.4.2",
"protobufjs": "^7.5.4", "protobufjs": "^7.5.4",
"puppeteer-real-browser": "^1.4.4", "puppeteer-real-browser": "^1.4.4",
"ws": "^8.18.3", "ws": "^8.18.3",
"yaml": "^2.8.1", "yaml": "^2.8.1",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.35.0", "@eslint/js": "^9.35.0",
"@types/bn.js": "^5.2.0", "@types/bn.js": "^5.2.0",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/elliptic": "^6.4.18", "@types/elliptic": "^6.4.18",
"@types/express": "^5.0.3", "@types/express": "^5.0.3",
"@types/ffprobe": "^1.1.8", "@types/ffprobe": "^1.1.8",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/node": "^24.5.1", "@types/node": "^24.5.1",
"@types/node-forge": "^1.3.14", "@types/node-forge": "^1.3.14",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@types/yargs": "^17.0.33", "@types/yargs": "^17.0.33",
"@typescript-eslint/eslint-plugin": "^8.44.0", "@typescript-eslint/eslint-plugin": "^8.44.0",
"@typescript-eslint/parser": "^8.44.0", "@typescript-eslint/parser": "^8.44.0",
"eslint": "^9.35.0", "eslint": "^9.35.0",
"protoc": "^1.1.3", "protoc": "^1.1.3",
"removeNPMAbsolutePaths": "^3.0.1", "removeNPMAbsolutePaths": "^3.0.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"typescript-eslint": "^8.44.0" "typescript-eslint": "^8.44.0"
}, },
"scripts": { "scripts": {
"prestart": "pnpm run tsc test", "prestart": "pnpm run tsc test",
"start": "pnpm prestart && cd lib && node gui.js", "start": "pnpm prestart && cd lib && node gui.js",
"gui": "cd ./gui/react/ && pnpm start", "gui": "cd ./gui/react/ && pnpm start",
"docs": "ts-node modules/build-docs.ts", "docs": "ts-node modules/build-docs.ts",
"tsc": "ts-node tsc.ts", "tsc": "ts-node tsc.ts",
"proto:compile": "protoc --plugin=protoc-gen-ts_proto=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_opt=\"esModuleInterop=true\" --ts_proto_opt=\"forceLong=long\" --ts_proto_opt=\"env=node\" --ts_proto_out=. modules/*.proto", "proto:compile": "protoc --plugin=protoc-gen-ts_proto=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_opt=\"esModuleInterop=true\" --ts_proto_opt=\"forceLong=long\" --ts_proto_opt=\"env=node\" --ts_proto_out=. modules/*.proto",
"prebuild-cli": "pnpm run tsc false false", "prebuild-cli": "pnpm run tsc false false",
"build-windows-cli": "pnpm run prebuild-cli && cd lib && node modules/build windows-x64", "build-windows-cli": "pnpm run prebuild-cli && cd lib && node modules/build windows-x64",
"build-linux-cli": "pnpm run prebuild-cli && cd lib && node modules/build linuxstatic-x64", "build-linux-cli": "pnpm run prebuild-cli && cd lib && node modules/build linuxstatic-x64",
"build-arm-cli": "pnpm run prebuild-cli && cd lib && node modules/build linux-arm64", "build-arm-cli": "pnpm run prebuild-cli && cd lib && node modules/build linux-arm64",
"build-macos-cli": "pnpm run prebuild-cli && cd lib && node modules/build macos-x64", "build-macos-cli": "pnpm run prebuild-cli && cd lib && node modules/build macos-x64",
"build-alpine-cli": "pnpm run prebuild-cli && cd lib && node modules/build alpine-x64", "build-alpine-cli": "pnpm run prebuild-cli && cd lib && node modules/build alpine-x64",
"build-android-cli": "pnpm run prebuild-cli && cd lib && node modules/build linuxstatic-armv7", "build-android-cli": "pnpm run prebuild-cli && cd lib && node modules/build linuxstatic-armv7",
"prebuild-gui": "pnpm run tsc", "prebuild-gui": "pnpm run tsc",
"build-windows-gui": "pnpm run prebuild-gui && cd lib && node modules/build windows-x64 true", "build-windows-gui": "pnpm run prebuild-gui && cd lib && node modules/build windows-x64 true",
"build-linux-gui": "pnpm run prebuild-gui && cd lib && node modules/build linuxstatic-x64 true", "build-linux-gui": "pnpm run prebuild-gui && cd lib && node modules/build linuxstatic-x64 true",
"build-arm-gui": "pnpm run prebuild-gui && cd lib && node modules/build linux-arm64 true", "build-arm-gui": "pnpm run prebuild-gui && cd lib && node modules/build linux-arm64 true",
"build-macos-gui": "pnpm run prebuild-gui && cd lib && node modules/build macos-x64 true", "build-macos-gui": "pnpm run prebuild-gui && cd lib && node modules/build macos-x64 true",
"build-alpine-gui": "pnpm run prebuild-gui && cd lib && node modules/build alpine-x64 true", "build-alpine-gui": "pnpm run prebuild-gui && cd lib && node modules/build alpine-x64 true",
"build-android-gui": "pnpm run prebuild-gui && cd lib && node modules/build linuxstatic-armv7 true", "build-android-gui": "pnpm run prebuild-gui && cd lib && node modules/build linuxstatic-armv7 true",
"eslint": "npx eslint .", "eslint": "npx eslint .",
"eslint-fix": "npx eslint . --fix", "eslint-fix": "npx eslint . --fix",
"pretest": "pnpm run tsc", "pretest": "pnpm run tsc",
"test": "pnpm run pretest && cd lib && node modules/build windows-x64 && node modules/build linuxstatic-x64 && node modules/build macos-x64" "test": "pnpm run pretest && cd lib && node modules/build windows-x64 && node modules/build linuxstatic-x64 && node modules/build macos-x64"
} }
} }

242
tsc.ts
View file

@ -10,156 +10,156 @@ const isTest = argv.length > 0 && argv[0] === 'test';
const isGUI = !(argv.length > 1 && argv[1] === 'false'); const isGUI = !(argv.length > 1 && argv[1] === 'false');
if (!isTest) if (!isTest)
buildIgnore = [ buildIgnore = [
'*/\\.env', '*/\\.env',
'./config/setup.json' './config/setup.json'
]; ];
if (!isGUI) if (!isGUI)
buildIgnore = buildIgnore.concat([ buildIgnore = buildIgnore.concat([
'./gui*', './gui*',
'./build*', './build*',
'gui.ts' 'gui.ts'
]); ]);
const ignore = [ const ignore = [
...buildIgnore, ...buildIgnore,
'*/\\.git*', '*/\\.git*',
'./lib*', './lib*',
'*/@types*', '*/@types*',
'./out*', './out*',
'./bin/mkvtoolnix*', './bin/mkvtoolnix*',
'./config/token.yml$', './config/token.yml$',
'./config/updates.json$', './config/updates.json$',
'./config/*_token.yml$', './config/*_token.yml$',
'./config/*_sess.yml$', './config/*_sess.yml$',
'./config/*_profile.yml$', './config/*_profile.yml$',
'*/\\.eslint*', '*/\\.eslint*',
'*/*\\.tsx?$', '*/*\\.tsx?$',
'./fonts*', './fonts*',
'./gui/react*', './gui/react*',
'./dev.js$', './dev.js$',
'*/node_modules/*', '*/node_modules/*',
'./widevine/*', './widevine/*',
'./playready/*', './playready/*',
'./videos/*', './videos/*',
'./logs/*', './logs/*',
].map(a => a.replace(/\*/g, '[^]*').replace(/\.\//g, escapeRegExp(__dirname) + '/').replace(/\//g, path.sep === '\\' ? '\\\\' : '/')).map(a => new RegExp(a, 'i')); ].map(a => a.replace(/\*/g, '[^]*').replace(/\.\//g, escapeRegExp(__dirname) + '/').replace(/\//g, path.sep === '\\' ? '\\\\' : '/')).map(a => new RegExp(a, 'i'));
export { ignore }; export { ignore };
(async () => { (async () => {
const waitForProcess = async (proc: ChildProcess) => { const waitForProcess = async (proc: ChildProcess) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
proc.stdout?.on('data', console.log); proc.stdout?.on('data', console.log);
proc.stderr?.on('data', console.error); proc.stderr?.on('data', console.error);
proc.on('close', resolve); proc.on('close', resolve);
proc.on('error', reject); proc.on('error', reject);
}); });
}; };
process.stdout.write('Removing lib dir... '); process.stdout.write('Removing lib dir... ');
removeSync('lib'); removeSync('lib');
process.stdout.write('✓\nRunning tsc... '); process.stdout.write('✓\nRunning tsc... ');
const tsc = exec('npx tsc'); const tsc = exec('npx tsc');
await waitForProcess(tsc); await waitForProcess(tsc);
if (!isGUI) {
fs.emptyDirSync(path.join('lib', 'gui'));
fs.rmdirSync(path.join('lib', 'gui'));
}
if (!isTest && isGUI) { if (!isGUI) {
process.stdout.write('✓\nBuilding react... '); fs.emptyDirSync(path.join('lib', 'gui'));
fs.rmdirSync(path.join('lib', 'gui'));
}
const installReactDependencies = exec('pnpm install', { if (!isTest && isGUI) {
cwd: path.join(__dirname, 'gui', 'react'), process.stdout.write('✓\nBuilding react... ');
});
await waitForProcess(installReactDependencies); const installReactDependencies = exec('pnpm install', {
cwd: path.join(__dirname, 'gui', 'react'),
const react = exec('pnpm run build', { });
cwd: path.join(__dirname, 'gui', 'react'),
env: {
...process.env,
CI: 'false'
}
});
await waitForProcess(react);
}
process.stdout.write('✓\nCopying files... '); await waitForProcess(installReactDependencies);
if (!isTest && isGUI) {
copyDir(path.join(__dirname, 'gui', 'react', 'build'), path.join(__dirname, 'lib', 'gui', 'server', 'build'));
}
const files = readDir(__dirname); const react = exec('pnpm run build', {
files.forEach(item => { cwd: path.join(__dirname, 'gui', 'react'),
const itemPath = path.join(__dirname, 'lib', item.path.replace(__dirname, '')); env: {
if (item.stats.isDirectory()) { ...process.env,
if (!fs.existsSync(itemPath)) CI: 'false'
fs.mkdirSync(itemPath); }
} else { });
copyFileSync(item.path, itemPath);
}
});
process.stdout.write('✓\nInstalling dependencies... '); await waitForProcess(react);
if (!isTest) { }
const dependencies = exec(`pnpm install ${isGUI ? '' : '-P'}`, {
cwd: path.join(__dirname, 'lib')
});
await waitForProcess(dependencies);
}
process.stdout.write('✓\n'); process.stdout.write('✓\nCopying files... ');
if (!isTest && isGUI) {
copyDir(path.join(__dirname, 'gui', 'react', 'build'), path.join(__dirname, 'lib', 'gui', 'server', '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) {
const dependencies = exec(`pnpm install ${isGUI ? '' : '-P'}`, {
cwd: path.join(__dirname, 'lib')
});
await waitForProcess(dependencies);
}
process.stdout.write('✓\n');
})(); })();
function readDir (dir: string): { function readDir(dir: string): {
path: string, path: string,
stats: fs.Stats stats: fs.Stats
}[] { }[] {
const items: { const items: {
path: string, path: string,
stats: fs.Stats stats: fs.Stats
}[] = []; }[] = [];
const content = fs.readdirSync(dir); const content = fs.readdirSync(dir);
itemLoop: for (const item of content) { itemLoop: for (const item of content) {
const itemPath = path.join(dir, item); const itemPath = path.join(dir, item);
for (const ignoreItem of ignore) { for (const ignoreItem of ignore) {
if (ignoreItem.test(itemPath)) if (ignoreItem.test(itemPath))
continue itemLoop; continue itemLoop;
} }
const stats = fs.statSync(itemPath); const stats = fs.statSync(itemPath);
items.push({ items.push({
path: itemPath, path: itemPath,
stats stats
}); });
if (stats.isDirectory()) { if (stats.isDirectory()) {
items.push(...readDir(itemPath)); items.push(...readDir(itemPath));
} }
} }
return items; return items;
} }
async function copyDir(src: string, dest: string) { async function copyDir(src: string, dest: string) {
await fs.promises.mkdir(dest, { recursive: true }); await fs.promises.mkdir(dest, { recursive: true });
const entries = await fs.promises.readdir(src, { withFileTypes: true }); const entries = await fs.promises.readdir(src, { withFileTypes: true });
for (const entry of entries) { for (const entry of entries) {
const srcPath = path.join(src, entry.name); const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name); const destPath = path.join(dest, entry.name);
entry.isDirectory() ? entry.isDirectory() ?
await copyDir(srcPath, destPath) : await copyDir(srcPath, destPath) :
await fs.promises.copyFile(srcPath, destPath); await fs.promises.copyFile(srcPath, destPath);
} }
} }
function escapeRegExp(string: string): string { function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
} }

View file

@ -1,20 +1,20 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"module": "commonjs", "module": "commonjs",
"outDir": "./lib", "outDir": "./lib",
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"downlevelIteration": true, "downlevelIteration": true,
"jsx": "react" "jsx": "react"
}, },
"exclude": [ "exclude": [
"./videos", "./videos",
"./tsc.ts", "./tsc.ts",
"lib/**/*", "lib/**/*",
"gui/react/**/*" "gui/react/**/*"
] ]
} }