prettier fix + updated to node 22 + added new workflow + crunchyroll updates + --version fix

This commit is contained in:
stratumadev 2025-09-30 14:43:25 +02:00
parent 8365e9d9e4
commit 130d964b64
144 changed files with 19708 additions and 18998 deletions

View file

@ -1,93 +1,93 @@
name: Bug name: Bug
description: File a bug report description: File a bug report
assignees: assignees:
- AnimeDL - AnimeDL
- AnidlSupport - AnidlSupport
labels: labels:
- bug - bug
title: "[BUG]: " title: '[BUG]: '
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Thank you for reporting the issues you found and have with this program. Thank you for reporting the issues you found and have with this program.
This template will guide you through all the information we need. This template will guide you through all the information we need.
- type: input - type: input
id: version id: version
attributes: attributes:
label: Program version label: Program version
description: "Which version of the program do you use?" description: 'Which version of the program do you use?'
placeholder: "1.0.0" placeholder: '1.0.0'
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
id: opsystem id: opsystem
attributes: attributes:
label: Operating System label: Operating System
description: "Please tell us what OS you are using." description: 'Please tell us what OS you are using.'
options: options:
- Windows - Windows
- Linux - Linux
- MacOS - MacOS
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
id: gui id: gui
attributes: attributes:
label: Type label: Type
description: "Please tell us if you are using the gui or the cli version." description: 'Please tell us if you are using the gui or the cli version.'
options: options:
- CLI - CLI
- GUI - GUI
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
id: service id: service
attributes: attributes:
label: Service label: Service
description: "Please tell us what service the bug occured in." description: 'Please tell us what service the bug occured in.'
options: options:
- Crunchyroll - Crunchyroll
- Hidive - Hidive
- AnimationDigitalNetwork - AnimationDigitalNetwork
- AnimeOnegai - AnimeOnegai
- All - All
- Irrelevant - Irrelevant
validations: validations:
required: true required: true
- type: input - type: input
id: command id: command
attributes: attributes:
label: Command used label: Command used
description: "Please tell us what command you used." description: 'Please tell us what command you used.'
validations: validations:
required: true required: true
- type: input - type: input
id: ShowID id: ShowID
attributes: attributes:
label: Show ID label: Show ID
description: "Please provide the ID of an example show." description: 'Please provide the ID of an example show.'
placeholder: "1234" placeholder: '1234'
validations: validations:
required: true required: true
- type: input - type: input
id: Episode id: Episode
attributes: attributes:
label: Episode label: Episode
description: "Please provide the episode ID you used as an example." description: 'Please provide the episode ID you used as an example.'
placeholder: "1" placeholder: '1'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: output id: output
attributes: attributes:
label: Console Output label: Console Output
description: "Please paste the console output from the beginning till termination here. If you are using the gui open the log folder under 'Debug > Open Log Folder' in the Menu. Please copy the content of latest.log here." description: "Please paste the console output from the beginning till termination here. If you are using the gui open the log folder under 'Debug > Open Log Folder' in the Menu. Please copy the content of latest.log here."
render: Shell render: Shell
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: additionalInfos id: additionalInfos
attributes: attributes:
label: Additional Information label: Additional Information
description: "Do you have any additional information you can provide?" description: 'Do you have any additional information you can provide?'

View file

@ -1,29 +1,29 @@
name: Enhancement name: Enhancement
description: Suggest a enhancement or feature description: Suggest a enhancement or feature
labels: labels:
- enhancement - enhancement
title: "[Feedback]: " title: '[Feedback]: '
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Thank you for giving feedback with this program. Thank you for giving feedback with this program.
This template will guide you through all the information we need. This template will guide you through all the information we need.
- type: dropdown - type: dropdown
id: programversion id: programversion
attributes: attributes:
label: Type label: Type
description: "Is this suggestion for the CLI, GUI, or Both?" description: 'Is this suggestion for the CLI, GUI, or Both?'
options: options:
- CLI - CLI
- GUI - GUI
- Both - Both
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: suggestion id: suggestion
attributes: attributes:
label: Suggestion label: Suggestion
description: "What is your suggestion?" description: 'What is your suggestion?'
validations: validations:
required: true required: true

View file

@ -1,6 +1,11 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: "npm" - package-ecosystem: 'npm'
directory: "/" directory: '/'
schedule: schedule:
interval: "daily" interval: 'daily'
open-pull-requests-limit: 0
allow:
- dependency-type: 'direct'
ignore:
- dependency-type: 'all'

View file

@ -1,26 +1,26 @@
name: auto-documentation name: auto-documentation
on: on:
push: push:
branches: [ master ] branches: [master]
jobs: jobs:
documentation: documentation:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with: with:
version: latest version: latest
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 20 node-version: 22
- run: pnpm i - run: pnpm i
- run: pnpm run docs - run: pnpm run docs
- uses: stefanzweifel/git-auto-commit-action@v4 - uses: stefanzweifel/git-auto-commit-action@v4
with: with:
commit_message: ${{ github.event.head_commit.message }} + Documentation commit_message: ${{ github.event.head_commit.message }} + Documentation

View file

@ -3,31 +3,30 @@
name: build and push docker image name: build and push docker image
on: on:
push: push:
branches: [ master ] branches: [master]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-node: build-node:
runs-on: ubuntu-latest
runs-on: ubuntu-latest steps:
steps: - uses: actions/checkout@v2
- uses: actions/checkout@v2 - name: Set up Docker Buildx
- name: Set up Docker Buildx id: buildx
id: buildx uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v1 - name: Login to DockerHub
- name: Login to DockerHub if: ${{ github.ref == 'refs/heads/master' }}
if: ${{ github.ref == 'refs/heads/master' }} uses: docker/login-action@v1
uses: docker/login-action@v1 with:
with: username: ${{ secrets.DOCKERHUB_USERNAME }}
username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }}
password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker images
- name: Build and push Docker images uses: docker/build-push-action@v2.9.0
uses: docker/build-push-action@v2.9.0 with:
with: github-token: ${{ github.token }}
github-token: ${{ github.token }} push: ${{ github.ref == 'refs/heads/master' }}
push: ${{ github.ref == 'refs/heads/master' }} tags: |
tags: | "multidl/multi-downloader-nx:latest"
"multidl/multi-downloader-nx:latest" - name: Image digest
- name: Image digest run: echo ${{ steps.docker_build.outputs.digest }}
run: echo ${{ steps.docker_build.outputs.digest }}

View file

@ -1,66 +1,66 @@
name: Release Builds name: Release Builds
on: on:
release: release:
types: [ published ] types: [published]
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
build_type: [ linux, macos, windows ] build_type: [linux, macos, windows]
build_arch: [ x64 ] build_arch: [x64]
gui: [ gui, cli ] gui: [gui, cli]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with: with:
version: latest version: latest
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 20 node-version: 22
check-latest: true check-latest: true
- name: Install Node modules - name: Install Node modules
run: | run: |
pnpm install pnpm install
- name: Get name and version from package.json - name: Get name and version from package.json
run: | run: |
test -n $(node -p -e "require('./package.json').name") && test -n $(node -p -e "require('./package.json').name") &&
test -n $(node -p -e "require('./package.json').version") && test -n $(node -p -e "require('./package.json').version") &&
echo PACKAGE_NAME=$(node -p -e "require('./package.json').name") >> $GITHUB_ENV && echo PACKAGE_NAME=$(node -p -e "require('./package.json').name") >> $GITHUB_ENV &&
echo PACKAGE_VERSION=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV || exit 1 echo PACKAGE_VERSION=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV || exit 1
- name: Make build - name: Make build
run: pnpm run build-${{ matrix.build_type }}-${{ matrix.gui }} run: pnpm run build-${{ matrix.build_type }}-${{ matrix.gui }}
- name: Upload release - name: Upload release
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
with: with:
upload_url: ${{ github.event.release.upload_url }} upload_url: ${{ github.event.release.upload_url }}
asset_name: multi-downloader-nx-${{ matrix.build_type }}-${{ matrix.gui }}.7z asset_name: multi-downloader-nx-${{ matrix.build_type }}-${{ matrix.gui }}.7z
asset_path: ./lib/_builds/multi-downloader-nx-${{ matrix.build_type }}-${{ matrix.build_arch }}-${{ matrix.gui }}.7z asset_path: ./lib/_builds/multi-downloader-nx-${{ matrix.build_type }}-${{ matrix.build_arch }}-${{ matrix.gui }}.7z
asset_content_type: application/x-7z-compressed asset_content_type: application/x-7z-compressed
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-docker: build-docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker images - name: Build and push Docker images
uses: docker/build-push-action@v2.9.0 uses: docker/build-push-action@v2.9.0
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
push: true push: true
tags: | tags: |
"multidl/multi-downloader-nx:${{ github.event.release.tag_name }}" "multidl/multi-downloader-nx:${{ github.event.release.tag_name }}"
- name: Image digest - name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}

View file

@ -7,6 +7,20 @@ on:
branches: [ master ] branches: [ master ]
jobs: jobs:
tsc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: latest
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 22
check-latest: true
- run: pnpm i
- run: npx tsc
eslint: eslint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -17,10 +31,24 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 20 node-version: 22
check-latest: true check-latest: true
- run: pnpm i - run: pnpm i
- run: npx eslint . --quiet - run: pnpm run eslint
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: latest
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 22
check-latest: true
- run: pnpm i
- run: pnpm run prettier
test: test:
needs: eslint needs: eslint
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -32,7 +60,7 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 20 node-version: 22
check-latest: true check-latest: true
- run: pnpm i - run: pnpm i
- run: pnpm run test - run: pnpm run test

23
.prettierignore Normal file
View file

@ -0,0 +1,23 @@
# Build
build
lib
# Local
.vscode
.idea
.DS_Store
node_modules
logs
ffmpeg
mkvmerge
fonts
*.json
*.md
*.yaml
!package.json
!tsconfig.json
docker-compose.yml
# Auto generated
docs

View file

@ -1,20 +1,20 @@
{ {
"arrowParens": "always", "arrowParens": "always",
"bracketSameLine": false, "bracketSameLine": false,
"bracketSpacing": true, "bracketSpacing": true,
"embeddedLanguageFormatting": "auto", "embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "strict", "htmlWhitespaceSensitivity": "strict",
"insertPragma": false, "insertPragma": false,
"jsxSingleQuote": false, "jsxSingleQuote": false,
"proseWrap": "never", "proseWrap": "never",
"quoteProps": "as-needed", "quoteProps": "as-needed",
"requirePragma": false, "requirePragma": false,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"tabWidth": 4, "tabWidth": 4,
"trailingComma": "none", "trailingComma": "none",
"useTabs": true, "useTabs": true,
"vueIndentScriptAndStyle": false, "vueIndentScriptAndStyle": false,
"printWidth": 180, "printWidth": 180,
"endOfLine": "auto" "endOfLine": "auto"
} }

View file

@ -3,48 +3,48 @@ export interface ADNPlayerConfig {
} }
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;
} }

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

@ -4,40 +4,40 @@ export interface ADNSearch {
} }
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 {

View file

@ -1,14 +1,14 @@
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;
} }
@ -18,10 +18,10 @@ export interface Streaming {
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 {
@ -29,23 +29,23 @@ export interface Subtitles {
} }
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

@ -3,9 +3,9 @@ export interface ADNSubtitles {
} }
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;
} }

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

@ -3,72 +3,72 @@ export interface ADNVideos {
} }
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 {

View file

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

View file

@ -1,85 +1,85 @@
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 {
@ -87,9 +87,9 @@ export interface Link {
} }
export interface Thumbnail { export interface Thumbnail {
width: number; width: number;
height: number; height: number;
type: string; type: string;
source: string; source: string;
} }
@ -109,28 +109,27 @@ export enum Locale {
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,49 +1,49 @@
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 {
@ -51,119 +51,119 @@ export interface LinkData {
} }
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 {
@ -182,5 +182,5 @@ export enum Locale {
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,37 +1,21 @@
export interface CrunchyAndroidStreams {
__class__: string;
__href__: string;
__resource_key__: string;
__links__: Links;
__actions__: Record<unknown, unknown>;
media_id: string;
audio_locale: Locale;
subtitles: Subtitles;
closed_captions: Subtitles;
streams: Streams;
bifs: string[];
versions: Version[];
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 {
@ -48,8 +32,8 @@ export interface Streams {
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 {
@ -58,17 +42,17 @@ export interface Urls {
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;
} }
@ -89,5 +73,5 @@ export enum Locale {
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,26 +1,26 @@
export interface CrunchyChapters { export interface CrunchyChapters {
[key: string]: CrunchyChapter; [key: string]: CrunchyChapter;
lastUpdate: Date; lastUpdate: Date;
mediaId: string; mediaId: string;
} }
export interface CrunchyChapter { export interface CrunchyChapter {
approverId: string; approverId: string;
distributionNumber: string; distributionNumber: string;
end: number; end: number;
start: number; start: number;
title: string; title: string;
seriesId: string; seriesId: string;
new: boolean; new: boolean;
type: string; type: string;
} }
export interface CrunchyOldChapter { export interface CrunchyOldChapter {
media_id: string; media_id: string;
startTime: number; startTime: number;
endTime: number; endTime: number;
duration: number; duration: number;
comparedWith: string; comparedWith: string;
ordering: string; ordering: string;
last_updated: Date; last_updated: Date;
} }

View file

@ -2,69 +2,69 @@ 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 {
@ -83,52 +83,52 @@ export enum Locale {
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,44 +1,44 @@
import { Locale } from './playbackData'; import { Locale } from './playbackData';
export interface CrunchyPlayStream { export interface CrunchyPlayStream {
assetId: string; assetId: string;
audioLocale: Locale; audioLocale: Locale;
bifs: string; bifs: string;
burnedInLocale: string; burnedInLocale: string;
captions: { [key: string]: Caption }; captions: { [key: string]: Caption };
hardSubs: { [key: string]: HardSub }; hardSubs: { [key: string]: HardSub };
playbackType: string; playbackType: string;
session: Session; session: Session;
subtitles: { [key: string]: Subtitle }; subtitles: { [key: string]: Subtitle };
token: string; token: string;
url: string; url: string;
versions: any[]; versions: any[];
} }
export interface Caption { export interface Caption {
format: string; format: string;
language: string; language: string;
url: string; url: string;
} }
export interface HardSub { export interface HardSub {
hlang: string; hlang: string;
url: string; url: string;
quality: string; quality: string;
} }
export interface Session { export interface Session {
renewSeconds: number; renewSeconds: number;
noNetworkRetryIntervalSeconds: number; noNetworkRetryIntervalSeconds: number;
noNetworkTimeoutSeconds: number; noNetworkTimeoutSeconds: number;
maximumPauseSeconds: number; maximumPauseSeconds: number;
endOfVideoUnloadSeconds: number; endOfVideoUnloadSeconds: number;
sessionExpirationSeconds: number; sessionExpirationSeconds: number;
usesStreamLimits: boolean; usesStreamLimits: boolean;
} }
export interface Subtitle { export interface Subtitle {
format: string; format: string;
language: string; language: string;
url: string; url: string;
} }

View file

@ -2,79 +2,79 @@
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 {
@ -93,65 +93,65 @@ export enum Locale {
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 {
@ -159,25 +159,25 @@ export interface SearchMetadata {
} }
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,180 +5,191 @@ 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[];
} // Subtitle Fix Options
srtAssFix: boolean;
layoutResFix: boolean;
scaledBorderAndShadowFix: boolean;
scaledBorderAndShadow: 'yes' | 'no';
originalScriptFix: boolean;
};
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', | {
lang: LanguageItem, type: 'Video';
path: string, lang: LanguageItem;
isPrimary?: boolean path: string;
} | { isPrimary?: boolean;
type: 'Audio', }
lang: LanguageItem, | {
path: string, type: 'Audio';
isPrimary?: boolean lang: LanguageItem;
} | { path: string;
type: 'Chapters', isPrimary?: boolean;
lang: LanguageItem, }
path: string | {
} | ({ type: 'Chapters';
type: 'Subtitle', lang: LanguageItem;
signs: boolean, path: string;
cc: boolean }
} & sxItem ) | ({
type: 'Subtitle';
signs: boolean;
cc: boolean;
} & 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 {
@ -207,7 +218,7 @@ export enum Locale {
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 {

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'
} }

598
@types/episode.d.ts vendored
View file

@ -1,391 +1,391 @@
// Generated by https://quicktype.io // Generated by https://quicktype.io
export interface EpisodeData { export interface EpisodeData {
id: number; id: number;
title: string; title: string;
mediaDict: { [key: string]: string }; mediaDict: { [key: string]: string };
episodeSlug: string; episodeSlug: string;
starRating: number; starRating: number;
parent: EpisodeDataParent; parent: EpisodeDataParent;
number: string; number: string;
description: string; description: string;
filename: string; filename: string;
seriesBanner: string; seriesBanner: string;
media: Media[]; media: Media[];
externalItemId: string; externalItemId: string;
contentId: string; contentId: string;
metaItems: MetaItems; metaItems: MetaItems;
thumb: string; thumb: string;
type: Type; type: Type;
default: { [key: string]: Default }; default: { [key: string]: Default };
published: boolean; published: boolean;
versions: VersionClass[]; versions: VersionClass[];
mediaCategory: string; mediaCategory: string;
order: number; order: number;
seriesVersions: any[]; seriesVersions: any[];
source: Source; source: Source;
ids: EpisodeDataIDS; ids: EpisodeDataIDS;
runtime: string; runtime: string;
siblings: PreviousSeasonEpisode[]; siblings: PreviousSeasonEpisode[];
seriesTitle: string; seriesTitle: string;
seriesSlug: string; seriesSlug: string;
next: Next; next: Next;
previousSeasonEpisode: PreviousSeasonEpisode; previousSeasonEpisode: PreviousSeasonEpisode;
seasonTitle: string; seasonTitle: string;
quality: Quality; quality: Quality;
ratings: Array<string[]>; ratings: Array<string[]>;
languages: TitleElement[]; languages: TitleElement[];
releaseDate: string; releaseDate: string;
historicalSelections: HistoricalSelections; historicalSelections: HistoricalSelections;
userRating: UserRating; userRating: UserRating;
} }
export interface Default { export interface Default {
items: DefaultItem[]; items: DefaultItem[];
} }
export interface DefaultItem { export interface DefaultItem {
languages: string[]; languages: string[];
territories: string[]; territories: string[];
version: null; version: null;
value: Value[]; value: Value[];
devices: any[]; devices: any[];
} }
export interface Value { export interface Value {
name: MetaType; name: MetaType;
value: string; value: string;
label: Label; label: Label;
} }
export enum Label { export enum Label {
Rating = 'Rating', Rating = 'Rating',
RatingSystem = 'Rating System', RatingSystem = 'Rating System',
ReleaseDate = 'Release Date', ReleaseDate = 'Release Date',
Synopsis = 'Synopsis', Synopsis = 'Synopsis',
SynopsisType = 'Synopsis Type', SynopsisType = 'Synopsis Type'
} }
export enum MetaType { export enum MetaType {
Rating = 'rating', Rating = 'rating',
RatingSystemType = 'RatingSystemType', RatingSystemType = 'RatingSystemType',
ReleaseDate = 'release-date', ReleaseDate = 'release-date',
Synopsis = 'synopsis', Synopsis = 'synopsis',
Synopsistype = 'synopsistype', Synopsistype = 'synopsistype',
VideoRatingType = 'VideoRatingType', VideoRatingType = 'VideoRatingType'
} }
export interface HistoricalSelections { export interface HistoricalSelections {
version: string; version: string;
language: string; language: string;
} }
export interface EpisodeDataIDS { export interface EpisodeDataIDS {
externalShowId: string; externalShowId: string;
externalSeasonId: string; externalSeasonId: string;
externalEpisodeId: string; externalEpisodeId: string;
} }
export enum TitleElement { export enum TitleElement {
Empty = '', Empty = '',
English = 'English', English = 'English'
} }
export interface Media { export interface Media {
id: number; id: number;
title: string; title: string;
experienceType: string; experienceType: string;
created: string; created: string;
createdBy: string; createdBy: string;
itemFieldData: Next; itemFieldData: Next;
keyPath: string; keyPath: string;
filename: string; filename: string;
complianceStatus: null; complianceStatus: null;
events: any[]; events: any[];
clients: string[]; clients: string[];
qcStatus: null; qcStatus: null;
qcStatusDate: null; qcStatusDate: null;
image: string; image: string;
thumb: string; thumb: string;
ext: string; ext: string;
avails: Avail[]; avails: Avail[];
version: string; version: string;
startTimecode: null; startTimecode: null;
endTimecode: null; endTimecode: null;
versionId: string; versionId: string;
mediaType: string; mediaType: string;
status: string; status: string;
languages: LanguageClass[]; languages: LanguageClass[];
territories: any[]; territories: any[];
devices: any[]; devices: any[];
keyType: string; keyType: string;
purpose: null; purpose: null;
externalItemId: null | string; externalItemId: null | string;
proxyId: null; proxyId: null;
externalDbId: null; externalDbId: null;
mediaChildren: MediaChild[]; mediaChildren: MediaChild[];
isDefault: boolean; isDefault: boolean;
parent: MediaChildParent; parent: MediaChildParent;
filePath: null | string; filePath: null | string;
mediaInfo: Next; mediaInfo: Next;
type: string; type: string;
approved: boolean; approved: boolean;
mediaKey: string; mediaKey: string;
itemFields: any[]; itemFields: any[];
source: Source; source: Source;
fieldData: Next; fieldData: Next;
sourceId: null | string; sourceId: null | string;
timecodeOverride: null; timecodeOverride: null;
seriesTitle: string; seriesTitle: string;
episodeTitle: string; episodeTitle: string;
genre: any[]; genre: any[];
txDate: string; txDate: string;
description: string; description: string;
synopsis: string; synopsis: string;
resolution: null; resolution: null;
restrictedAccess: boolean; restrictedAccess: boolean;
createdById: string; createdById: string;
userIdsWithAccess: any[]; userIdsWithAccess: any[];
runtime?: number; runtime?: number;
language?: TitleElement; language?: TitleElement;
purchased: boolean; purchased: boolean;
} }
export interface Avail { export interface Avail {
id: number; id: number;
description: string; description: string;
endDate: string; endDate: string;
startDate: string; startDate: string;
ids: AvailIDS; ids: AvailIDS;
originalAirDate: null; originalAirDate: null;
physicalReleaseDate: null; physicalReleaseDate: null;
preorderDate: null; preorderDate: null;
language: TitleElement; language: TitleElement;
territory: string; territory: string;
territoryCode: string; territoryCode: string;
license: string; license: string;
parentAvail: null; parentAvail: null;
item: number; item: number;
version: string; version: string;
applyToLevel: null; applyToLevel: null;
availLevel: string; availLevel: string;
availDisplayCode: string; availDisplayCode: string;
availStatus: string; availStatus: string;
bundleOnly: boolean; bundleOnly: boolean;
contentOwnerOrganization: string; contentOwnerOrganization: string;
currency: null; currency: null;
price: null; price: null;
purchase: string; purchase: string;
priceValue: string; priceValue: string;
resolutionFormat: null; resolutionFormat: null;
runtimeMilliseconds: null; runtimeMilliseconds: null;
seasonOrEpisodeNumber: null; seasonOrEpisodeNumber: null;
tmsid: null; tmsid: null;
deviceList: string; deviceList: string;
tvodSku: null; tvodSku: null;
} }
export interface AvailIDS { export interface AvailIDS {
externalSeasonId: string; externalSeasonId: string;
externalAsianId: null; externalAsianId: null;
externalShowId: string; externalShowId: string;
externalEpisodeId: string; externalEpisodeId: string;
externalEnglishId: string; externalEnglishId: string;
externalAlphaId: string; externalAlphaId: string;
} }
export type Next = Record<string, unknown> export type Next = Record<string, unknown>;
export interface LanguageClass { export interface LanguageClass {
code: string; code: string;
id: number; id: number;
title: TitleElement; title: TitleElement;
} }
export interface MediaChild { export interface MediaChild {
id: number; id: number;
title: string; title: string;
experienceType: string; experienceType: string;
created: string; created: string;
createdBy: string; createdBy: string;
itemFieldData: Next; itemFieldData: Next;
keyPath: null; keyPath: null;
filename: string; filename: string;
complianceStatus: null; complianceStatus: null;
events: any[]; events: any[];
clients: string[]; clients: string[];
qcStatus: null; qcStatus: null;
qcStatusDate: null; qcStatusDate: null;
image: string; image: string;
ext: string; ext: string;
avails: any[]; avails: any[];
version: string; version: string;
startTimecode: null; startTimecode: null;
endTimecode: null; endTimecode: null;
versionId: string; versionId: string;
mediaType: string; mediaType: string;
status: string; status: string;
languages: LanguageClass[]; languages: LanguageClass[];
territories: any[]; territories: any[];
devices: any[]; devices: any[];
keyType: string; keyType: string;
purpose: null; purpose: null;
externalItemId: string; externalItemId: string;
proxyId: null; proxyId: null;
externalDbId: null; externalDbId: null;
mediaChildren: any[]; mediaChildren: any[];
isDefault: boolean; isDefault: boolean;
parent: MediaChildParent; parent: MediaChildParent;
filePath: string; filePath: string;
mediaInfo: MediaInfo; mediaInfo: MediaInfo;
type: string; type: string;
approved: boolean; approved: boolean;
mediaKey: null; mediaKey: null;
itemFields: any[]; itemFields: any[];
source: Source; source: Source;
fieldData: Next; fieldData: Next;
sourceId: null; sourceId: null;
timecodeOverride: null; timecodeOverride: null;
seriesTitle: string; seriesTitle: string;
episodeTitle: string; episodeTitle: string;
genre: any[]; genre: any[];
txDate: string; txDate: string;
description: string; description: string;
synopsis: string; synopsis: string;
resolution: null | string; resolution: null | string;
restrictedAccess: boolean; restrictedAccess: boolean;
createdById: string; createdById: string;
userIdsWithAccess: any[]; userIdsWithAccess: any[];
language: TitleElement; language: TitleElement;
} }
export interface MediaInfo { export interface MediaInfo {
imageAspectRatio: null | string; imageAspectRatio: null | string;
format: string; format: string;
scanMode: null | string; scanMode: null | string;
burnedInSubtitleLanguage: string; burnedInSubtitleLanguage: string;
screenAspectRatio: null | string; screenAspectRatio: null | string;
subtitleFormat: null | string; subtitleFormat: null | string;
subtitleContent: null | string; subtitleContent: null | string;
frameHeight: number | null; frameHeight: number | null;
frameWidth: number | null; frameWidth: number | null;
video: Video; video: Video;
} }
export interface Video { export interface Video {
codecId: null | string; codecId: null | string;
container: null | string; container: null | string;
encodingRate: number | null; encodingRate: number | null;
frameRate: null | string; frameRate: null | string;
height: number | null; height: number | null;
width: number | null; width: number | null;
duration: number | null; duration: number | null;
bitRate: number | null; bitRate: number | null;
} }
export interface MediaChildParent { export interface MediaChildParent {
title: string; title: string;
type: string; type: string;
catalogParent: CatalogParent; catalogParent: CatalogParent;
slug: string; slug: string;
grandparentId: number; grandparentId: number;
id: number; id: number;
} }
export interface CatalogParent { export interface CatalogParent {
id: number; id: number;
title: string; title: string;
} }
export enum Source { export enum Source {
Dbb = 'dbb', Dbb = 'dbb'
} }
export interface MetaItems { export interface MetaItems {
items: Items; items: Items;
filters: Filters; filters: Filters;
} }
export interface Filters { export interface Filters {
territory: any[]; territory: any[];
language: any[]; language: any[];
} }
export interface Items { export interface Items {
'release-date': AnimationProductionStudio; 'release-date': AnimationProductionStudio;
rating: AnimationProductionStudio; rating: AnimationProductionStudio;
synopsis: AnimationProductionStudio; synopsis: AnimationProductionStudio;
'animation-production-studio': AnimationProductionStudio; 'animation-production-studio': AnimationProductionStudio;
} }
export interface AnimationProductionStudio { export interface AnimationProductionStudio {
items: AnimationProductionStudioItem[]; items: AnimationProductionStudioItem[];
label: string; label: string;
id: number; id: number;
slug: string; slug: string;
} }
export interface AnimationProductionStudioItem { export interface AnimationProductionStudioItem {
id: number; id: number;
metaType: MetaType; metaType: MetaType;
metaTypeId: string; metaTypeId: string;
client: null; client: null;
languages: TitleElement; languages: TitleElement;
territories: string; territories: string;
devices: string; devices: string;
isDefault: boolean; isDefault: boolean;
value: Value[]; value: Value[];
approved: boolean; approved: boolean;
version: null; version: null;
source: Source; source: Source;
} }
export interface EpisodeDataParent { export interface EpisodeDataParent {
seasonId: number; seasonId: number;
seasonNumber: string; seasonNumber: string;
title: string; title: string;
titleSlug: string; titleSlug: string;
titleType: string; titleType: string;
titleId: number; titleId: number;
} }
export interface PreviousSeasonEpisode { export interface PreviousSeasonEpisode {
seasonTitle?: string; seasonTitle?: string;
mediaCategory: Type; mediaCategory: Type;
thumb: string; thumb: string;
title: string; title: string;
image: string; image: string;
number: string; number: string;
id: number; id: number;
version: string[]; version: string[];
order: number; order: number;
slug: string; slug: string;
season?: number; season?: number;
languages?: TitleElement[]; languages?: TitleElement[];
} }
export enum Type { export enum Type {
Episode = 'episode', Episode = 'episode',
Ova = 'ova', Ova = 'ova'
} }
export interface Quality { export interface Quality {
quality: string; quality: string;
height: number; height: number;
} }
export interface UserRating { export interface UserRating {
overall: number; overall: number;
ja: number; ja: number;
eng: number; eng: number;
} }
export interface VersionClass { export interface VersionClass {
compliance_approved: boolean; compliance_approved: boolean;
title: string; title: string;
version_id: string; version_id: string;
is_default: boolean; is_default: boolean;
runtime: string; runtime: string;
external_id: string; external_id: string;
id: number; id: number;
} }

156
@types/github.d.ts vendored
View file

@ -1,106 +1,106 @@
export type GithubTag = { export type GithubTag = {
name: string, name: string;
zipball_url: string, zipball_url: string;
tarball_url: string, tarball_url: string;
commit: { commit: {
sha: string, sha: string;
url: string url: string;
}, };
node_id: string node_id: string;
} };
export interface TagCompare { export interface TagCompare {
url: string; url: string;
html_url: string; html_url: string;
permalink_url: string; permalink_url: string;
diff_url: string; diff_url: string;
patch_url: string; patch_url: string;
base_commit: BaseCommitClass; base_commit: BaseCommitClass;
merge_base_commit: BaseCommitClass; merge_base_commit: BaseCommitClass;
status: string; status: string;
ahead_by: number; ahead_by: number;
behind_by: number; behind_by: number;
total_commits: number; total_commits: number;
commits: BaseCommitClass[]; commits: BaseCommitClass[];
files: File[]; files: File[];
} }
export interface BaseCommitClass { export interface BaseCommitClass {
sha: string; sha: string;
node_id: string; node_id: string;
commit: BaseCommitCommit; commit: BaseCommitCommit;
url: string; url: string;
html_url: string; html_url: string;
comments_url: string; comments_url: string;
author: BaseCommitAuthor; author: BaseCommitAuthor;
committer: BaseCommitAuthor; committer: BaseCommitAuthor;
parents: Parent[]; parents: Parent[];
} }
export interface BaseCommitAuthor { export interface BaseCommitAuthor {
login: string; login: string;
id: number; id: number;
node_id: string; node_id: string;
avatar_url: string; avatar_url: string;
gravatar_id: string; gravatar_id: string;
url: string; url: string;
html_url: string; html_url: string;
followers_url: string; followers_url: string;
following_url: string; following_url: string;
gists_url: string; gists_url: string;
starred_url: string; starred_url: string;
subscriptions_url: string; subscriptions_url: string;
organizations_url: string; organizations_url: string;
repos_url: string; repos_url: string;
events_url: string; events_url: string;
received_events_url: string; received_events_url: string;
type: string; type: string;
site_admin: boolean; site_admin: boolean;
} }
export interface BaseCommitCommit { export interface BaseCommitCommit {
author: PurpleAuthor; author: PurpleAuthor;
committer: PurpleAuthor; committer: PurpleAuthor;
message: string; message: string;
tree: Tree; tree: Tree;
url: string; url: string;
comment_count: number; comment_count: number;
verification: Verification; verification: Verification;
} }
export interface PurpleAuthor { export interface PurpleAuthor {
name: string; name: string;
email: string; email: string;
date: string; date: string;
} }
export interface Tree { export interface Tree {
sha: string; sha: string;
url: string; url: string;
} }
export interface Verification { export interface Verification {
verified: boolean; verified: boolean;
reason: string; reason: string;
signature: string; signature: string;
payload: string; payload: string;
} }
export interface Parent { export interface Parent {
sha: string; sha: string;
url: string; url: string;
html_url: string; html_url: string;
} }
export interface File { export interface File {
sha: string; sha: string;
filename: string; filename: string;
status: string; status: string;
additions: number; additions: number;
deletions: number; deletions: number;
changes: number; changes: number;
blob_url: string; blob_url: string;
raw_url: string; raw_url: string;
contents_url: string; contents_url: string;
patch: string; patch: string;
} }

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,9 +1,9 @@
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;
} }
@ -13,72 +13,72 @@ export interface Data {
} }
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,63 @@
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', | {
lang: LanguageItem, type: 'Video';
path: string, lang: LanguageItem;
uncut: boolean path: string;
} | ({ uncut: boolean;
type: 'Subtitle', }
cc: boolean | ({
} & sxItem ) type: 'Subtitle';
cc: boolean;
} & sxItem);

10
@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;
} }

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

@ -1,169 +1,168 @@
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,111 +4,145 @@ 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 EpisodeListResponse = ResponseBase<Episode[]>
export type FuniEpisodeData = {
title: string,
episode: string,
epsiodeNumber: string,
episodeID: string,
seasonTitle: string,
seasonNumber: string,
ids: {
episode: string,
show: string,
season: string
},
image: string
}; };
export type AuthData = { username: string, password: string }; export type SearchResponse = ResponseBase<SearchResponseItem[]>;
export type SearchData = { search: string, page?: number, 'search-type'?: string, 'search-locale'?: string }; export type EpisodeListResponse = ResponseBase<Episode[]>;
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 FuniEpisodeData = {
export type FuniStreamData = { force?: 'Y'|'y'|'N'|'n'|'C'|'c', callbackMaker?: (data: DownloadInfo) => HLSCallback, q: number, x: number, fileName: string, numbers: number, novids?: boolean, title: string;
timeout: number, partsize: number, fsRetryTime: number, noaudio?: boolean, mp4: boolean, ass: boolean, fontSize: number, fontName?: string, skipmux?: boolean, episode: string;
forceMuxer: AvailableMuxer | undefined, simul: boolean, skipSubMux: boolean, nocleanup: boolean, override: string[], videoTitle: string, epsiodeNumber: string;
ffmpegOptions: string[], mkvmergeOptions: string[], defaultAudio: LanguageItem, defaultSub: LanguageItem, ccTag: string } episodeID: string;
export type FuniSubsData = { nosubs?: boolean, sub: boolean, dlsubs: string[], ccTag: string } seasonTitle: string;
seasonNumber: string;
ids: {
episode: string;
show: string;
season: string;
};
image: string;
};
export type AuthData = { username: string; password: string };
export type SearchData = { search: string; page?: number; 'search-type'?: string; 'search-locale'?: string };
export type FuniGetShowData = { id: number; e?: string; but: boolean; all: boolean };
export type FuniGetEpisodeData = { subs: FuniSubsData; fnSlug: FuniEpisodeData; simul?: boolean; dubLang: string[]; s: string };
export type FuniStreamData = {
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;
forceMuxer: AvailableMuxer | undefined;
simul: boolean;
skipSubMux: boolean;
nocleanup: boolean;
override: string[];
videoTitle: string;
ffmpegOptions: string[];
mkvmergeOptions: string[];
defaultAudio: LanguageItem;
defaultSub: LanguageItem;
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>;
export type FuniSearchReponse = ResponseBase<FunimationSearch>; export type FuniSearchReponse = ResponseBase<FunimationSearch>;
@ -116,46 +150,47 @@ export type FuniShowResponse = ResponseBase<FuniEpisodeData[]>;
export type FuniGetEpisodeResponse = ResponseBase<undefined>; export type FuniGetEpisodeResponse = ResponseBase<undefined>;
export type CheckTokenResponse = ResponseBase<undefined>; export type CheckTokenResponse = ResponseBase<undefined>;
export type ResponseBase<T> =
export type ResponseBase<T> = ({ | {
isOk: true, isOk: true;
value: T value: T;
} | { }
isOk: false, | {
reason: Error isOk: false;
}); 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[];
} };

152
@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

@ -3,56 +3,56 @@ export interface NewHidiveSearch {
} }
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;
} }
@ -61,7 +61,7 @@ export interface HighlightResultLocalisations {
} }
export interface PurpleEnUS { export interface PurpleEnUS {
title: Description; title: Description;
description: Description; description: Description;
} }
@ -70,7 +70,7 @@ export interface HitLocalisations {
} }
export interface HitLocalization { export interface HitLocalization {
title: string; title: string;
description: string; description: string;
} }
@ -83,6 +83,6 @@ export interface ProcessingTimingsMS {
} }
export interface Request { export interface Request {
queue: number; queue: number;
roundTrip: number; roundTrip: number;
} }

View file

@ -1,52 +1,52 @@
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 {
@ -54,24 +54,24 @@ export interface ContentDownload {
} }
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 {
@ -79,11 +79,11 @@ export interface NewHidiveSeriesExtra extends Series {
} }
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;
} }

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

@ -2,42 +2,42 @@
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 {
@ -45,150 +45,150 @@ export interface LinkData {
} }
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 {
@ -207,5 +207,5 @@ export enum Locale {
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

@ -7,96 +7,95 @@ export interface PlaybackData {
} }
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 {
@ -116,5 +115,5 @@ export enum Locale {
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;
} };

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

@ -1,45 +1,45 @@
import { GUIConfig } from '../modules/module.cfg-loader'; 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];
} };

1717
adn.ts

File diff suppressed because it is too large Load diff

1568
ao.ts

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
ffmpeg: "ffmpeg.exe" ffmpeg: 'ffmpeg.exe'
mkvmerge: "mkvmerge.exe" mkvmerge: 'mkvmerge.exe'
ffprobe: "ffprobe.exe" ffprobe: 'ffprobe.exe'
mp4decrypt: "mp4decrypt.exe" mp4decrypt: 'mp4decrypt.exe'
shaka: "shaka-packager.exe" shaka: 'shaka-packager.exe'

View file

@ -13,16 +13,16 @@ dlVideoOnce: false
# Whether to keep all downloaded videos or only a single copy # Whether to keep all downloaded videos or only a single copy
keepAllVideos: false keepAllVideos: false
# What to use as the file name template # What to use as the file name template
fileName: "[${service}] ${showTitle} - S${season}E${episode} [${height}p]" fileName: '[${service}] ${showTitle} - S${season}E${episode} [${height}p]'
# What Audio languages to download # What Audio languages to download
dubLang: ["jpn"] dubLang: ['jpn']
# What Subtitle languages to download # What Subtitle languages to download
dlsubs: ["all"] dlsubs: ['all']
# What language Audio to set as default # What language Audio to set as default
defaultAudio: "jpn" defaultAudio: 'jpn'
# Video Playback Endpoint (Crunchyroll) # Video Playback Endpoint (Crunchyroll)
vstream: "androidtv" vstream: 'androidtv'
# Audio Playback Endpoint (Crunchyroll) # Audio Playback Endpoint (Crunchyroll)
astream: "android" astream: 'android'
# Total Session Death (Could kill active streaming sessions from watching users if account shared, use with caution) (Crunchyroll) # Total Session Death (Could kill active streaming sessions from watching users if account shared, use with caution) (Crunchyroll)
tsd: false tsd: false

6130
crunchy.ts

File diff suppressed because it is too large Load diff

28
dev.js
View file

@ -3,19 +3,21 @@ const path = require('path');
const toRun = process.argv.slice(2).join(' ').split('---'); const toRun = process.argv.slice(2).join(' ').split('---');
const waitForProcess = async (proc) => { const waitForProcess = async (proc) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
proc.stdout?.on('data', (data) => process.stdout.write(data)); proc.stdout?.on('data', (data) => process.stdout.write(data));
proc.stderr?.on('data', (data) => process.stderr.write(data)); proc.stderr?.on('data', (data) => process.stderr.write(data));
proc.on('close', resolve); proc.on('close', resolve);
proc.on('error', reject); proc.on('error', reject);
}); });
}; };
(async () => { (async () => {
await waitForProcess(exec('pnpm run tsc test false')); await waitForProcess(exec('pnpm run tsc test false'));
for (let command of toRun) { for (let command of toRun) {
await waitForProcess(exec(`node index.js --service hidive ${command}`, { await waitForProcess(
cwd: path.join(__dirname, 'lib') exec(`node index.js --service hidive ${command}`, {
})); cwd: path.join(__dirname, 'lib')
} })
})(); );
}
})();

View file

@ -2,61 +2,46 @@
import eslint from '@eslint/js'; import eslint from '@eslint/js';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
export default tseslint.config( export default tseslint.config(
eslint.configs.recommended, eslint.configs.recommended,
...tseslint.configs.recommended, ...tseslint.configs.recommended,
{ {
rules: { rules: {
'no-console': 2, 'no-console': 2,
'react/prop-types': 0, 'react/prop-types': 0,
'react-hooks/exhaustive-deps': 0, 'react-hooks/exhaustive-deps': 0,
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-declaration-merging': 'warn', '@typescript-eslint/no-unsafe-declaration-merging': 'warn',
'@typescript-eslint/no-unused-vars' : 'warn', '@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn', '@typescript-eslint/no-unused-expressions': 'warn',
'indent': [ indent: ['error', 4],
'error', 'linebreak-style': ['warn', 'windows'],
4 quotes: ['error', 'single', { avoidEscape: true }],
], semi: ['error', 'always']
'linebreak-style': [ },
'warn', languageOptions: {
'windows' parserOptions: {
], ecmaFeatures: {
'quotes': [ jsx: true
'error', },
'single' ecmaVersion: 2020,
], sourceType: 'module'
'semi': [ },
'error', parser: tseslint.parser
'always' }
] },
}, {
languageOptions: { ignores: ['**/lib', '**/videos', '**/build', 'dev.js', 'tsc.ts']
parserOptions: { },
ecmaFeatures: { {
jsx: true, files: ['gui/react/**/*'],
}, rules: {
ecmaVersion: 2020, 'no-console': 0,
sourceType: 'module' indent: 'off'
}, }
parser: tseslint.parser },
} // Disables all rules that conflict with prettier
}, prettier
{ );
ignores: [
'**/lib',
'**/videos/*.ts',
'**/build',
'dev.js',
'tsc.ts'
]
},
{
files: ['gui/react/**/*'],
rules: {
'no-console': 0,
'indent': 'off'
}
}
);

2
gui.ts
View file

@ -1,3 +1,3 @@
process.env.isGUI = 'true'; process.env.isGUI = 'true';
import './modules/log'; import './modules/log';
import './gui/server/index'; import './gui/server/index';

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

@ -54,4 +54,4 @@
"last 1 safari version" "last 1 safari version"
] ]
} }
} }

View file

@ -1,13 +1,10 @@
<!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" content="script-src 'self' 'unsafe-eval'" />
http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-eval'"
/>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View file

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

View file

@ -2,9 +2,7 @@ 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

@ -10,29 +10,36 @@ import StartQueueButton from './components/StartQueue';
import MenuBar from './components/MenuBar/MenuBar'; 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
<Box sx={{ 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 /> >
<AuthButton /> <LogoutButton />
<Button variant="contained" startIcon={<Folder />} onClick={() => messageHandler?.openFolder('content')} sx={{ height: '37px' }}>Open Output Directory</Button> <AuthButton />
<Button variant="contained" startIcon={<ClearAll />} onClick={() => messageHandler?.clearQueue() } sx={{ height: '37px' }}>Clear Queue</Button> <Button variant="contained" startIcon={<Folder />} onClick={() => messageHandler?.openFolder('content')} sx={{ height: '37px' }}>
<AddToQueue /> Open Output Directory
<StartQueueButton /> </Button>
</Box> <Button variant="contained" startIcon={<ClearAll />} onClick={() => messageHandler?.clearQueue()} sx={{ height: '37px' }}>
<MainFrame /> Clear Queue
</Box>; </Button>
<AddToQueue />
<StartQueueButton />
</Box>
<MainFrame />
</Box>
);
}; };
export default Layout; export default Layout;

View file

@ -1,19 +1,21 @@
import React from 'react'; 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 (
<Box sx={{ }}/> <ThemeProvider theme={makeTheme('dark')}>
{children} <Box sx={{}} />
</ThemeProvider>; {children}
</ThemeProvider>
);
}; };
export default Style; export default Style;

View file

@ -6,22 +6,24 @@ 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 (
<EpisodeListing /> <Box>
<Dialog open={isOpen} onClose={() => setOpen(false)} maxWidth='md' PaperProps={{ elevation:4 }}> <EpisodeListing />
<Box> <Dialog open={isOpen} onClose={() => setOpen(false)} maxWidth="md" PaperProps={{ elevation: 4 }}>
<SearchBox /> <Box>
<Divider variant='middle'/> <SearchBox />
<DownloadSelector onFinish={() => setOpen(false)} /> <Divider variant="middle" />
</Box> <DownloadSelector onFinish={() => setOpen(false)} />
</Dialog> </Box>
<Button variant='contained' onClick={() => setOpen(true)} sx={{ maxHeight: '2.3rem' }}> </Dialog>
<Add /> <Button variant="contained" onClick={() => setOpen(true)} sx={{ maxHeight: '2.3rem' }}>
Add to Queue <Add />
</Button> Add to Queue
</Box>; </Button>
</Box>
);
}; };
export default AddToQueue; export default AddToQueue;

View file

@ -8,320 +8,398 @@ import { useSnackbar } from 'notistack';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
type DownloadSelectorProps = { type DownloadSelectorProps = {
onFinish?: () => unknown onFinish?: () => unknown;
}
const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
const messageHandler = React.useContext(messageChannelContext);
const [store, dispatch] = useStore();
const [availableDubs, setAvailableDubs] = React.useState<string[]>([]);
const [availableSubs, setAvailableSubs ] = React.useState<string[]>([]);
const [ loading, setLoading ] = React.useState(false);
const { enqueueSnackbar } = useSnackbar();
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
React.useEffect(() => {
(async () => {
/* If we don't wait the response is undefined? */
await new Promise((resolve) => setTimeout(() => resolve(undefined), 100));
const dubLang = messageHandler?.handleDefault('dubLang');
const subLang = messageHandler?.handleDefault('dlsubs');
const q = messageHandler?.handleDefault('q');
const fileName = messageHandler?.handleDefault('fileName');
const dlVideoOnce = messageHandler?.handleDefault('dlVideoOnce');
const result = await Promise.all([dubLang, subLang, q, fileName, dlVideoOnce]);
dispatch({
type: 'downloadOptions',
payload: {
...store.downloadOptions,
dubLang: result[0],
dlsubs: result[1],
q: result[2],
fileName: result[3],
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);
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'/>
<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({
type: 'downloadOptions',
payload: { ...store.downloadOptions, q: parsed }
});
}} label='Quality Level (0 for max)'/>
<Box sx={{ display: 'flex', gap: '5px' }}>
<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>
<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={
<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!
</Typography>
} arrow placement='top'>
<InfoOutlinedIcon sx={{
transition: '100ms',
ml: '0.35rem',
mr: '0.65rem',
'&:hover' : {
color: '#ffffff30',
}
}} />
</Tooltip>
</Box>
</Tooltip>
</Box>
</Box>
<Box sx={{width: '95%', height: '0.3rem', backgroundColor: '#ffffff50', borderRadius: '10px', marginBottom: '20px'}}/>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
gap: '15px'
}}>
<TextField value={store.downloadOptions.fileName} onChange={e => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, fileName: e.target.value }
});
}} sx={{ width: '87%' }} label='Filename Overwrite' />
<Tooltip title={
<Typography>
Click here to see the documentation
</Typography>
} arrow placement='top'>
<Link href='https://github.com/anidl/multi-downloader-nx/blob/master/docs/DOCUMENTATION.md#filename-template' rel="noopener noreferrer" target="_blank">
<InfoOutlinedIcon sx={{
transition: '100ms',
'&:hover' : {
color: '#ffffff30',
}
}} />
</Link>
</Tooltip>
</Box>
</Box>
<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>
</Box>;
}; };
export default DownloadSelector; const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
const messageHandler = React.useContext(messageChannelContext);
const [store, dispatch] = useStore();
const [availableDubs, setAvailableDubs] = React.useState<string[]>([]);
const [availableSubs, setAvailableSubs] = React.useState<string[]>([]);
const [loading, setLoading] = React.useState(false);
const { enqueueSnackbar } = useSnackbar();
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
React.useEffect(() => {
(async () => {
/* If we don't wait the response is undefined? */
await new Promise((resolve) => setTimeout(() => resolve(undefined), 100));
const dubLang = messageHandler?.handleDefault('dubLang');
const subLang = messageHandler?.handleDefault('dlsubs');
const q = messageHandler?.handleDefault('q');
const fileName = messageHandler?.handleDefault('fileName');
const dlVideoOnce = messageHandler?.handleDefault('dlVideoOnce');
const result = await Promise.all([dubLang, subLang, q, fileName, dlVideoOnce]);
dispatch({
type: 'downloadOptions',
payload: {
...store.downloadOptions,
dubLang: result[0],
dlsubs: result[1],
q: result[2],
fileName: result[3],
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);
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"
/>
<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({
type: 'downloadOptions',
payload: { ...store.downloadOptions, q: parsed }
});
}}
label="Quality Level (0 for max)"
/>
<Box sx={{ display: 'flex', gap: '5px' }}>
<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>
<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={
<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!
</Typography>
}
arrow
placement="top"
>
<InfoOutlinedIcon
sx={{
transition: '100ms',
ml: '0.35rem',
mr: '0.65rem',
'&:hover': {
color: '#ffffff30'
}
}}
/>
</Tooltip>
</Box>
</Tooltip>
</Box>
</Box>
<Box sx={{ width: '95%', height: '0.3rem', backgroundColor: '#ffffff50', borderRadius: '10px', marginBottom: '20px' }} />
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
gap: '15px'
}}
>
<TextField
value={store.downloadOptions.fileName}
onChange={(e) => {
dispatch({
type: 'downloadOptions',
payload: { ...store.downloadOptions, fileName: e.target.value }
});
}}
sx={{ width: '87%' }}
label="Filename Overwrite"
/>
<Tooltip title={<Typography>Click here to see the documentation</Typography>} arrow placement="top">
<Link href="https://github.com/anidl/multi-downloader-nx/blob/master/docs/DOCUMENTATION.md#filename-template" rel="noopener noreferrer" target="_blank">
<InfoOutlinedIcon
sx={{
transition: '100ms',
'&:hover': {
color: '#ffffff30'
}
}}
/>
</Link>
</Tooltip>
</Box>
</Box>
<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>
</Box>
);
};
export default DownloadSelector;

View file

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

View file

@ -1,8 +1,8 @@
.listitem-hover:hover { .listitem-hover:hover {
-webkit-filter: brightness(70%); -webkit-filter: brightness(70%);
filter: brightness(70%); filter: brightness(70%);
} }
.listitem-hover { .listitem-hover {
transition: filter 0.1s ease-in; transition: filter 0.1s ease-in;
} }

View file

@ -8,112 +8,144 @@ 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 (
<Box sx={{ m: 2 }}> <ClickAwayListener onClickAway={() => setFocus(false)}>
<TextField ref={anchor} value={search} onClick={() => setFocus(true)} onChange={e => setSearch(e.target.value)} variant='outlined' label='Search' fullWidth /> <Box sx={{ m: 2 }}>
{searchResult !== undefined && searchResult.isOk && searchResult.value.length > 0 && focus && <TextField ref={anchor} value={search} onClick={() => setFocus(true)} onChange={(e) => setSearch(e.target.value)} variant="outlined" label="Search" fullWidth />
<Paper sx={{ position: 'fixed', maxHeight: '50%', width: `${anchorBounding?.width}px`, {searchResult !== undefined && searchResult.isOk && searchResult.value.length > 0 && focus && (
left: anchorBounding?.x, top: (anchorBounding?.y ?? 0) + (anchorBounding?.height ?? 0), zIndex: 99, overflowY: 'scroll'}}> <Paper
<List> sx={{
{searchResult && searchResult.isOk ? position: 'fixed',
searchResult.value.map((a, ind, arr) => { maxHeight: '50%',
const imageRef = React.createRef<HTMLImageElement>(); width: `${anchorBounding?.width}px`,
const summaryRef = React.createRef<HTMLParagraphElement>(); left: anchorBounding?.x,
return <Box key={a.id}> top: (anchorBounding?.y ?? 0) + (anchorBounding?.height ?? 0),
<ListItem className='listitem-hover' onClick={() => { zIndex: 99,
selectItem(a.id); overflowY: 'scroll'
setFocus(false); }}
}}> >
<Box sx={{ display: 'flex' }}> <List>
<Box sx={{ width: '20%', height: '100%', pr: 2 }}> {searchResult && searchResult.isOk ? (
<img ref={imageRef} src={a.image} style={{ width: '100%', height: 'auto' }} alt="thumbnail"/> searchResult.value.map((a, ind, arr) => {
</Box> const imageRef = React.createRef<HTMLImageElement>();
<Box sx={{ display: 'flex', flexDirection: 'column', maxWidth: '70%' }}> const summaryRef = React.createRef<HTMLParagraphElement>();
<Typography variant='h6' component='h6' color='text.primary' sx={{ }}> return (
{a.name} <Box key={a.id}>
</Typography> <ListItem
{a.desc && <Typography variant='caption' component='p' color='text.primary' sx={{ pt: 1, pb: 1 }} ref={summaryRef}> className="listitem-hover"
{a.desc} onClick={() => {
</Typography>} selectItem(a.id);
{a.lang && <Typography variant='caption' component='p' color='text.primary' sx={{ }}> setFocus(false);
Languages: {a.lang.join(', ')} }}
</Typography>} >
<Typography variant='caption' component='p' color='text.primary' sx={{ }}> <Box sx={{ display: 'flex' }}>
ID: {a.id} <Box sx={{ width: '20%', height: '100%', pr: 2 }}>
</Typography> <img ref={imageRef} src={a.image} style={{ width: '100%', height: 'auto' }} alt="thumbnail" />
</Box> </Box>
</Box> <Box sx={{ display: 'flex', flexDirection: 'column', maxWidth: '70%' }}>
</ListItem> <Typography variant="h6" component="h6" color="text.primary" sx={{}}>
<ContextMenu options={[ { text: 'Copy image URL', onClick: async () => { {a.name}
await navigator.clipboard.writeText(a.image); </Typography>
enqueueSnackbar('Copied URL to clipboard', { {a.desc && (
variant: 'info' <Typography variant="caption" component="p" color="text.primary" sx={{ pt: 1, pb: 1 }} ref={summaryRef}>
}); {a.desc}
}}, </Typography>
{ )}
text: 'Open image in new tab', {a.lang && (
onClick: () => { <Typography variant="caption" component="p" color="text.primary" sx={{}}>
window.open(a.image); Languages: {a.lang.join(', ')}
} </Typography>
} ]} popupItem={imageRef as RefObject<HTMLElement>} /> )}
{a.desc && <Typography variant="caption" component="p" color="text.primary" sx={{}}>
<ContextMenu options={[ ID: {a.id}
{ </Typography>
onClick: async () => { </Box>
await navigator.clipboard.writeText(a.desc!); </Box>
enqueueSnackbar('Copied summary to clipboard', { </ListItem>
variant: 'info' <ContextMenu
}); options={[
}, {
text: 'Copy summary to clipboard' text: 'Copy image URL',
} onClick: async () => {
]} popupItem={summaryRef as RefObject<HTMLElement>} /> await navigator.clipboard.writeText(a.image);
} enqueueSnackbar('Copied URL to clipboard', {
{(ind < arr.length - 1) && <Divider />} variant: 'info'
</Box>; });
}) }
: <></>} },
</List> {
</Paper>} text: 'Open image in new tab',
</Box> onClick: () => {
</ClickAwayListener>; window.open(a.image);
}
}
]}
popupItem={imageRef as RefObject<HTMLElement>}
/>
{a.desc && (
<ContextMenu
options={[
{
onClick: async () => {
await navigator.clipboard.writeText(a.desc!);
enqueueSnackbar('Copied summary to clipboard', {
variant: 'info'
});
},
text: 'Copy summary to clipboard'
}
]}
popupItem={summaryRef as RefObject<HTMLElement>}
/>
)}
{ind < arr.length - 1 && <Divider />}
</Box>
);
})
) : (
<></>
)}
</List>
</Paper>
)}
</Box>
</ClickAwayListener>
);
}; };
export default SearchBox; export default SearchBox;

View file

@ -6,107 +6,115 @@ 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) return setUsernameError(true);
if (username.trim().length === 0) if (password.trim().length === 0) return setPasswordError(true);
return setUsernameError(true); setUsernameError(false);
if (password.trim().length === 0) setPasswordError(false);
return setPasswordError(true); setLoading(true);
setUsernameError(false);
setPasswordError(false);
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 (
<Dialog open={open}> <Require value={messageChannel}>
<Dialog open={!!error}> <Dialog open={open}>
<DialogTitle>Error during Authentication</DialogTitle> <Dialog open={!!error}>
<DialogContentText> <DialogTitle>Error during Authentication</DialogTitle>
{error?.name} <DialogContentText>
{error?.message} {error?.name}
</DialogContentText> {error?.message}
<DialogActions> </DialogContentText>
<Button onClick={() => setError(undefined)}>Close</Button> <DialogActions>
</DialogActions> <Button onClick={() => setError(undefined)}>Close</Button>
</Dialog> </DialogActions>
<DialogTitle sx={{ flexGrow: 1 }}>Authentication</DialogTitle> </Dialog>
<DialogContent> <DialogTitle sx={{ flexGrow: 1 }}>Authentication</DialogTitle>
<DialogContentText> <DialogContent>
Here, you need to enter your username (most likely your Email) and your password.<br /> <DialogContentText>
These information are not stored anywhere and are only used to authenticate with the service once. Here, you need to enter your username (most likely your Email) and your password.
</DialogContentText> <br />
<TextField These information are not stored anywhere and are only used to authenticate with the service once.
error={usernameError} </DialogContentText>
helperText={usernameError ? 'Please enter something before submiting' : undefined} <TextField
margin="dense" error={usernameError}
id="username" helperText={usernameError ? 'Please enter something before submiting' : undefined}
label="Username" margin="dense"
type="text" id="username"
fullWidth label="Username"
variant="standard" type="text"
value={username} fullWidth
onChange={(e) => setUsername(e.target.value)} variant="standard"
disabled={loading} value={username}
/> onChange={(e) => setUsername(e.target.value)}
<TextField disabled={loading}
error={passwordError} />
helperText={passwordError ? 'Please enter something before submiting' : undefined} <TextField
margin="dense" error={passwordError}
id="password" helperText={passwordError ? 'Please enter something before submiting' : undefined}
label="Password" margin="dense"
type="password" id="password"
fullWidth label="Password"
variant="standard" type="password"
value={password} fullWidth
onChange={(e) => setPassword(e.target.value)} variant="standard"
disabled={loading} value={password}
/> onChange={(e) => setPassword(e.target.value)}
</DialogContent> disabled={loading}
<DialogActions> />
{loading && <CircularProgress size={30}/>} </DialogContent>
<Button disabled={loading} onClick={() => setOpen(false)}>Close</Button> <DialogActions>
<Button disabled={loading} onClick={() => handleSubmit()}>Authenticate</Button> {loading && <CircularProgress size={30} />}
</DialogActions> <Button disabled={loading} onClick={() => setOpen(false)}>
</Dialog> Close
<Button startIcon={authed ? <Check />: <Close />} variant="contained" onClick={() => setOpen(true)}>Authenticate</Button> </Button>
</Require>; <Button disabled={loading} onClick={() => handleSubmit()}>
Authenticate
</Button>
</DialogActions>
</Dialog>
<Button startIcon={authed ? <Check /> : <Close />} variant="contained" onClick={() => setOpen(true)}>
Authenticate
</Button>
</Require>
);
}; };
export default AuthButton; export default AuthButton;

View file

@ -6,32 +6,26 @@ 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 alert('Unable to change service');
else };
alert('Unable to change service');
};
return <Require value={messageChannel}>
<Button
startIcon={<ExitToApp />}
variant='contained'
onClick={logout}
sx={{ maxHeight: '2.3rem' }}
>
Service select
</Button>
</Require>;
return (
<Require value={messageChannel}>
<Button startIcon={<ExitToApp />} variant="contained" onClick={logout} sx={{ maxHeight: '2.3rem' }}>
Service select
</Button>
</Require>
);
}; };
export default LogoutButton; export default LogoutButton;

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(() => {
const handler = (ev: RandomEvent<'progress'>) => {
console.log(ev.data);
setProgressData(ev.data);
};
const currentHandler = (ev: RandomEvent<'current'>) => { React.useEffect(() => {
setCurrent(ev.data); const handler = (ev: RandomEvent<'progress'>) => {
}; console.log(ev.data);
setProgressData(ev.data);
const finishHandler = () => { };
setProgressData(undefined);
};
messageHandler?.randomEvents.on('progress', handler); const currentHandler = (ev: RandomEvent<'current'>) => {
messageHandler?.randomEvents.on('current', currentHandler); setCurrent(ev.data);
messageHandler?.randomEvents.on('finish', finishHandler); };
return () => { const finishHandler = () => {
messageHandler?.randomEvents.removeListener('progress', handler); setProgressData(undefined);
messageHandler?.randomEvents.removeListener('finish', finishHandler); };
messageHandler?.randomEvents.removeListener('current', currentHandler);
}; messageHandler?.randomEvents.on('progress', handler);
}, [messageHandler]); messageHandler?.randomEvents.on('current', currentHandler);
messageHandler?.randomEvents.on('finish', finishHandler);
return { data: progressData, current};
return () => {
messageHandler?.randomEvents.removeListener('progress', handler);
messageHandler?.randomEvents.removeListener('finish', finishHandler);
messageHandler?.randomEvents.removeListener('current', currentHandler);
};
}, [messageHandler]);
return { data: progressData, current };
}; };
export default useDownloadManager; export default useDownloadManager;

View file

@ -3,9 +3,11 @@ 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 (
<Queue /> <Box sx={{}}>
</Box>; <Queue />
</Box>
);
}; };
export default MainFrame; export default MainFrame;

View file

@ -7,414 +7,537 @@ 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) return <>Never</>;
if (!msg) return data || queue.length > 0 ? (
return <>Never</>; <>
{data && (
return data || queue.length > 0 ? <> <>
{data && <> <Box
<Box sx={{ sx={{
display: 'flex', display: 'flex',
width: '100%', width: '100%',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center'
}}> }}
<Box sx={{ >
marginTop: '2rem', <Box
marginBottom: '1rem', sx={{
height: '12rem', marginTop: '2rem',
width: '93vw', marginBottom: '1rem',
maxWidth: '93rem', height: '12rem',
backgroundColor: '#282828', width: '93vw',
boxShadow: '0px 0px 50px #00000090', maxWidth: '93rem',
borderRadius: '10px', backgroundColor: '#282828',
display: 'flex', boxShadow: '0px 0px 50px #00000090',
transition: '250ms' borderRadius: '10px',
}}> display: 'flex',
<img style={{ transition: '250ms'
borderRadius: '5px', }}
margin: '5px', >
boxShadow: '0px 0px 10px #00000090', <img
userSelect: 'none', style={{
}} borderRadius: '5px',
src={data.downloadInfo.image} height='auto' width='auto' alt="Thumbnail" /> margin: '5px',
<Box boxShadow: '0px 0px 10px #00000090',
sx={{ userSelect: 'none'
display: 'flex', }}
flexDirection: 'column', src={data.downloadInfo.image}
width: '100%', height="auto"
justifyContent: 'center' width="auto"
}}> alt="Thumbnail"
<Box sx={{ />
display: 'flex', <Box
}}> sx={{
<Box sx={{ display: 'flex',
//backgroundColor: '#ff0000', flexDirection: 'column',
width: '70%', width: '100%',
marginLeft: '10px' justifyContent: 'center'
}}> }}
<Box sx={{ >
flexDirection: 'column', <Box
display: 'flex', sx={{
justifyContent: 'space-between', display: 'flex'
}}> }}
<Typography color='text.primary' sx={{ >
fontSize: '1.8rem', <Box
}}> sx={{
{data.downloadInfo.parent.title} //backgroundColor: '#ff0000',
</Typography> width: '70%',
<Typography color='text.primary' sx={{ marginLeft: '10px'
fontSize: '1.2rem', }}
}}> >
{data.downloadInfo.title} <Box
</Typography> sx={{
</Box> flexDirection: 'column',
</Box> display: 'flex',
<Box sx={{ justifyContent: 'space-between'
//backgroundColor: '#00ff00', }}
width: '30%', >
display: 'flex', <Typography
flexDirection: 'column', color="text.primary"
justifyContent: 'center', sx={{
alignItems: 'center', fontSize: '1.8rem'
}}> }}
<Typography color='text.primary' sx={{ >
fontSize: '1.8rem', {data.downloadInfo.parent.title}
}}> </Typography>
Downloading: {data.downloadInfo.language.name} <Typography
</Typography> color="text.primary"
</Box> sx={{
</Box> fontSize: '1.2rem'
<Box sx={{ }}
height: '50%', >
display: 'flex', {data.downloadInfo.title}
flexDirection: 'column', </Typography>
justifyContent: 'center', </Box>
alignItems: 'center', </Box>
//backgroundColor: '#0000ff', <Box
}}> sx={{
<LinearProgress variant='determinate' //backgroundColor: '#00ff00',
sx={{ width: '30%',
height: '20px', display: 'flex',
width: '97.53%', flexDirection: 'column',
margin: '10px', justifyContent: 'center',
boxShadow: '0px 0px 10px #00000090', alignItems: 'center'
borderRadius: '10px', }}
}} value={(typeof data.progress.percent === 'string' ? parseInt(data.progress.percent) : data.progress.percent)} >
/> <Typography
<Box> color="text.primary"
<Typography color='text.primary' sx={{
sx={{ fontSize: '1.8rem'
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) Downloading: {data.downloadInfo.language.name}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
</Box> <Box
</Box> sx={{
</Box> height: '50%',
</> display: 'flex',
} flexDirection: 'column',
{ justifyContent: 'center',
current && !data && <> alignItems: 'center'
<Box sx={{ //backgroundColor: '#0000ff',
display: 'flex', }}
flexDirection: 'column', >
alignItems: 'center', <LinearProgress
}}> variant="determinate"
<Box sx={{ sx={{
marginTop: '2rem', height: '20px',
marginBottom: '1rem', width: '97.53%',
height: '12rem', margin: '10px',
width: '93vw', boxShadow: '0px 0px 10px #00000090',
maxWidth: '93rem', borderRadius: '10px'
backgroundColor: '#282828', }}
boxShadow: '0px 0px 50px #00000090', value={typeof data.progress.percent === 'string' ? parseInt(data.progress.percent) : data.progress.percent}
borderRadius: '10px', />
display: 'flex', <Box>
overflow: 'hidden', <Typography
transition: '250ms' color="text.primary"
}}> sx={{
<img style={{ fontSize: '1.3rem'
borderRadius: '5px', }}
margin: '5px', >
boxShadow: '0px 0px 10px #00000090', {data.progress.cur} / {data.progress.total} parts ({data.progress.percent}% | {formatTime(data.progress.time)} |{' '}
userSelect: 'none', {(data.progress.downloadSpeed / 1024 / 1024).toFixed(2)} MB/s | {(data.progress.bytes / 1024 / 1024).toFixed(2)}MB)
maxWidth: '20.5rem', </Typography>
}} </Box>
src={current.image} height='auto' width='auto' alt="Thumbnail" /> </Box>
<Box </Box>
sx={{ </Box>
display: 'flex', </Box>
flexDirection: 'column', </>
width: '100%', )}
justifyContent: 'center', {current && !data && (
//backgroundColor: '#ffffff0f' <>
}}> <Box
<Box sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column',
}}> alignItems: 'center'
<Box sx={{ }}
width: '70%', >
marginLeft: '10px' <Box
}}> sx={{
<Box sx={{ marginTop: '2rem',
flexDirection: 'column', marginBottom: '1rem',
display: 'flex', height: '12rem',
justifyContent: 'space-between', width: '93vw',
}}> maxWidth: '93rem',
<Typography color='text.primary' sx={{ backgroundColor: '#282828',
fontSize: '1.8rem', boxShadow: '0px 0px 50px #00000090',
}}> borderRadius: '10px',
{current.parent.title} display: 'flex',
</Typography> overflow: 'hidden',
<Typography color='text.primary' sx={{ transition: '250ms'
fontSize: '1.2rem', }}
}}> >
{current.title} <img
</Typography> style={{
</Box> borderRadius: '5px',
</Box> margin: '5px',
<Box sx={{ boxShadow: '0px 0px 10px #00000090',
//backgroundColor: '#00ff00', userSelect: 'none',
width: '30%', maxWidth: '20.5rem'
display: 'flex', }}
flexDirection: 'column', src={current.image}
justifyContent: 'center', height="auto"
alignItems: 'center', width="auto"
}}> alt="Thumbnail"
<Box sx={{ />
display: 'flex', <Box
alignItems: 'center', sx={{
justifyContent: 'space-between', display: 'flex',
position: 'relative', flexDirection: 'column',
}}> width: '100%',
<Typography color='text.primary' sx={{ justifyContent: 'center'
fontSize: '1.8rem', //backgroundColor: '#ffffff0f'
}}> }}
Downloading: >
</Typography> <Box
<CircularProgress variant="indeterminate" sx={{ sx={{
marginLeft: '2rem', display: 'flex'
}}/> }}
</Box> >
</Box> <Box
</Box> sx={{
<Box sx={{ width: '70%',
height: '50%', marginLeft: '10px'
display: 'flex', }}
flexDirection: 'column', >
justifyContent: 'center', <Box
alignItems: 'center', sx={{
//backgroundColor: '#0000ff', flexDirection: 'column',
}}> display: 'flex',
<LinearProgress variant='indeterminate' justifyContent: 'space-between'
sx={{ }}
height: '20px', >
width: '97.53%', <Typography
margin: '10px', color="text.primary"
boxShadow: '0px 0px 10px #00000090', sx={{
borderRadius: '10px', fontSize: '1.8rem'
}} }}
/> >
<Box> {current.parent.title}
<Typography color='text.primary' </Typography>
sx={{ <Typography
fontSize: '1.3rem', color="text.primary"
}}> sx={{
0 / ? parts (0% | XX:XX | 0 MB/s | 0MB) fontSize: '1.2rem'
</Typography> }}
</Box> >
</Box> {current.title}
</Box> </Typography>
</Box> </Box>
</Box> </Box>
</> <Box
} sx={{
{queue.map((queueItem, index, { length }) => { //backgroundColor: '#00ff00',
return <Box key={`queue_item_${index}`} sx={{ width: '30%',
display: 'flex', display: 'flex',
mb: '-1.5rem', flexDirection: 'column',
flexDirection: 'column', justifyContent: 'center',
alignItems: 'center', alignItems: 'center'
}}> }}
<Box sx={{ >
marginTop: '1.5rem', <Box
marginBottom: '1.5rem', sx={{
height: '11rem', display: 'flex',
width: '90vw', alignItems: 'center',
maxWidth: '90rem', justifyContent: 'space-between',
backgroundColor: '#282828', position: 'relative'
boxShadow: '0px 0px 10px #00000090', }}
borderRadius: '10px', >
display: 'flex', <Typography
overflow: 'hidden', color="text.primary"
}}> sx={{
<img style={{ fontSize: '1.8rem'
borderRadius: '5px', }}
margin: '5px', >
boxShadow: '0px 0px 5px #00000090', Downloading:
userSelect: 'none', </Typography>
maxWidth: '18.5rem' <CircularProgress
}} variant="indeterminate"
src={queueItem.image} height='auto' width='auto' alt="Thumbnail" /> sx={{
<Box sx={{ marginLeft: '2rem'
margin: '5px', }}
display: 'flex', />
width: '100%', </Box>
justifyContent: 'space-between', </Box>
}}> </Box>
<Box sx={{ <Box
width: '30%', sx={{
marginRight: '5px', height: '50%',
marginLeft: '5px', display: 'flex',
display: 'flex', flexDirection: 'column',
flexDirection: 'column', justifyContent: 'center',
justifyContent: 'space-between', alignItems: 'center'
overflow: 'hidden', //backgroundColor: '#0000ff',
textOverflow: 'ellipsis', }}
}}> >
<Typography color='text.primary' sx={{ <LinearProgress
fontSize: '1.8rem', variant="indeterminate"
overflow: 'hidden', sx={{
whiteSpace: 'nowrap', height: '20px',
textOverflow: 'ellipsis', width: '97.53%',
}}> margin: '10px',
{queueItem.parent.title} boxShadow: '0px 0px 10px #00000090',
</Typography> borderRadius: '10px'
<Typography color='text.primary' sx={{ }}
fontSize: '1.6rem', />
marginTop: '-0.4rem', <Box>
marginBottom: '0.4rem', <Typography
}}> color="text.primary"
S{queueItem.parent.season}E{queueItem.episode} sx={{
</Typography> fontSize: '1.3rem'
<Typography color='text.primary' sx={{ }}
fontSize: '1.2rem', >
marginTop: '-0.4rem', 0 / ? parts (0% | XX:XX | 0 MB/s | 0MB)
marginBottom: '0.4rem', </Typography>
textOverflow: 'ellipsis', </Box>
}}> </Box>
{queueItem.title} </Box>
</Typography> </Box>
</Box> </Box>
<Box sx={{ </>
width: '40%', )}
marginRight: '5px', {queue.map((queueItem, index, { length }) => {
marginLeft: '5px', return (
display: 'flex', <Box
flexDirection: 'column', key={`queue_item_${index}`}
overflow: 'hidden', sx={{
whiteSpace: 'nowrap', display: 'flex',
justifyContent: 'space-between', mb: '-1.5rem',
}}> flexDirection: 'column',
<Typography color='text.primary' sx={{ alignItems: 'center'
fontSize: '1.8rem', }}
overflow: 'hidden', >
whiteSpace: 'nowrap', <Box
textOverflow: 'ellipsis', sx={{
}}> marginTop: '1.5rem',
Dub(s): {queueItem.dubLang.join(', ')} marginBottom: '1.5rem',
</Typography> height: '11rem',
<Typography color='text.primary' sx={{ width: '90vw',
fontSize: '1.8rem', maxWidth: '90rem',
overflow: 'hidden', backgroundColor: '#282828',
whiteSpace: 'nowrap', boxShadow: '0px 0px 10px #00000090',
textOverflow: 'ellipsis', borderRadius: '10px',
}}> display: 'flex',
Sub(s): {queueItem.dlsubs.join(', ')} overflow: 'hidden'
</Typography> }}
<Typography color='text.primary' sx={{ >
fontSize: '1.8rem', <img
style={{
}}> borderRadius: '5px',
Quality: {queueItem.q} margin: '5px',
</Typography> boxShadow: '0px 0px 5px #00000090',
</Box> userSelect: 'none',
<Box sx={{ maxWidth: '18.5rem'
marginRight: '5px', }}
marginLeft: '5px', src={queueItem.image}
width: '30%', height="auto"
justifyContent: 'center', width="auto"
alignItems: 'center', alt="Thumbnail"
display: 'flex' />
}}> <Box
<Tooltip title="Delete from queue" arrow placement='top'> sx={{
<IconButton margin: '5px',
onClick={() => { display: 'flex',
msg.removeFromQueue(index); width: '100%',
}} justifyContent: 'space-between'
sx={{ }}
backgroundColor: '#ff573a25', >
height: '40px', <Box
transition: '250ms', sx={{
'&:hover' : { width: '30%',
backgroundColor: '#ff573a', marginRight: '5px',
} marginLeft: '5px',
}}> display: 'flex',
<DeleteIcon /> flexDirection: 'column',
</IconButton> justifyContent: 'space-between',
</Tooltip> overflow: 'hidden',
</Box> textOverflow: 'ellipsis'
</Box> }}
</Box> >
</Box> <Typography
; color="text.primary"
})} sx={{
</> : <Box sx={{ fontSize: '1.8rem',
display: 'flex', overflow: 'hidden',
width: '100%', whiteSpace: 'nowrap',
height: '12rem', textOverflow: 'ellipsis'
flexDirection: 'column', }}
alignItems: 'center', >
}}> {queueItem.parent.title}
<Typography color='text.primary' sx={{ </Typography>
fontSize: '2rem', <Typography
margin: '10px' color="text.primary"
}}> sx={{
Selected episodes will be shown here fontSize: '1.6rem',
</Typography> marginTop: '-0.4rem',
<Box sx={{ marginBottom: '0.4rem'
display: 'flex', }}
margin: '10px' >
}}> S{queueItem.parent.season}E{queueItem.episode}
<Skeleton variant='rectangular' height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> </Typography>
<Box sx={{ <Typography
display: 'flex', color="text.primary"
flexDirection: 'column', sx={{
}}> fontSize: '1.2rem',
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> marginTop: '-0.4rem',
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> marginBottom: '0.4rem',
</Box> textOverflow: 'ellipsis'
</Box> }}
<Box sx={{ >
display: 'flex', {queueItem.title}
margin: '10px' </Typography>
}}> </Box>
<Skeleton variant='rectangular' height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> <Box
<Box sx={{ sx={{
display: 'flex', width: '40%',
flexDirection: 'column', marginRight: '5px',
}}> marginLeft: '5px',
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> display: 'flex',
<Skeleton variant='text' height={'100%'} width={'30rem'} sx={{ margin: '5px', borderRadius: '5px' }}/> flexDirection: 'column',
</Box> overflow: 'hidden',
</Box> whiteSpace: 'nowrap',
</Box>; 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
</Typography>
<Box
sx={{
display: 'flex',
margin: '10px'
}}
>
<Skeleton variant="rectangular" height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }} />
<Box
sx={{
display: 'flex',
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' }} />
</Box>
</Box>
<Box
sx={{
display: 'flex',
margin: '10px'
}}
>
<Skeleton variant="rectangular" height={'10rem'} width={'20rem'} sx={{ margin: '5px', borderRadius: '5px' }} />
<Box
sx={{
display: 'flex',
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' }} />
</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,134 @@ 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={{ position: 'relative', left: '0%', width: '50%'}}> <Box sx={{ display: 'flex', marginBottom: '1rem', width: '100%', alignItems: 'center' }}>
<Button onClick={(e) => handleClick(e, 'settings')}> <Box sx={{ position: 'relative', left: '0%', width: '50%' }}>
Settings <Button onClick={(e) => handleClick(e, 'settings')}>Settings</Button>
</Button> <Button onClick={(e) => handleClick(e, 'help')}>Help</Button>
<Button onClick={(e) => handleClick(e, 'help')}> </Box>
Help <Menu open={openMenu === 'settings'} anchorEl={anchorEl} onClose={handleClose}>
</Button> <MenuItem
</Box> onClick={() => {
<Menu open={openMenu === 'settings'} anchorEl={anchorEl} onClose={handleClose}> msg.openFolder('config');
<MenuItem onClick={() => { handleClose();
msg.openFolder('config'); }}
handleClose(); >
}}> Open settings folder
Open settings folder </MenuItem>
</MenuItem> <MenuItem
<MenuItem onClick={() => { onClick={() => {
msg.openFile(['config', 'bin-path.yml']); msg.openFile(['config', 'bin-path.yml']);
handleClose(); handleClose();
}}> }}
Open FFmpeg/Mkvmerge file >
</MenuItem> Open FFmpeg/Mkvmerge file
<MenuItem onClick={() => { </MenuItem>
msg.openFile(['config', 'cli-defaults.yml']); <MenuItem
handleClose(); onClick={() => {
}}> msg.openFile(['config', 'cli-defaults.yml']);
Open advanced options handleClose();
</MenuItem> }}
<MenuItem onClick={() => { >
msg.openFolder('content'); Open advanced options
handleClose(); </MenuItem>
}}> <MenuItem
Open output path onClick={() => {
</MenuItem> msg.openFolder('content');
</Menu> handleClose();
<Menu open={openMenu === 'help'} anchorEl={anchorEl} onClose={handleClose}> }}
<MenuItem onClick={() => { >
msg.openURL('https://github.com/anidl/multi-downloader-nx'); Open output path
handleClose(); </MenuItem>
}}> </Menu>
GitHub <Menu open={openMenu === 'help'} anchorEl={anchorEl} onClose={handleClose}>
</MenuItem> <MenuItem
<MenuItem onClick={() => { 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');
handleClose(); handleClose();
}}> }}
Report a bug >
</MenuItem> GitHub
<MenuItem onClick={() => { </MenuItem>
msg.openURL('https://github.com/anidl/multi-downloader-nx/graphs/contributors'); <MenuItem
handleClose(); onClick={() => {
}}> msg.openURL('https://github.com/anidl/multi-downloader-nx/issues/new?assignees=AnimeDL,AnidlSupport&labels=bug&template=bug.yml&title=BUG');
Contributors handleClose();
</MenuItem> }}
<MenuItem onClick={() => { >
msg.openURL('https://discord.gg/qEpbWen5vq'); Report a bug
handleClose(); </MenuItem>
}}> <MenuItem
Discord onClick={() => {
</MenuItem> msg.openURL('https://github.com/anidl/multi-downloader-nx/graphs/contributors');
<MenuItem onClick={() => { handleClose();
handleClose(); }}
}}> >
Version: {store.version} Contributors
</MenuItem> </MenuItem>
</Menu> <MenuItem
<Typography variant="h5" color="text.primary"> onClick={() => {
{transformService(store.service)} msg.openURL('https://discord.gg/qEpbWen5vq');
</Typography> handleClose();
</Box>; }}
>
Discord
</MenuItem>
<MenuItem
onClick={() => {
handleClose();
}}
>
Version: {store.version}
</MenuItem>
</Menu>
<Typography variant="h5" color="text.primary">
{transformService(store.service)}
</Typography>
</Box>
);
}; };
export default MenuBar; export default MenuBar;

View file

@ -2,13 +2,17 @@ import React from 'react';
import { Box, Backdrop, CircularProgress } from '@mui/material'; import { Box, Backdrop, CircularProgress } from '@mui/material';
export type RequireType<T> = { export type RequireType<T> = {
value?: T value?: T;
}
const Require = <T, >(props: React.PropsWithChildren<RequireType<T>>) => {
return props.value === undefined ? <Backdrop open>
<CircularProgress />
</Backdrop> : <Box>{props.children}</Box>;
}; };
export default Require; const Require = <T,>(props: React.PropsWithChildren<RequireType<T>>) => {
return props.value === undefined ? (
<Backdrop open>
<CircularProgress />
</Backdrop>
) : (
<Box>{props.children}</Box>
);
};
export default Require;

View file

@ -5,38 +5,30 @@ 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}>
<Button
startIcon={start ? <PauseCircleFilled /> : <PlayCircleFilled /> }
variant='contained'
onClick={change}
sx={{ maxHeight: '2.3rem' }}
>
{
start ? 'Stop Queue' : 'Start Queue'
}
</Button>
</Require>;
return (
<Require value={messageChannel}>
<Button startIcon={start ? <PauseCircleFilled /> : <PlayCircleFilled />} variant="contained" onClick={change} sx={{ maxHeight: '2.3rem' }}>
{start ? 'Stop Queue' : 'Start Queue'}
</Button>
</Require>
);
}; };
export default StartQueueButton; export default StartQueueButton;

View file

@ -2,64 +2,74 @@ import { Box, Button, Divider, List, SxProps } from '@mui/material';
import React from 'react'; import React from 'react';
export type Option = { export type Option = {
text: string, text: string;
onClick: () => unknown onClick: () => unknown;
}
export type ContextMenuProps<T extends HTMLElement> = {
options: ('divider'|Option)[],
popupItem: React.RefObject<T>
}
const buttonSx: SxProps = {
'&:hover': {
background: 'rgb(0, 30, 60)'
},
fontSize: '0.7rem',
minHeight: '30px',
justifyContent: 'center',
p: 0
}; };
function ContextMenu<T extends HTMLElement, >(props: ContextMenuProps<T>) { export type ContextMenuProps<T extends HTMLElement> = {
const [anchor, setAnchor] = React.useState( { x: 0, y: 0 } ); options: ('divider' | Option)[];
popupItem: React.RefObject<T>;
};
const [show, setShow] = React.useState(false); const buttonSx: SxProps = {
'&:hover': {
background: 'rgb(0, 30, 60)'
},
fontSize: '0.7rem',
minHeight: '30px',
justifyContent: 'center',
p: 0
};
React.useEffect(() => { function ContextMenu<T extends HTMLElement>(props: ContextMenuProps<T>) {
const { popupItem: ref } = props; const [anchor, setAnchor] = React.useState({ x: 0, y: 0 });
if (ref.current === null)
return;
const listener = (ev: MouseEvent) => {
ev.preventDefault();
setAnchor({ x: ev.x + 10, y: ev.y + 10 });
setShow(true);
};
ref.current.addEventListener('contextmenu', listener);
return () => { const [show, setShow] = React.useState(false);
if (ref.current)
ref.current.removeEventListener('contextmenu', listener);
};
}, [ 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 }}> React.useEffect(() => {
<List sx={{ p: 0, m: 0, display: 'flex', flexDirection: 'column' }}> const { popupItem: ref } = props;
{props.options.map((item, i) => { if (ref.current === null) return;
return item === 'divider' ? <Divider key={`ContextMenu_Divider_${i}_${item}`}/> : const listener = (ev: MouseEvent) => {
<Button color='inherit' key={`ContextMenu_Value_${i}_${item}`} onClick={() => { ev.preventDefault();
item.onClick(); setAnchor({ x: ev.x + 10, y: ev.y + 10 });
setShow(false); setShow(true);
}} sx={buttonSx}> };
{item.text} ref.current.addEventListener('contextmenu', listener);
</Button>;
})} return () => {
<Divider /> if (ref.current) ref.current.removeEventListener('contextmenu', listener);
<Button fullWidth color='inherit' onClick={() => setShow(false)} sx={buttonSx} > };
Close }, [props.popupItem]);
</Button>
</List> return show ? (
</Box> : <></>; <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' }}>
{props.options.map((item, i) => {
return item === 'divider' ? (
<Divider key={`ContextMenu_Divider_${i}_${item}`} />
) : (
<Button
color="inherit"
key={`ContextMenu_Value_${i}_${item}`}
onClick={() => {
item.onClick();
setShow(false);
}}
sx={buttonSx}
>
{item.text}
</Button>
);
})}
<Divider />
<Button fullWidth color="inherit" onClick={() => setShow(false)} sx={buttonSx}>
Close
</Button>
</List>
</Box>
) : (
<></>
);
} }
export default ContextMenu; export default ContextMenu;

View file

@ -7,18 +7,16 @@ 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)}%`}</Typography>
props.value, </Box>
)}%`}</Typography> </Box>
</Box> );
</Box>
);
}; };
export default LinearProgressWithLabel; export default LinearProgressWithLabel;

View file

@ -2,73 +2,64 @@ import React from 'react';
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select, Theme, useTheme } from '@mui/material'; import { FormControl, InputLabel, MenuItem, OutlinedInput, Select, Theme, useTheme } from '@mui/material';
export type MultiSelectProps = { export type MultiSelectProps = {
values: string[], values: string[];
selected: string[], selected: string[];
onChange: (values: string[]) => unknown, onChange: (values: string[]) => unknown;
title: string, title: string;
allOption?: boolean allOption?: boolean;
} };
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 ? theme.typography.fontWeightRegular : theme.typography.fontWeightMedium
(personName ?? []).indexOf(name) === -1 };
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium
};
} }
const MultiSelect: React.FC<MultiSelectProps> = (props) => { const MultiSelect: React.FC<MultiSelectProps> = (props) => {
const theme = useTheme(); const theme = useTheme();
return <div> return (
<FormControl sx={{ width: 300 }}> <div>
<InputLabel id="multi-select-label">{props.title}</InputLabel> <FormControl sx={{ width: 300 }}>
<Select <InputLabel id="multi-select-label">{props.title}</InputLabel>
labelId="multi-select-label" <Select
id="multi-select" labelId="multi-select-label"
multiple id="multi-select"
value={(props.selected ?? [])} multiple
onChange={e => { value={props.selected ?? []}
const val = typeof e.target.value === 'string' ? e.target.value.split(',') : e.target.value; onChange={(e) => {
if (props.allOption && val.includes('all')) { const val = typeof e.target.value === 'string' ? e.target.value.split(',') : e.target.value;
if (props.values.length === val.length - 1) if (props.allOption && val.includes('all')) {
props.onChange([]); if (props.values.length === val.length - 1) 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) => selected.join(', ')}
renderValue={(selected) => ( MenuProps={MenuProps}
selected.join(', ') >
)} {props.values.concat(props.allOption ? 'all' : []).map((name) => (
MenuProps={MenuProps} <MenuItem key={`${props.title}_${name}`} value={name} style={getStyles(name, props.selected, theme)}>
> {name}
{props.values.concat(props.allOption ? 'all' : []).map((name) => ( </MenuItem>
<MenuItem ))}
key={`${props.title}_${name}`} </Select>
value={name} </FormControl>
style={getStyles(name, props.selected, theme)} </div>
> );
{name}
</MenuItem>
))}
</Select>
</FormControl>
</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

@ -16,36 +16,35 @@ document.body.style.display = 'flex';
document.body.style.justifyContent = 'center'; 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

@ -1,39 +1,44 @@
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import React from 'react'; import React from 'react';
export default class ErrorHandler extends React.Component<{ export default class ErrorHandler extends React.Component<
children: React.ReactNode|React.ReactNode[] {
}, { children: React.ReactNode | React.ReactNode[];
error?: { },
er: Error, {
stack: React.ErrorInfo error?: {
} er: Error;
}> { stack: React.ErrorInfo;
};
constructor(props: { }
children: React.ReactNode|React.ReactNode[] > {
}) { constructor(props: { 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} <>
<br/> {a}
</>; <br />
})} </>
</Typography> );
</Box> : this.props.children; })}
} </Typography>
} </Box>
) : (
this.props.children
);
}
}

View file

@ -9,236 +9,236 @@ import { useSnackbar } from 'notistack';
import { LockOutlined, PowerSettingsNew } from '@mui/icons-material'; import { LockOutlined, PowerSettingsNew } from '@mui/icons-material';
import { GUIConfig } from '../../../../modules/module.cfg-loader'; 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 [socket, setSocket] = React.useState<undefined | WebSocket>();
const [publicWS, setPublicWS] = React.useState<undefined | WebSocket>();
const [usePassword, setUsePassword] = React.useState<'waiting' | 'yes' | 'no'>('waiting');
const [isSetup, setIsSetup] = React.useState<'waiting' | 'yes' | 'no'>('waiting');
const [store, dispatch] = useStore(); const { enqueueSnackbar } = useSnackbar();
const [socket, setSocket] = React.useState<undefined|WebSocket>();
const [publicWS, setPublicWS] = React.useState<undefined|WebSocket>();
const [usePassword, setUsePassword] = React.useState<'waiting'|'yes'|'no'>('waiting');
const [isSetup, setIsSetup] = React.useState<'waiting'|'yes'|'no'>('waiting');
const { enqueueSnackbar } = useSnackbar(); React.useEffect(() => {
const wss = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/public`);
wss.addEventListener('open', () => {
setPublicWS(wss);
});
wss.addEventListener('error', () => {
enqueueSnackbar('Unable to connect to server. Please reload the page to try again.', { variant: 'error' });
});
}, []);
React.useEffect(() => { React.useEffect(() => {
const wss = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/public`); (async () => {
wss.addEventListener('open', () => { if (!publicWS) return;
setPublicWS(wss); setUsePassword((await messageAndResponse(publicWS, { name: 'requirePassword', data: undefined })).data ? 'yes' : 'no');
}); setIsSetup((await messageAndResponse(publicWS, { name: 'isSetup', data: undefined })).data ? 'yes' : 'no');
wss.addEventListener('error', () => { })();
enqueueSnackbar('Unable to connect to server. Please reload the page to try again.', { variant: 'error' }); }, [publicWS]);
});
}, []);
React.useEffect(() => { const connect = (ev?: React.FormEvent<HTMLFormElement>) => {
(async () => { let search = new URLSearchParams();
if (!publicWS) if (ev) {
return; ev.preventDefault();
setUsePassword((await messageAndResponse(publicWS, { name: 'requirePassword', data: undefined })).data ? 'yes' : 'no'); const formData = new FormData(ev.currentTarget);
setIsSetup((await messageAndResponse(publicWS, { name: 'isSetup', data: undefined })).data ? 'yes' : 'no'); const password = formData.get('password')?.toString();
})(); if (!password)
}, [publicWS]); return enqueueSnackbar('Please provide both a username and password', {
variant: 'error'
});
search = new URLSearchParams({
password
});
}
const connect = (ev?: React.FormEvent<HTMLFormElement>) => { const wws = new WebSocket(
let search = new URLSearchParams(); `${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/private?${search}`
if (ev) { );
ev.preventDefault(); wws.addEventListener('open', () => {
const formData = new FormData(ev.currentTarget); console.log('[INFO] [WS] Connected');
const password = formData.get('password')?.toString(); setSocket(wws);
if (!password) });
return enqueueSnackbar('Please provide both a username and password', { wws.addEventListener('error', (er) => {
variant: 'error' console.error('[ERROR] [WS]', er);
}); enqueueSnackbar('Unable to connect to server. Please check the password and try again.', {
search = new URLSearchParams({ variant: 'error'
password });
}); });
} };
const wws = new WebSocket(`${location.protocol == 'https:' ? 'wss' : 'ws'}://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/private?${search}`, ); const setup = async (ev: React.FormEvent<HTMLFormElement>) => {
wws.addEventListener('open', () => { ev.preventDefault();
console.log('[INFO] [WS] Connected'); if (!socket) return enqueueSnackbar('Invalid state: socket not found', { variant: 'error' });
setSocket(wws); const formData = new FormData(ev.currentTarget);
}); const password = formData.get('password');
wws.addEventListener('error', (er) => { const data = {
console.error('[ERROR] [WS]', er); port: parseInt(formData.get('port')?.toString() ?? '') ?? 3000,
enqueueSnackbar('Unable to connect to server. Please check the password and try again.', { password: password ? password.toString() : undefined
variant: 'error' } 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
});
};
const setup = async (ev: React.FormEvent<HTMLFormElement>) => { const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []);
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
});
};
const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []); React.useEffect(() => {
(async () => {
if (!socket) return;
const currentService = await messageAndResponse(socket, { name: 'type', data: undefined });
if (currentService.data !== undefined) return dispatch({ type: 'service', payload: currentService.data });
if (store.service !== currentService.data) messageAndResponse(socket, { name: 'setup', data: store.service });
})();
}, [store.service, dispatch, socket]);
React.useEffect(() => { React.useEffect(() => {
(async () => { if (!socket) return;
if (!socket) /* finish is a placeholder */
return; const listener = (initalData: MessageEvent<string>) => {
const currentService = await messageAndResponse(socket, { name: 'type', data: undefined }); const data = JSON.parse(initalData.data) as RandomEvent<'finish'>;
if (currentService.data !== undefined) randomEventHandler.emit(data.name, data);
return dispatch({ type: 'service', payload: currentService.data }); };
if (store.service !== currentService.data) socket.addEventListener('message', listener);
messageAndResponse(socket, { name: 'setup', data: store.service }); return () => {
})(); socket.removeEventListener('message', listener);
}, [store.service, dispatch, socket]); };
}, [socket]);
React.useEffect(() => { if (usePassword === 'waiting') return <></>;
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') if (socket === undefined) {
return <></>; 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
</Typography>
<Box component="form" onSubmit={connect} sx={{ mt: 1 }}>
<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>
<Typography color="text.secondary" align="center" component="p" variant="body2">
You need to login in order to use this tool.
</Typography>
</Box>
</Box>
);
}
if (socket === undefined) { if (isSetup === 'no') {
if (usePassword === 'no') { return (
connect(undefined); <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}>
return <></>; <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
} <PowerSettingsNew />
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}> </Avatar>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}> <Typography component="h1" variant="h5" color="text.primary">
<LockOutlined /> Confirm
</Avatar> </Typography>
<Typography component="h1" variant="h5" color="text.primary"> <Box component="form" onSubmit={setup} sx={{ mt: 1 }}>
Login <TextField name="port" margin="normal" type="number" fullWidth variant="filled" required label={'Port'} defaultValue={3000} />
</Typography> <TextField name="password" margin="normal" type="password" fullWidth variant="filled" label={'Password'} />
<Box component="form" onSubmit={connect} sx={{ mt: 1 }}> <Button type="submit" variant="contained" sx={{ mt: 3, mb: 2 }} fullWidth>
<TextField name="password" margin='normal' type="password" fullWidth variant="filled" required label={'Password'} /> Confirm
<Button type='submit' variant='contained' sx={{ mt: 3, mb: 2 }} fullWidth>Login</Button> </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. Please enter data that will be set to use this tool.
</Typography> <br />
</Box> Leave blank to use no password (NOT RECOMMENDED)!
</Box>; </Typography>
} </Box>
</Box>
);
}
if (isSetup === 'no') { const messageHandler: FrontEndMessages = {
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}> name: 'default',
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}> auth: async (data) => (await messageAndResponse(socket, { name: 'auth', data })).data,
<PowerSettingsNew /> version: async () => (await messageAndResponse(socket, { name: 'version', data: undefined })).data,
</Avatar> checkToken: async () => (await messageAndResponse(socket, { name: 'checkToken', data: undefined })).data,
<Typography component="h1" variant="h5" color="text.primary"> search: async (data) => (await messageAndResponse(socket, { name: 'search', data })).data,
Confirm handleDefault: async (data) => (await messageAndResponse(socket, { name: 'default', data })).data,
</Typography> availableDubCodes: async () => (await messageAndResponse(socket, { name: 'availableDubCodes', data: undefined })).data,
<Box component="form" onSubmit={setup} sx={{ mt: 1 }}> availableSubCodes: async () => (await messageAndResponse(socket, { name: 'availableSubCodes', data: undefined })).data,
<TextField name="port" margin='normal' type="number" fullWidth variant="filled" required label={'Port'} defaultValue={3000} /> resolveItems: async (data) => (await messageAndResponse(socket, { name: 'resolveItems', data })).data,
<TextField name="password" margin='normal' type="password" fullWidth variant="filled" label={'Password'} /> listEpisodes: async (data) => (await messageAndResponse(socket, { name: 'listEpisodes', data })).data,
<Button type='submit' variant='contained' sx={{ mt: 3, mb: 2 }} fullWidth>Confirm</Button> randomEvents: randomEventHandler,
<Typography color="text.secondary" align='center' component="p" variant='body2'> downloadItem: (data) => messageAndResponse(socket, { name: 'downloadItem', data }),
Please enter data that will be set to use this tool. isDownloading: async () => (await messageAndResponse(socket, { name: 'isDownloading', data: undefined })).data,
<br /> openFolder: async (data) => messageAndResponse(socket, { name: 'openFolder', data }),
Leave blank to use no password (NOT RECOMMENDED)! logout: async () => (await messageAndResponse(socket, { name: 'changeProvider', data: undefined })).data,
</Typography> openFile: async (data) => await messageAndResponse(socket, { name: 'openFile', data }),
</Box> openURL: async (data) => await messageAndResponse(socket, { name: 'openURL', data }),
</Box>; getQueue: async () => (await messageAndResponse(socket, { name: 'getQueue', data: undefined })).data,
} removeFromQueue: async (data) => await messageAndResponse(socket, { name: 'removeFromQueue', data }),
clearQueue: async () => await messageAndResponse(socket, { name: 'clearQueue', data: undefined }),
setDownloadQueue: async (data) => await messageAndResponse(socket, { name: 'setDownloadQueue', data }),
getDownloadQueue: async () => (await messageAndResponse(socket, { name: 'getDownloadQueue', data: undefined })).data
};
const messageHandler: FrontEndMessages = { return <messageChannelContext.Provider value={messageHandler}>{children}</messageChannelContext.Provider>;
name: 'default',
auth: async (data) => (await messageAndResponse(socket, { name: 'auth', data })).data,
version: async () => (await messageAndResponse(socket, { name: 'version', data: undefined })).data,
checkToken: async () => (await messageAndResponse(socket, { name: 'checkToken', data: undefined })).data,
search: async (data) => (await messageAndResponse(socket, { name: 'search', data })).data,
handleDefault: async (data) => (await messageAndResponse(socket, { name: 'default', data })).data,
availableDubCodes: async () => (await messageAndResponse(socket, { name: 'availableDubCodes', data: undefined})).data,
availableSubCodes: async () => (await messageAndResponse(socket, { name: 'availableSubCodes', data: undefined })).data,
resolveItems: async (data) => (await messageAndResponse(socket, { name: 'resolveItems', data })).data,
listEpisodes: async (data) => (await messageAndResponse(socket, { name: 'listEpisodes', data })).data,
randomEvents: randomEventHandler,
downloadItem: (data) => messageAndResponse(socket, { name: 'downloadItem', data }),
isDownloading: async () => (await messageAndResponse(socket, { name: 'isDownloading', data: undefined })).data,
openFolder: async (data) => messageAndResponse(socket, { name: 'openFolder', data }),
logout: async () => (await messageAndResponse(socket, { name: 'changeProvider', data: undefined })).data,
openFile: async (data) => await messageAndResponse(socket, { name: 'openFile', data }),
openURL: async (data) => await messageAndResponse(socket, { name: 'openURL', data }),
getQueue: async () => (await messageAndResponse(socket, { name: 'getQueue', data: undefined })).data,
removeFromQueue: async (data) => await messageAndResponse(socket, { name: 'removeFromQueue', data }),
clearQueue: async () => await messageAndResponse(socket, { name: 'clearQueue', data: undefined }),
setDownloadQueue: async (data) => await messageAndResponse(socket, { name: 'setDownloadQueue', data }),
getDownloadQueue: async () => (await messageAndResponse(socket, { name: 'getDownloadQueue', data: undefined })).data,
};
return <messageChannelContext.Provider value={messageHandler}>
{children}
</messageChannelContext.Provider>;
}; };
export default MessageChannelProvider; export default MessageChannelProvider;

View file

@ -6,30 +6,28 @@ 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}</queueContext.Provider>;
{children}
</queueContext.Provider>;
}; };
export default QueueProvider; export default QueueProvider;

View file

@ -1,35 +1,60 @@
import React from 'react'; import React from 'react';
import {Divider, Box, Button, Typography, Avatar} from '@mui/material'; import { Divider, Box, Button, Typography, Avatar } from '@mui/material';
import useStore from '../hooks/useStore'; import useStore from '../hooks/useStore';
import { StoreState } from './Store'; import { StoreState } from './Store';
type Services = 'crunchy'|'hidive'|'ao'|'adn'; 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 }}>
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}> Please select your service
<Button size='large' variant="contained" onClick={() => setService('crunchy')} startIcon={<Avatar src={'https://static.crunchyroll.com/cxweb/assets/img/favicons/favicon-32x32.png'} />}>Crunchyroll</Button> </Typography>
<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> <Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
<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
<Button size='large' variant="contained" onClick={() => setService('adn')} startIcon={<Avatar src={'https://animationdigitalnetwork.com/favicon.ico'} />}>AnimationDigitalNetwork</Button> size="large"
</Box> variant="contained"
</Box> onClick={() => setService('crunchy')}
: <serviceContext.Provider value={service}> startIcon={<Avatar src={'https://static.crunchyroll.com/cxweb/assets/img/favicons/favicon-32x32.png'} />}
{children} >
</serviceContext.Provider>; 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('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>
</Box>
</Box>
) : (
<serviceContext.Provider value={service}>{children}</serviceContext.Provider>
);
}; };
export default ServiceProvider; export default ServiceProvider;

View file

@ -3,64 +3,64 @@ import { Episode } from '../../../../@types/messageHandler';
import { dubLanguageCodes } from '../../../../modules/module.langsData'; import { dubLanguageCodes } from '../../../../modules/module.langsData';
export type DownloadOptions = { export type DownloadOptions = {
q: number, q: number;
id: string, id: string;
e: string, e: string;
dubLang: typeof dubLanguageCodes, dubLang: typeof dubLanguageCodes;
dlsubs: string[], dlsubs: string[];
fileName: string, fileName: string;
dlVideoOnce: boolean, dlVideoOnce: boolean;
all: boolean, all: boolean;
but: boolean, but: boolean;
novids: boolean, novids: boolean;
hslang?: string, hslang?: string;
simul: boolean, simul: boolean;
noaudio: boolean noaudio: boolean;
} };
export type StoreState = { export type StoreState = {
episodeListing: Episode[]; episodeListing: Episode[];
downloadOptions: DownloadOptions, downloadOptions: DownloadOptions;
service: 'crunchy'|'hidive'|'ao'|'adn'|undefined, service: 'crunchy' | 'hidive' | 'ao' | 'adn' | undefined;
version: string, version: string;
} };
export type StoreAction<T extends (keyof StoreState)> = { export type StoreAction<T extends keyof StoreState> = {
type: T, type: T;
payload: StoreState[T], payload: StoreState[T];
extraInfo?: Record<string, unknown> extraInfo?: Record<string, unknown>;
} };
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,13 +79,9 @@ 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]}>{children}</StoreContext.Provider>;
<StoreContext.Provider value={[state, dispatch]}>
{children}
</StoreContext.Provider>
);
}; };
/* Importent Notice -- The 'queue' generic will be overriden */ /* Importent Notice -- The 'queue' generic will be overriden */
export const StoreContext = React.createContext<[StoreState, React.Dispatch<StoreAction<'downloadOptions'>>]>([initialState, undefined as any]); export const StoreContext = React.createContext<[StoreState, React.Dispatch<StoreAction<'downloadOptions'>>]>([initialState, undefined as any]);
export default Store; export default Store;

View file

@ -2,11 +2,7 @@
"compilerOptions": { "compilerOptions": {
"outDir": "./build", "outDir": "./build",
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@ -22,8 +18,5 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"downlevelIteration": true "downlevelIteration": true
}, },
"include": [ "include": ["./src", "./webpack.config.ts"]
"./src", }
"./webpack.config.ts"
]
}

View file

@ -4,55 +4,58 @@ 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', { [
targets: 'defaults' '@babel/preset-env',
}] {
] targets: 'defaults'
} }
}, ]
}, ]
{ }
test: /\.css$/i, }
use: ['style-loader', 'css-loader'], },
}, {
], test: /\.css$/i,
}, use: ['style-loader', 'css-loader']
plugins: [ }
new HtmlWebpackPlugin({ ]
template: path.join(process.cwd(), 'public', 'index.html') },
}) plugins: [
] new HtmlWebpackPlugin({
template: path.join(process.cwd(), 'public', 'index.html')
})
]
}; };
export default config; export default config;

View file

@ -25,10 +25,10 @@ app.use(express.static(path.join(workingDir, 'gui', 'server', 'build'), { maxAge
console.info(`\n=== Multi Downloader NX GUI ${packageJson.version} ===\n`); console.info(`\n=== Multi Downloader NX GUI ${packageJson.version} ===\n`);
const server = app.listen(cfg.gui.port, () => { const server = app.listen(cfg.gui.port, () => {
console.info(`GUI server started on port ${cfg.gui.port}`); console.info(`GUI server started on port ${cfg.gui.port}`);
}); });
new PublicWebSocket(server); new PublicWebSocket(server);
new ServiceHandler(server); new ServiceHandler(server);
open(`http://localhost:${cfg.gui.port}`); open(`http://localhost:${cfg.gui.port}`);

View file

@ -11,124 +11,113 @@ import WebSocketHandler from './websocket';
import packageJson from '../../package.json'; import packageJson from '../../package.json';
export default class ServiceHandler { export default class ServiceHandler {
private service: MessageHandler | undefined = undefined;
private ws: WebSocketHandler;
private state: GuiState;
private service: MessageHandler|undefined = undefined; constructor(server: Server<typeof IncomingMessage, typeof ServerResponse>) {
private ws: WebSocketHandler; this.ws = new WebSocketHandler(server);
private state: GuiState; this.handleMessages();
this.state = getState();
}
constructor(server: Server<typeof IncomingMessage, typeof ServerResponse>) { private handleMessages() {
this.ws = new WebSocketHandler(server); this.ws.events.on('setupServer', ({ data }, respond) => {
this.handleMessages(); writeYamlCfgFile('gui', data);
this.state = getState(); this.state.setup = true;
} setState(this.state);
respond(true);
process.exit(0);
});
private handleMessages() { this.ws.events.on('setup', ({ data }) => {
this.ws.events.on('setupServer', ({ data }, respond) => { if (data === 'crunchy') {
writeYamlCfgFile('gui', data); this.service = new CrunchyHandler(this.ws);
this.state.setup = true; } else if (data === 'hidive') {
setState(this.state); this.service = new HidiveHandler(this.ws);
respond(true); } else if (data === 'ao') {
process.exit(0); this.service = new AnimeOnegaiHandler(this.ws);
}); } else if (data === 'adn') {
this.service = new ADNHandler(this.ws);
}
});
this.ws.events.on('setup', ({ data }) => { this.ws.events.on('changeProvider', async (_, respond) => {
if (data === 'crunchy') { if (await this.service?.isDownloading()) return respond(false);
this.service = new CrunchyHandler(this.ws); this.service = undefined;
} else if (data === 'hidive') { respond(true);
this.service = new HidiveHandler(this.ws); });
} else if (data === 'ao') {
this.service = new AnimeOnegaiHandler(this.ws);
} else if (data === 'adn') {
this.service = new ADNHandler(this.ws);
}
});
this.ws.events.on('changeProvider', async (_, respond) => {
if (await this.service?.isDownloading())
return respond(false);
this.service = undefined;
respond(true);
});
this.ws.events.on('auth', async ({ data }, respond) => { this.ws.events.on('auth', async ({ data }, respond) => {
if (this.service === undefined) if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') });
return respond({ isOk: false, reason: new Error('No service selected') }); respond(await this.service.auth(data));
respond(await this.service.auth(data)); });
}); this.ws.events.on('version', async (_, respond) => {
this.ws.events.on('version', async (_, respond) => { respond(packageJson.version);
respond(packageJson.version); });
}); this.ws.events.on('type', async (_, respond) => respond(this.service === undefined ? undefined : (this.service.name as 'hidive' | 'crunchy' | 'ao' | 'adn')));
this.ws.events.on('type', async (_, respond) => respond(this.service === undefined ? undefined : this.service.name as 'hidive'|'crunchy'|'ao'|'adn')); this.ws.events.on('checkToken', async (_, respond) => {
this.ws.events.on('checkToken', async (_, respond) => { if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') });
if (this.service === undefined) respond(await this.service.checkToken());
return respond({ isOk: false, reason: new Error('No service selected') }); });
respond(await this.service.checkToken()); this.ws.events.on('search', async ({ data }, respond) => {
}); if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') });
this.ws.events.on('search', async ({ data }, respond) => { respond(await this.service.search(data));
if (this.service === undefined) });
return respond({ isOk: false, reason: new Error('No service selected') }); this.ws.events.on('default', async ({ data }, respond) => {
respond(await this.service.search(data)); if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') });
}); respond(await this.service.handleDefault(data));
this.ws.events.on('default', async ({ data }, respond) => { });
if (this.service === undefined) this.ws.events.on('availableDubCodes', async (_, respond) => {
return respond({ isOk: false, reason: new Error('No service selected') }); if (this.service === undefined) return respond([]);
respond(await this.service.handleDefault(data)); respond(await this.service.availableDubCodes());
}); });
this.ws.events.on('availableDubCodes', async (_, respond) => { this.ws.events.on('availableSubCodes', async (_, respond) => {
if (this.service === undefined) if (this.service === undefined) return respond([]);
return respond([]); respond(await this.service.availableSubCodes());
respond(await this.service.availableDubCodes()); });
}); this.ws.events.on('resolveItems', async ({ data }, respond) => {
this.ws.events.on('availableSubCodes', async (_, respond) => { if (this.service === undefined) return respond(false);
if (this.service === undefined) respond(await this.service.resolveItems(data));
return respond([]); });
respond(await this.service.availableSubCodes()); this.ws.events.on('listEpisodes', async ({ data }, respond) => {
}); if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') });
this.ws.events.on('resolveItems', async ({ data }, respond) => { respond(await this.service.listEpisodes(data));
if (this.service === undefined) });
return respond(false); this.ws.events.on('downloadItem', async ({ data }, respond) => {
respond(await this.service.resolveItems(data)); this.service?.downloadItem(data);
}); respond(undefined);
this.ws.events.on('listEpisodes', async ({ data }, respond) => { });
if (this.service === undefined) this.ws.events.on('openFolder', async ({ data }, respond) => {
return respond({ isOk: false, reason: new Error('No service selected') }); this.service?.openFolder(data);
respond(await this.service.listEpisodes(data)); respond(undefined);
}); });
this.ws.events.on('downloadItem', async ({ data }, respond) => { this.ws.events.on('openFile', async ({ data }, respond) => {
this.service?.downloadItem(data); this.service?.openFile(data);
respond(undefined); respond(undefined);
}); });
this.ws.events.on('openFolder', async ({ data }, respond) => { this.ws.events.on('openURL', async ({ data }, respond) => {
this.service?.openFolder(data); this.service?.openURL(data);
respond(undefined); respond(undefined);
}); });
this.ws.events.on('openFile', async ({ data }, respond) => { this.ws.events.on('getQueue', async (_, respond) => {
this.service?.openFile(data); respond((await this.service?.getQueue()) ?? []);
respond(undefined); });
}); this.ws.events.on('removeFromQueue', async ({ data }, respond) => {
this.ws.events.on('openURL', async ({ data }, respond) => { this.service?.removeFromQueue(data);
this.service?.openURL(data); respond(undefined);
respond(undefined); });
}); this.ws.events.on('clearQueue', async (_, respond) => {
this.ws.events.on('getQueue', async (_, respond) => { this.service?.clearQueue();
respond(await this.service?.getQueue() ?? []); respond(undefined);
}); });
this.ws.events.on('removeFromQueue', async ({ data }, respond) => { this.ws.events.on('setDownloadQueue', async ({ data }, respond) => {
this.service?.removeFromQueue(data); this.service?.setDownloadQueue(data);
respond(undefined); respond(undefined);
}); });
this.ws.events.on('clearQueue', async (_, respond) => { this.ws.events.on('getDownloadQueue', async (_, respond) => {
this.service?.clearQueue(); respond((await this.service?.getDownloadQueue()) ?? false);
respond(undefined); });
}); this.ws.events.on('isDownloading', async (_, respond) => respond((await this.service?.isDownloading()) ?? false));
this.ws.events.on('setDownloadQueue', async ({ data }, respond) => { }
this.service?.setDownloadQueue(data); }
respond(undefined);
});
this.ws.events.on('getDownloadQueue', async (_, respond) => {
respond(await this.service?.getDownloadQueue() ?? false);
});
this.ws.events.on('isDownloading', async (_, respond) => respond(await this.service?.isDownloading() ?? false));
}
}

View file

@ -8,132 +8,144 @@ import { console } from '../../../modules/log';
import * as yargs from '../../../modules/module.app-args'; import * as yargs from '../../../modules/module.app-args';
class ADNHandler extends Base implements MessageHandler { class ADNHandler extends Base implements MessageHandler {
private adn: AnimationDigitalNetwork; private adn: AnimationDigitalNetwork;
public name = 'adn'; public name = 'adn';
constructor(ws: WebSocketHandler) { constructor(ws: WebSocketHandler) {
super(ws); super(ws);
this.adn = new AnimationDigitalNetwork(); this.adn = new AnimationDigitalNetwork();
this.initState(); this.initState();
this.getDefaults(); this.getDefaults();
} }
public getDefaults() { public getDefaults() {
const _default = yargs.appArgv(this.adn.cfg.cli, true); const _default = yargs.appArgv(this.adn.cfg.cli, true);
if (['fr', 'de'].includes(_default.locale)) if (['fr', 'de'].includes(_default.locale)) this.adn.locale = _default.locale;
this.adn.locale = _default.locale; }
}
public async auth(data: AuthData) { public async auth(data: AuthData) {
return this.adn.doAuth(data); return this.adn.doAuth(data);
} }
public async checkToken(): Promise<CheckTokenResponse> { public async checkToken(): Promise<CheckTokenResponse> {
//TODO: implement proper method to check token //TODO: implement proper method to check token
return { isOk: true, value: undefined }; return { isOk: true, value: undefined };
} }
public async search(data: SearchData): Promise<SearchResponse> { public async search(data: SearchData): Promise<SearchResponse> {
console.debug(`Got search options: ${JSON.stringify(data)}`); console.debug(`Got search options: ${JSON.stringify(data)}`);
const search = await this.adn.doSearch(data); const search = await this.adn.doSearch(data);
if (!search.isOk) { if (!search.isOk) {
return search; return search;
} }
return { isOk: true, value: search.value }; return { isOk: true, value: search.value };
} }
public async handleDefault(name: string) { public async handleDefault(name: string) {
return getDefault(name, this.adn.cfg.cli); return getDefault(name, this.adn.cfg.cli);
} }
public async availableDubCodes(): Promise<string[]> { public async availableDubCodes(): Promise<string[]> {
const dubLanguageCodesArray: string[] = []; const dubLanguageCodesArray: string[] = [];
for(const language of languages){ for (const language of languages) {
if (language.adn_locale) if (language.adn_locale) dubLanguageCodesArray.push(language.code);
dubLanguageCodesArray.push(language.code); }
} return [...new Set(dubLanguageCodesArray)];
return [...new Set(dubLanguageCodesArray)]; }
}
public async availableSubCodes(): Promise<string[]> { public async availableSubCodes(): Promise<string[]> {
const subLanguageCodesArray: string[] = []; const subLanguageCodesArray: string[] = [];
for(const language of languages){ for (const language of languages) {
if (language.adn_locale) if (language.adn_locale) subLanguageCodesArray.push(language.locale);
subLanguageCodesArray.push(language.locale); }
} return ['all', 'none', ...new Set(subLanguageCodesArray)];
return ['all', 'none', ...new Set(subLanguageCodesArray)]; }
}
public async resolveItems(data: ResolveItemsData): Promise<boolean> { public async resolveItems(data: ResolveItemsData): Promise<boolean> {
const parse = parseInt(data.id); const parse = parseInt(data.id);
if (isNaN(parse) || parse <= 0) if (isNaN(parse) || parse <= 0) return false;
return false; console.debug(`Got resolve options: ${JSON.stringify(data)}`);
console.debug(`Got resolve options: ${JSON.stringify(data)}`); const res = await this.adn.selectShow(parseInt(data.id), data.e, data.but, data.all);
const res = await this.adn.selectShow(parseInt(data.id), data.e, data.but, data.all); if (!res.isOk || !res.value) return res.isOk;
if (!res.isOk || !res.value) this.addToQueue(
return res.isOk; res.value.map((a) => {
this.addToQueue(res.value.map(a => { return {
return { ...data,
...data, ids: [a.id],
ids: [a.id], title: a.title,
title: a.title, parent: {
parent: { title: a.show.shortTitle,
title: a.show.shortTitle, season: a.season
season: a.season },
}, e: a.shortNumber,
e: a.shortNumber, image: a.image,
image: a.image, episode: a.shortNumber
episode: a.shortNumber };
}; })
})); );
return true; return true;
} }
public async listEpisodes(id: string): Promise<EpisodeListResponse> { public async listEpisodes(id: string): Promise<EpisodeListResponse> {
const parse = parseInt(id); const parse = parseInt(id);
if (isNaN(parse) || parse <= 0) if (isNaN(parse) || parse <= 0) return { isOk: false, reason: new Error('The ID is invalid') };
return { isOk: false, reason: new Error('The ID is invalid') };
const request = await this.adn.listShow(parse); const request = await this.adn.listShow(parse);
if (!request.isOk || !request.value) if (!request.isOk || !request.value) return { isOk: false, reason: new Error('Unknown upstream error, check for additional logs') };
return {isOk: false, reason: new Error('Unknown upstream error, check for additional logs')};
return { isOk: true, value: request.value.videos.map(function(item) { return {
return { isOk: true,
e: item.shortNumber, value: request.value.videos.map(function (item) {
lang: [], return {
name: item.title, e: item.shortNumber,
season: item.season, lang: [],
seasonTitle: item.show.title, name: item.title,
episode: item.shortNumber, season: item.season,
id: item.id+'', seasonTitle: item.show.title,
img: item.image, episode: item.shortNumber,
description: item.summary, id: item.id + '',
time: item.duration+'' img: item.image,
}; description: item.summary,
})}; time: item.duration + ''
} };
})
};
}
public async downloadItem(data: DownloadData) { public async downloadItem(data: DownloadData) {
this.setDownloading(true); this.setDownloading(true);
console.debug(`Got download options: ${JSON.stringify(data)}`); console.debug(`Got download options: ${JSON.stringify(data)}`);
const _default = yargs.appArgv(this.adn.cfg.cli, true); const _default = yargs.appArgv(this.adn.cfg.cli, true);
const res = await this.adn.selectShow(parseInt(data.id), data.e, false, false); const res = await this.adn.selectShow(parseInt(data.id), data.e, false, false);
if (res.isOk) { if (res.isOk) {
for (const select of res.value) { for (const select of res.value) {
if (!(await this.adn.getEpisode(select, {..._default, skipsubs: false, callbackMaker: this.makeProgressHandler.bind(this), q: data.q, fileName: data.fileName, dlsubs: data.dlsubs, dlVideoOnce: data.dlVideoOnce, force: 'y', if (
novids: data.novids, noaudio: data.noaudio, hslang: data.hslang || 'none', dubLang: data.dubLang }))) { !(await this.adn.getEpisode(select, {
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`); ..._default,
er.name = 'Download error'; skipsubs: false,
this.alertError(er); callbackMaker: this.makeProgressHandler.bind(this),
} q: data.q,
} fileName: data.fileName,
} else { dlsubs: data.dlsubs,
this.alertError(new Error('Failed to download episode, check for additional logs.')); dlVideoOnce: data.dlVideoOnce,
} force: 'y',
this.sendMessage({ name: 'finish', data: undefined }); novids: data.novids,
this.setDownloading(false); noaudio: data.noaudio,
this.onFinish(); hslang: data.hslang || 'none',
} dubLang: data.dubLang
}))
) {
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`);
er.name = 'Download error';
this.alertError(er);
}
}
} else {
this.alertError(new Error('Failed to download episode, check for additional logs.'));
}
this.sendMessage({ name: 'finish', data: undefined });
this.setDownloading(false);
this.onFinish();
}
} }
export default ADNHandler; export default ADNHandler;

View file

@ -1,4 +1,14 @@
import { AuthData, CheckTokenResponse, DownloadData, Episode, EpisodeListResponse, MessageHandler, ResolveItemsData, SearchData, SearchResponse } from '../../../@types/messageHandler'; import {
AuthData,
CheckTokenResponse,
DownloadData,
Episode,
EpisodeListResponse,
MessageHandler,
ResolveItemsData,
SearchData,
SearchResponse
} from '../../../@types/messageHandler';
import AnimeOnegai from '../../../ao'; import AnimeOnegai from '../../../ao';
import { getDefault } from '../../../modules/module.args'; import { getDefault } from '../../../modules/module.args';
import { languages } from '../../../modules/module.langsData'; import { languages } from '../../../modules/module.langsData';
@ -8,144 +18,153 @@ import { console } from '../../../modules/log';
import * as yargs from '../../../modules/module.app-args'; import * as yargs from '../../../modules/module.app-args';
class AnimeOnegaiHandler extends Base implements MessageHandler { class AnimeOnegaiHandler extends Base implements MessageHandler {
private ao: AnimeOnegai; private ao: AnimeOnegai;
public name = 'ao'; public name = 'ao';
constructor(ws: WebSocketHandler) { constructor(ws: WebSocketHandler) {
super(ws); super(ws);
this.ao = new AnimeOnegai(); this.ao = new AnimeOnegai();
this.initState(); this.initState();
this.getDefaults(); this.getDefaults();
} }
public getDefaults() { public getDefaults() {
const _default = yargs.appArgv(this.ao.cfg.cli, true); const _default = yargs.appArgv(this.ao.cfg.cli, true);
if (['es', 'pt'].includes(_default.locale)) if (['es', 'pt'].includes(_default.locale)) this.ao.locale = _default.locale;
this.ao.locale = _default.locale; }
}
public async auth(data: AuthData) { public async auth(data: AuthData) {
return this.ao.doAuth(data); return this.ao.doAuth(data);
} }
public async checkToken(): Promise<CheckTokenResponse> { public async checkToken(): Promise<CheckTokenResponse> {
//TODO: implement proper method to check token //TODO: implement proper method to check token
return { isOk: true, value: undefined }; return { isOk: true, value: undefined };
} }
public async search(data: SearchData): Promise<SearchResponse> { public async search(data: SearchData): Promise<SearchResponse> {
console.debug(`Got search options: ${JSON.stringify(data)}`); console.debug(`Got search options: ${JSON.stringify(data)}`);
const search = await this.ao.doSearch(data); const search = await this.ao.doSearch(data);
if (!search.isOk) { if (!search.isOk) {
return search; return search;
} }
return { isOk: true, value: search.value }; return { isOk: true, value: search.value };
} }
public async handleDefault(name: string) { public async handleDefault(name: string) {
return getDefault(name, this.ao.cfg.cli); return getDefault(name, this.ao.cfg.cli);
} }
public async availableDubCodes(): Promise<string[]> { public async availableDubCodes(): Promise<string[]> {
const dubLanguageCodesArray: string[] = []; const dubLanguageCodesArray: string[] = [];
for(const language of languages){ for (const language of languages) {
if (language.ao_locale) if (language.ao_locale) dubLanguageCodesArray.push(language.code);
dubLanguageCodesArray.push(language.code); }
} return [...new Set(dubLanguageCodesArray)];
return [...new Set(dubLanguageCodesArray)]; }
}
public async availableSubCodes(): Promise<string[]> { public async availableSubCodes(): Promise<string[]> {
const subLanguageCodesArray: string[] = []; const subLanguageCodesArray: string[] = [];
for(const language of languages){ for (const language of languages) {
if (language.ao_locale) if (language.ao_locale) subLanguageCodesArray.push(language.locale);
subLanguageCodesArray.push(language.locale); }
} return ['all', 'none', ...new Set(subLanguageCodesArray)];
return ['all', 'none', ...new Set(subLanguageCodesArray)]; }
}
public async resolveItems(data: ResolveItemsData): Promise<boolean> { public async resolveItems(data: ResolveItemsData): Promise<boolean> {
const parse = parseInt(data.id); const parse = parseInt(data.id);
if (isNaN(parse) || parse <= 0) if (isNaN(parse) || parse <= 0) return false;
return false; console.debug(`Got resolve options: ${JSON.stringify(data)}`);
console.debug(`Got resolve options: ${JSON.stringify(data)}`); const _default = yargs.appArgv(this.ao.cfg.cli, true);
const _default = yargs.appArgv(this.ao.cfg.cli, true); const res = await this.ao.selectShow(parseInt(data.id), data.e, data.but, data.all, _default);
const res = await this.ao.selectShow(parseInt(data.id), data.e, data.but, data.all, _default); if (!res.isOk || !res.value) return res.isOk;
if (!res.isOk || !res.value) this.addToQueue(
return res.isOk; res.value.map((a) => {
this.addToQueue(res.value.map(a => { return {
return { ...data,
...data, ids: a.data.map((a) => a.videoId),
ids: a.data.map(a => a.videoId), title: a.episodeTitle,
title: a.episodeTitle, parent: {
parent: { title: a.seasonTitle,
title: a.seasonTitle, season: a.seasonTitle
season: a.seasonTitle },
}, e: a.episodeNumber + '',
e: a.episodeNumber+'', image: a.image,
image: a.image, episode: a.episodeNumber + ''
episode: a.episodeNumber+'' };
}; })
})); );
return true; return true;
} }
public async listEpisodes(id: string): Promise<EpisodeListResponse> { public async listEpisodes(id: string): Promise<EpisodeListResponse> {
const parse = parseInt(id); const parse = parseInt(id);
if (isNaN(parse) || parse <= 0) if (isNaN(parse) || parse <= 0) return { isOk: false, reason: new Error('The ID is invalid') };
return { isOk: false, reason: new Error('The ID is invalid') };
const request = await this.ao.listShow(parse); const request = await this.ao.listShow(parse);
if (!request.isOk || !request.value) if (!request.isOk || !request.value) return { isOk: false, reason: new Error('Unknown upstream error, check for additional logs') };
return {isOk: false, reason: new Error('Unknown upstream error, check for additional logs')};
const episodes: Episode[] = []; const episodes: Episode[] = [];
const seasonNumberTitleParse = request.series.data.title.match(/\d+$/); const seasonNumberTitleParse = request.series.data.title.match(/\d+$/);
const seasonNumber = seasonNumberTitleParse ? parseInt(seasonNumberTitleParse[0]) : 1; const seasonNumber = seasonNumberTitleParse ? parseInt(seasonNumberTitleParse[0]) : 1;
//request.value //request.value
for (const episodeKey in request.value) { for (const episodeKey in request.value) {
const episode = request.value[episodeKey][0]; const episode = request.value[episodeKey][0];
const langs = Array.from(new Set(request.value[episodeKey].map(a=>a.lang))); const langs = Array.from(new Set(request.value[episodeKey].map((a) => a.lang)));
episodes.push({ episodes.push({
e: episode.number+'', e: episode.number + '',
lang: langs as string[], lang: langs as string[],
name: episode.name, name: episode.name,
season: seasonNumber+'', season: seasonNumber + '',
seasonTitle: '', seasonTitle: '',
episode: episode.number+'', episode: episode.number + '',
id: episode.video_entry+'', id: episode.video_entry + '',
img: episode.thumbnail, img: episode.thumbnail,
description: episode.description, description: episode.description,
time: '' time: ''
}); });
} }
return { isOk: true, value: episodes }; return { isOk: true, value: episodes };
} }
public async downloadItem(data: DownloadData) { public async downloadItem(data: DownloadData) {
this.setDownloading(true); this.setDownloading(true);
console.debug(`Got download options: ${JSON.stringify(data)}`); console.debug(`Got download options: ${JSON.stringify(data)}`);
const _default = yargs.appArgv(this.ao.cfg.cli, true); const _default = yargs.appArgv(this.ao.cfg.cli, true);
const res = await this.ao.selectShow(parseInt(data.id), data.e, false, false, { const res = await this.ao.selectShow(parseInt(data.id), data.e, false, false, {
..._default, ..._default,
dubLang: data.dubLang, dubLang: data.dubLang,
e: data.e e: data.e
}); });
if (res.isOk) { if (res.isOk) {
for (const select of res.value) { for (const select of res.value) {
if (!(await this.ao.downloadEpisode(select, {..._default, skipsubs: false, callbackMaker: this.makeProgressHandler.bind(this), q: data.q, fileName: data.fileName, dlsubs: data.dlsubs, dlVideoOnce: data.dlVideoOnce, force: 'y', if (
novids: data.novids, noaudio: data.noaudio, hslang: data.hslang || 'none', dubLang: data.dubLang }))) { !(await this.ao.downloadEpisode(select, {
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`); ..._default,
er.name = 'Download error'; skipsubs: false,
this.alertError(er); callbackMaker: this.makeProgressHandler.bind(this),
} q: data.q,
} fileName: data.fileName,
} else { dlsubs: data.dlsubs,
this.alertError(new Error('Failed to download episode, check for additional logs.')); dlVideoOnce: data.dlVideoOnce,
} force: 'y',
this.sendMessage({ name: 'finish', data: undefined }); novids: data.novids,
this.setDownloading(false); noaudio: data.noaudio,
this.onFinish(); hslang: data.hslang || 'none',
} dubLang: data.dubLang
}))
) {
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`);
er.name = 'Download error';
this.alertError(er);
}
}
} else {
this.alertError(new Error('Failed to download episode, check for additional logs.'));
}
this.sendMessage({ name: 'finish', data: undefined });
this.setDownloading(false);
this.onFinish();
}
} }
export default AnimeOnegaiHandler; export default AnimeOnegaiHandler;

View file

@ -9,140 +9,140 @@ import { getState, setState } from '../../../modules/module.cfg-loader';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
export default class Base { export default class Base {
private state: GuiState; private state: GuiState;
public name = 'default'; public name = 'default';
constructor(private ws: WebSocketHandler) { constructor(private ws: WebSocketHandler) {
this.state = getState(); this.state = getState();
} }
private downloading = false; private downloading = false;
private queue: QueueItem[] = []; private queue: QueueItem[] = [];
private workOnQueue = false; private workOnQueue = false;
version(): Promise<string> { version(): Promise<string> {
return new Promise(() => { return new Promise(() => {
return packageJson.version; return packageJson.version;
}); });
} }
initState() { initState() {
if (this.state.services[this.name]) { if (this.state.services[this.name]) {
this.queue = this.state.services[this.name].queue; this.queue = this.state.services[this.name].queue;
this.queueChange(); this.queueChange();
} else { } else {
this.state.services[this.name] = { this.state.services[this.name] = {
'queue': [] queue: []
}; };
} }
} }
setDownloading(downloading: boolean) { setDownloading(downloading: boolean) {
this.downloading = downloading; this.downloading = downloading;
} }
getDownloading() { getDownloading() {
return this.downloading; return this.downloading;
} }
alertError(error: Error) { alertError(error: Error) {
console.error(`${error}`); console.error(`${error}`);
} }
makeProgressHandler(videoInfo: DownloadInfo) { makeProgressHandler(videoInfo: DownloadInfo) {
return ((data: ProgressData) => { return (data: ProgressData) => {
this.sendMessage({ this.sendMessage({
name: 'progress', name: 'progress',
data: { data: {
downloadInfo: videoInfo, downloadInfo: videoInfo,
progress: data progress: data
} }
}); });
}); };
} }
sendMessage<T extends keyof RandomEvents>(data: RandomEvent<T>) { sendMessage<T extends keyof RandomEvents>(data: RandomEvent<T>) {
this.ws.sendMessage(data); this.ws.sendMessage(data);
} }
async isDownloading() { async isDownloading() {
return this.downloading; return this.downloading;
} }
async openFolder(folderType: FolderTypes) { async openFolder(folderType: FolderTypes) {
switch (folderType) { switch (folderType) {
case 'content': case 'content':
open(cfg.dir.content); open(cfg.dir.content);
break; break;
case 'config': case 'config':
open(cfg.dir.config); open(cfg.dir.config);
break; break;
} }
} }
async openFile(data: [FolderTypes, string]) { async openFile(data: [FolderTypes, string]) {
switch (data[0]) { switch (data[0]) {
case 'config': case 'config':
open(path.join(cfg.dir.config, data[1])); open(path.join(cfg.dir.config, data[1]));
break; break;
case 'content': case 'content':
throw new Error('No subfolders'); throw new Error('No subfolders');
} }
} }
async openURL(data: string) { async openURL(data: string) {
open(data); open(data);
} }
public async getQueue(): Promise<QueueItem[]> { public async getQueue(): Promise<QueueItem[]> {
return this.queue; return this.queue;
} }
public async removeFromQueue(index: number) { public async removeFromQueue(index: number) {
this.queue.splice(index, 1); this.queue.splice(index, 1);
this.queueChange(); this.queueChange();
} }
public async clearQueue() { public async clearQueue() {
this.queue = []; this.queue = [];
this.queueChange(); this.queueChange();
} }
public addToQueue(data: QueueItem[]) { public addToQueue(data: QueueItem[]) {
this.queue = this.queue.concat(...data); this.queue = this.queue.concat(...data);
this.queueChange(); this.queueChange();
} }
public setDownloadQueue(data: boolean) { public setDownloadQueue(data: boolean) {
this.workOnQueue = data; this.workOnQueue = data;
this.queueChange(); this.queueChange();
} }
public async getDownloadQueue(): Promise<boolean> { public async getDownloadQueue(): Promise<boolean> {
return this.workOnQueue; return this.workOnQueue;
} }
private async queueChange() { private async queueChange() {
this.sendMessage({ name: 'queueChange', data: this.queue }); this.sendMessage({ name: 'queueChange', data: this.queue });
if (this.workOnQueue && this.queue.length > 0 && !await this.isDownloading()) { if (this.workOnQueue && this.queue.length > 0 && !(await this.isDownloading())) {
this.setDownloading(true); this.setDownloading(true);
this.sendMessage({ name: 'current', data: this.queue[0] }); this.sendMessage({ name: 'current', data: this.queue[0] });
this.downloadItem(this.queue[0]); this.downloadItem(this.queue[0]);
this.queue = this.queue.slice(1); this.queue = this.queue.slice(1);
this.queueChange(); this.queueChange();
} }
this.state.services[this.name].queue = this.queue; this.state.services[this.name].queue = this.queue;
setState(this.state); setState(this.state);
} }
public async onFinish() { public async onFinish() {
this.sendMessage({ name: 'current', data: undefined }); this.sendMessage({ name: 'current', data: undefined });
this.queueChange(); this.queueChange();
} }
//Overriten //Overriten
// eslint-disable-next-line // eslint-disable-next-line
public async downloadItem(_: QueueItem) { public async downloadItem(_: QueueItem) {
throw new Error('downloadItem not overriden'); throw new Error('downloadItem not overriden');
} }
} }

View file

@ -8,120 +8,133 @@ import { console } from '../../../modules/log';
import * as yargs from '../../../modules/module.app-args'; import * as yargs from '../../../modules/module.app-args';
class CrunchyHandler extends Base implements MessageHandler { class CrunchyHandler extends Base implements MessageHandler {
private crunchy: Crunchy; private crunchy: Crunchy;
public name = 'crunchy'; public name = 'crunchy';
constructor(ws: WebSocketHandler) { constructor(ws: WebSocketHandler) {
super(ws); super(ws);
this.crunchy = new Crunchy(); this.crunchy = new Crunchy();
this.crunchy.refreshToken(); this.crunchy.refreshToken();
this.initState(); this.initState();
this.getDefaults(); this.getDefaults();
} }
public getDefaults() { public getDefaults() {
const _default = yargs.appArgv(this.crunchy.cfg.cli, true); const _default = yargs.appArgv(this.crunchy.cfg.cli, true);
this.crunchy.locale = _default.locale; this.crunchy.locale = _default.locale;
} }
public async listEpisodes (id: string): Promise<EpisodeListResponse> {
this.getDefaults();
await this.crunchy.refreshToken(true);
return { isOk: true, value: (await this.crunchy.listSeriesID(id)).list };
}
public async handleDefault(name: string) {
return getDefault(name, this.crunchy.cfg.cli);
}
public async availableDubCodes(): Promise<string[]> { public async listEpisodes(id: string): Promise<EpisodeListResponse> {
const dubLanguageCodesArray: string[] = []; this.getDefaults();
for(const language of languages){ await this.crunchy.refreshToken(true);
if (language.cr_locale) return { isOk: true, value: (await this.crunchy.listSeriesID(id)).list };
dubLanguageCodesArray.push(language.code); }
}
return [...new Set(dubLanguageCodesArray)];
}
public async availableSubCodes(): Promise<string[]> { public async handleDefault(name: string) {
return subtitleLanguagesFilter; return getDefault(name, this.crunchy.cfg.cli);
} }
public async resolveItems(data: ResolveItemsData): Promise<boolean> { public async availableDubCodes(): Promise<string[]> {
this.getDefaults(); const dubLanguageCodesArray: string[] = [];
await this.crunchy.refreshToken(true); for (const language of languages) {
console.debug(`Got resolve options: ${JSON.stringify(data)}`); if (language.cr_locale) dubLanguageCodesArray.push(language.code);
const res = await this.crunchy.downloadFromSeriesID(data.id, data); }
if (!res.isOk) return [...new Set(dubLanguageCodesArray)];
return res.isOk; }
this.addToQueue(res.value.map(a => {
return {
...data,
ids: a.data.map(a => a.mediaId),
title: a.episodeTitle,
parent: {
title: a.seasonTitle,
season: a.season.toString()
},
e: a.e,
image: a.image,
episode: a.episodeNumber
};
}));
return true;
}
public async search(data: SearchData): Promise<SearchResponse> { public async availableSubCodes(): Promise<string[]> {
this.getDefaults(); return subtitleLanguagesFilter;
await this.crunchy.refreshToken(true); }
if (!data['search-type']) data['search-type'] = 'series';
console.debug(`Got search options: ${JSON.stringify(data)}`);
const crunchySearch = await this.crunchy.doSearch(data);
if (!crunchySearch.isOk) {
this.crunchy.refreshToken();
return crunchySearch;
}
return { isOk: true, value: crunchySearch.value };
}
public async checkToken(): Promise<CheckTokenResponse> { public async resolveItems(data: ResolveItemsData): Promise<boolean> {
if (await this.crunchy.getProfile()) { this.getDefaults();
return { isOk: true, value: undefined }; await this.crunchy.refreshToken(true);
} else { console.debug(`Got resolve options: ${JSON.stringify(data)}`);
return { isOk: false, reason: new Error('') }; const res = await this.crunchy.downloadFromSeriesID(data.id, data);
} if (!res.isOk) return res.isOk;
} this.addToQueue(
res.value.map((a) => {
return {
...data,
public auth(data: AuthData) { ids: a.data.map((a) => a.mediaId),
return this.crunchy.doAuth(data); title: a.episodeTitle,
} parent: {
title: a.seasonTitle,
season: a.season.toString()
},
e: a.e,
image: a.image,
episode: a.episodeNumber
};
})
);
return true;
}
public async downloadItem(data: DownloadData) { public async search(data: SearchData): Promise<SearchResponse> {
this.getDefaults(); this.getDefaults();
await this.crunchy.refreshToken(true); await this.crunchy.refreshToken(true);
console.debug(`Got download options: ${JSON.stringify(data)}`); if (!data['search-type']) data['search-type'] = 'series';
this.setDownloading(true); console.debug(`Got search options: ${JSON.stringify(data)}`);
const _default = yargs.appArgv(this.crunchy.cfg.cli, true); const crunchySearch = await this.crunchy.doSearch(data);
const res = await this.crunchy.downloadFromSeriesID(data.id, { if (!crunchySearch.isOk) {
dubLang: data.dubLang, this.crunchy.refreshToken();
e: data.e return crunchySearch;
}); }
if (res.isOk) { return { isOk: true, value: crunchySearch.value };
for (const select of res.value) { }
if (!(await this.crunchy.downloadEpisode(select, {..._default, skipsubs: false, callbackMaker: this.makeProgressHandler.bind(this), q: data.q, fileName: data.fileName, dlsubs: data.dlsubs, dlVideoOnce: data.dlVideoOnce, force: 'y',
novids: data.novids, noaudio: data.noaudio, hslang: data.hslang || 'none' }))) { public async checkToken(): Promise<CheckTokenResponse> {
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`); if (await this.crunchy.getProfile()) {
er.name = 'Download error'; return { isOk: true, value: undefined };
this.alertError(er); } else {
} return { isOk: false, reason: new Error('') };
} }
} else { }
this.alertError(res.reason);
} public auth(data: AuthData) {
this.sendMessage({ name: 'finish', data: undefined }); return this.crunchy.doAuth(data);
this.setDownloading(false); }
this.onFinish();
} public async downloadItem(data: DownloadData) {
this.getDefaults();
await this.crunchy.refreshToken(true);
console.debug(`Got download options: ${JSON.stringify(data)}`);
this.setDownloading(true);
const _default = yargs.appArgv(this.crunchy.cfg.cli, true);
const res = await this.crunchy.downloadFromSeriesID(data.id, {
dubLang: data.dubLang,
e: data.e
});
if (res.isOk) {
for (const select of res.value) {
if (
!(await this.crunchy.downloadEpisode(select, {
..._default,
skipsubs: false,
callbackMaker: this.makeProgressHandler.bind(this),
q: data.q,
fileName: data.fileName,
dlsubs: data.dlsubs,
dlVideoOnce: data.dlVideoOnce,
force: 'y',
novids: data.novids,
noaudio: data.noaudio,
hslang: data.hslang || 'none'
}))
) {
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`);
er.name = 'Download error';
this.alertError(er);
}
}
} else {
this.alertError(res.reason);
}
this.sendMessage({ name: 'finish', data: undefined });
this.setDownloading(false);
this.onFinish();
}
} }
export default CrunchyHandler; export default CrunchyHandler;

View file

@ -8,120 +8,128 @@ import { console } from '../../../modules/log';
import * as yargs from '../../../modules/module.app-args'; import * as yargs from '../../../modules/module.app-args';
class HidiveHandler extends Base implements MessageHandler { class HidiveHandler extends Base implements MessageHandler {
private hidive: Hidive; private hidive: Hidive;
public name = 'hidive'; public name = 'hidive';
constructor(ws: WebSocketHandler) { constructor(ws: WebSocketHandler) {
super(ws); super(ws);
this.hidive = new Hidive(); this.hidive = new Hidive();
this.initState(); this.initState();
} }
public async auth(data: AuthData) { public async auth(data: AuthData) {
return this.hidive.doAuth(data); return this.hidive.doAuth(data);
} }
public async checkToken(): Promise<CheckTokenResponse> { public async checkToken(): Promise<CheckTokenResponse> {
//TODO: implement proper method to check token //TODO: implement proper method to check token
return { isOk: true, value: undefined }; return { isOk: true, value: undefined };
} }
public async search(data: SearchData): Promise<SearchResponse> { public async search(data: SearchData): Promise<SearchResponse> {
console.debug(`Got search options: ${JSON.stringify(data)}`); console.debug(`Got search options: ${JSON.stringify(data)}`);
const hidiveSearch = await this.hidive.doSearch(data); const hidiveSearch = await this.hidive.doSearch(data);
if (!hidiveSearch.isOk) { if (!hidiveSearch.isOk) {
return hidiveSearch; return hidiveSearch;
} }
return { isOk: true, value: hidiveSearch.value }; return { isOk: true, value: hidiveSearch.value };
} }
public async handleDefault(name: string) { public async handleDefault(name: string) {
return getDefault(name, this.hidive.cfg.cli); return getDefault(name, this.hidive.cfg.cli);
} }
public async availableDubCodes(): Promise<string[]> { public async availableDubCodes(): Promise<string[]> {
const dubLanguageCodesArray: string[] = []; const dubLanguageCodesArray: string[] = [];
for(const language of languages){ for (const language of languages) {
if (language.new_hd_locale) if (language.new_hd_locale) dubLanguageCodesArray.push(language.code);
dubLanguageCodesArray.push(language.code); }
} return [...new Set(dubLanguageCodesArray)];
return [...new Set(dubLanguageCodesArray)]; }
}
public async availableSubCodes(): Promise<string[]> { public async availableSubCodes(): Promise<string[]> {
const subLanguageCodesArray: string[] = []; const subLanguageCodesArray: string[] = [];
for(const language of languages){ for (const language of languages) {
if (language.new_hd_locale) if (language.new_hd_locale) subLanguageCodesArray.push(language.locale);
subLanguageCodesArray.push(language.locale); }
} return ['all', 'none', ...new Set(subLanguageCodesArray)];
return ['all', 'none', ...new Set(subLanguageCodesArray)]; }
}
public async resolveItems(data: ResolveItemsData): Promise<boolean> { public async resolveItems(data: ResolveItemsData): Promise<boolean> {
const parse = parseInt(data.id); const parse = parseInt(data.id);
if (isNaN(parse) || parse <= 0) if (isNaN(parse) || parse <= 0) return false;
return false; console.debug(`Got resolve options: ${JSON.stringify(data)}`);
console.debug(`Got resolve options: ${JSON.stringify(data)}`); const res = await this.hidive.selectSeries(parseInt(data.id), data.e, data.but, data.all);
const res = await this.hidive.selectSeries(parseInt(data.id), data.e, data.but, data.all); if (!res.isOk || !res.value) return res.isOk;
if (!res.isOk || !res.value) this.addToQueue(
return res.isOk; res.value.map((item) => {
this.addToQueue(res.value.map(item => { return {
return { ...data,
...data, ids: [item.id],
ids: [item.id], title: item.title,
title: item.title, parent: {
parent: { title: item.seriesTitle,
title: item.seriesTitle, season: item.episodeInformation.seasonNumber + ''
season: item.episodeInformation.seasonNumber+'' },
}, image: item.thumbnailUrl,
image: item.thumbnailUrl, e: item.episodeInformation.episodeNumber + '',
e: item.episodeInformation.episodeNumber+'', episode: item.episodeInformation.episodeNumber + ''
episode: item.episodeInformation.episodeNumber+'', };
}; })
})); );
return true; return true;
} }
public async listEpisodes(id: string): Promise<EpisodeListResponse> { public async listEpisodes(id: string): Promise<EpisodeListResponse> {
const parse = parseInt(id); const parse = parseInt(id);
if (isNaN(parse) || parse <= 0) if (isNaN(parse) || parse <= 0) return { isOk: false, reason: new Error('The ID is invalid') };
return { isOk: false, reason: new Error('The ID is invalid') };
const request = await this.hidive.listSeries(parse); const request = await this.hidive.listSeries(parse);
if (!request.isOk || !request.value) if (!request.isOk || !request.value) return { isOk: false, reason: new Error('Unknown upstream error, check for additional logs') };
return {isOk: false, reason: new Error('Unknown upstream error, check for additional logs')};
return { isOk: true, value: request.value.map(function(item) { return {
const description = item.description.split('\r\n'); isOk: true,
return { value: request.value.map(function (item) {
e: item.episodeInformation.episodeNumber+'', const description = item.description.split('\r\n');
lang: [], return {
name: item.title, e: item.episodeInformation.episodeNumber + '',
season: item.episodeInformation.seasonNumber+'', lang: [],
seasonTitle: request.series.seasons[item.episodeInformation.seasonNumber-1]?.title ?? request.series.title, name: item.title,
episode: item.episodeInformation.episodeNumber+'', season: item.episodeInformation.seasonNumber + '',
id: item.id+'', seasonTitle: request.series.seasons[item.episodeInformation.seasonNumber - 1]?.title ?? request.series.title,
img: item.thumbnailUrl, episode: item.episodeInformation.episodeNumber + '',
description: description ? description[0] : '', id: item.id + '',
time: '' img: item.thumbnailUrl,
}; description: description ? description[0] : '',
})}; time: ''
} };
})
};
}
public async downloadItem(data: DownloadData) { public async downloadItem(data: DownloadData) {
this.setDownloading(true); this.setDownloading(true);
console.debug(`Got download options: ${JSON.stringify(data)}`); console.debug(`Got download options: ${JSON.stringify(data)}`);
const _default = yargs.appArgv(this.hidive.cfg.cli, true); const _default = yargs.appArgv(this.hidive.cfg.cli, true);
const res = await this.hidive.selectSeries(parseInt(data.id), data.e, false, false); const res = await this.hidive.selectSeries(parseInt(data.id), data.e, false, false);
if (!res.isOk || !res.showData) if (!res.isOk || !res.showData) return this.alertError(new Error('Download failed upstream, check for additional logs'));
return this.alertError(new Error('Download failed upstream, check for additional logs'));
for (const ep of res.value) { for (const ep of res.value) {
await this.hidive.downloadEpisode(ep, {..._default, callbackMaker: this.makeProgressHandler.bind(this), dubLang: data.dubLang, dlsubs: data.dlsubs, fileName: data.fileName, q: data.q, force: 'y', noaudio: data.noaudio, novids: data.novids }); await this.hidive.downloadEpisode(ep, {
} ..._default,
this.sendMessage({ name: 'finish', data: undefined }); callbackMaker: this.makeProgressHandler.bind(this),
this.setDownloading(false); dubLang: data.dubLang,
this.onFinish(); dlsubs: data.dlsubs,
} fileName: data.fileName,
q: data.q,
force: 'y',
noaudio: data.noaudio,
novids: data.novids
});
}
this.sendMessage({ name: 'finish', data: undefined });
this.setDownloading(false);
this.onFinish();
}
} }
export default HidiveHandler; export default HidiveHandler;

Some files were not shown because too many files have changed in this diff Show more