mirror of
https://github.com/OpenSTDL/CrunchyDL.git
synced 2024-10-18 12:25:08 +02:00
added prettier
This commit is contained in:
parent
2e29f467d1
commit
9909e3b558
@ -11,7 +11,7 @@
|
|||||||
"requirePragma": false,
|
"requirePragma": false,
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"tabWidth": 2,
|
"tabWidth": 4,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"vueIndentScriptAndStyle": false,
|
"vueIndentScriptAndStyle": false,
|
||||||
|
72
build.js
72
build.js
@ -1,72 +0,0 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const builder = require('electron-builder')
|
|
||||||
const Platform = builder.Platform
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {import('electron-builder').Configuration}
|
|
||||||
*/
|
|
||||||
const options = {
|
|
||||||
appId: 'com.stratum.crunchyrolldownloader',
|
|
||||||
productName: 'Crunchyroll Downloader',
|
|
||||||
|
|
||||||
compression: 'maximum',
|
|
||||||
removePackageScripts: true,
|
|
||||||
|
|
||||||
nodeGypRebuild: true,
|
|
||||||
buildDependenciesFromSource: true,
|
|
||||||
|
|
||||||
publish: {
|
|
||||||
provider: 'github',
|
|
||||||
releaseType: 'release'
|
|
||||||
},
|
|
||||||
|
|
||||||
directories: {
|
|
||||||
output: 'crunchyroll-downloader-output-${version}'
|
|
||||||
},
|
|
||||||
|
|
||||||
win: {
|
|
||||||
artifactName: 'crunchyroll-downloader-${version}-windows-installer.${ext}',
|
|
||||||
icon: 'public/favicon.ico',
|
|
||||||
target: [
|
|
||||||
{
|
|
||||||
target: 'nsis',
|
|
||||||
arch: ['x64', 'ia32']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
nsis: {
|
|
||||||
deleteAppDataOnUninstall: true
|
|
||||||
},
|
|
||||||
mac: {
|
|
||||||
category: 'public.app-category.entertainment',
|
|
||||||
hardenedRuntime: false,
|
|
||||||
gatekeeperAssess: false,
|
|
||||||
target: [
|
|
||||||
{
|
|
||||||
target: 'default',
|
|
||||||
arch: ['x64', 'arm64']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
linux: {
|
|
||||||
maintainer: 'Stratum',
|
|
||||||
desktop: {
|
|
||||||
StartupNotify: 'false',
|
|
||||||
Encoding: 'UTF-8',
|
|
||||||
MimeType: 'x-scheme-handler/deeplink'
|
|
||||||
},
|
|
||||||
target: ['AppImage', 'rpm', 'deb']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const platform = 'WINDOWS'
|
|
||||||
builder
|
|
||||||
.build({
|
|
||||||
targets: Platform[platform].createTarget(),
|
|
||||||
config: options
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
console.log('----------------------------')
|
|
||||||
console.log('Platform:', platform)
|
|
||||||
console.log('Output:', JSON.stringify(result, null, 2))
|
|
||||||
})
|
|
72
build.ts
Normal file
72
build.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const builder = require('electron-builder')
|
||||||
|
const Platform = builder.Platform
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('electron-builder').Configuration}
|
||||||
|
*/
|
||||||
|
const options = {
|
||||||
|
appId: 'com.stratum.crunchyrolldownloader',
|
||||||
|
productName: 'Crunchyroll Downloader',
|
||||||
|
|
||||||
|
compression: 'maximum',
|
||||||
|
removePackageScripts: true,
|
||||||
|
|
||||||
|
nodeGypRebuild: true,
|
||||||
|
buildDependenciesFromSource: true,
|
||||||
|
|
||||||
|
publish: {
|
||||||
|
provider: 'github',
|
||||||
|
releaseType: 'release'
|
||||||
|
},
|
||||||
|
|
||||||
|
directories: {
|
||||||
|
output: 'crunchyroll-downloader-output-${version}'
|
||||||
|
},
|
||||||
|
|
||||||
|
win: {
|
||||||
|
artifactName: 'crunchyroll-downloader-${version}-windows-installer.${ext}',
|
||||||
|
icon: 'public/favicon.ico',
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
target: 'nsis',
|
||||||
|
arch: ['x64', 'ia32']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
nsis: {
|
||||||
|
deleteAppDataOnUninstall: true
|
||||||
|
},
|
||||||
|
mac: {
|
||||||
|
category: 'public.app-category.entertainment',
|
||||||
|
hardenedRuntime: false,
|
||||||
|
gatekeeperAssess: false,
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
target: 'default',
|
||||||
|
arch: ['x64', 'arm64']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
maintainer: 'Stratum',
|
||||||
|
desktop: {
|
||||||
|
StartupNotify: 'false',
|
||||||
|
Encoding: 'UTF-8',
|
||||||
|
MimeType: 'x-scheme-handler/deeplink'
|
||||||
|
},
|
||||||
|
target: ['AppImage', 'rpm', 'deb']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = 'WINDOWS'
|
||||||
|
builder
|
||||||
|
.build({
|
||||||
|
targets: Platform[platform].createTarget(),
|
||||||
|
config: options
|
||||||
|
})
|
||||||
|
.then((result: any) => {
|
||||||
|
console.log('----------------------------')
|
||||||
|
console.log('Platform:', platform)
|
||||||
|
console.log('Output:', JSON.stringify(result, null, 2))
|
||||||
|
})
|
@ -1,19 +1,16 @@
|
|||||||
import type { ADNSearchFetch } from "./Types";
|
import type { ADNSearchFetch } from './Types'
|
||||||
|
|
||||||
export async function searchADN(q: string) {
|
export async function searchADN(q: string) {
|
||||||
const { data: deData, error: deError } = await useFetch<ADNSearchFetch>(
|
const { data: deData, error: deError } = await useFetch<ADNSearchFetch>(`https://gw.api.animationdigitalnetwork.fr/show/catalog`, {
|
||||||
`https://gw.api.animationdigitalnetwork.fr/show/catalog`,
|
method: 'GET',
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
'x-target-distribution': 'de'
|
||||||
headers: {
|
},
|
||||||
"x-target-distribution": 'de',
|
query: {
|
||||||
},
|
maxAgeCategory: '18',
|
||||||
query: {
|
search: q
|
||||||
"maxAgeCategory": "18",
|
|
||||||
"search": q
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
})
|
||||||
|
|
||||||
if (deError.value) {
|
if (deError.value) {
|
||||||
throw new Error(deError.value?.data.message as string)
|
throw new Error(deError.value?.data.message as string)
|
||||||
@ -21,19 +18,16 @@ export async function searchADN(q: string) {
|
|||||||
|
|
||||||
if (!deData.value) return
|
if (!deData.value) return
|
||||||
|
|
||||||
const { data: frData, error: frError } = await useFetch<ADNSearchFetch>(
|
const { data: frData, error: frError } = await useFetch<ADNSearchFetch>(`https://gw.api.animationdigitalnetwork.fr/show/catalog`, {
|
||||||
`https://gw.api.animationdigitalnetwork.fr/show/catalog`,
|
method: 'GET',
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
'x-target-distribution': 'fr'
|
||||||
headers: {
|
},
|
||||||
"x-target-distribution": 'fr',
|
query: {
|
||||||
},
|
maxAgeCategory: '18',
|
||||||
query: {
|
search: q
|
||||||
"maxAgeCategory": "18",
|
|
||||||
"search": q
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
})
|
||||||
|
|
||||||
if (frError.value) {
|
if (frError.value) {
|
||||||
throw new Error(frError.value?.data.message as string)
|
throw new Error(frError.value?.data.message as string)
|
||||||
@ -41,22 +35,24 @@ export async function searchADN(q: string) {
|
|||||||
|
|
||||||
if (!frData.value) return
|
if (!frData.value) return
|
||||||
|
|
||||||
const deShows = deData.value.shows;
|
const deShows = deData.value.shows
|
||||||
const frShows = frData.value.shows;
|
const frShows = frData.value.shows
|
||||||
|
|
||||||
const mergeLanguagesOfDuplicates = (shows: {
|
const mergeLanguagesOfDuplicates = (
|
||||||
id: number
|
shows: {
|
||||||
url: string
|
id: number
|
||||||
title: string
|
url: string
|
||||||
image2x: string
|
title: string
|
||||||
episodeCount: number,
|
image2x: string
|
||||||
languages: Array<string>
|
episodeCount: number
|
||||||
}[]) => {
|
languages: Array<string>
|
||||||
shows.forEach(show => {
|
}[]
|
||||||
const existingShow = shows.find(s => s.id === show.id);
|
) => {
|
||||||
|
shows.forEach((show) => {
|
||||||
|
const existingShow = shows.find((s) => s.id === show.id)
|
||||||
if (existingShow) {
|
if (existingShow) {
|
||||||
const existingShowIndex = shows.findIndex(s=> s === existingShow);
|
const existingShowIndex = shows.findIndex((s) => s === existingShow)
|
||||||
const rawLanguages = [...show.languages, ...existingShow.languages];
|
const rawLanguages = [...show.languages, ...existingShow.languages]
|
||||||
const languages: Array<string> = []
|
const languages: Array<string> = []
|
||||||
|
|
||||||
for (const l of rawLanguages) {
|
for (const l of rawLanguages) {
|
||||||
@ -66,13 +62,13 @@ export async function searchADN(q: string) {
|
|||||||
}
|
}
|
||||||
show.languages = languages
|
show.languages = languages
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return shows;
|
return shows
|
||||||
};
|
}
|
||||||
|
|
||||||
const allShows = mergeLanguagesOfDuplicates([...deShows, ...frShows]);
|
const allShows = mergeLanguagesOfDuplicates([...deShows, ...frShows])
|
||||||
|
|
||||||
const unique = [...new Map(allShows.map((s) => [s.id, s])).values()];
|
const unique = [...new Map(allShows.map((s) => [s.id, s])).values()]
|
||||||
|
|
||||||
return unique
|
return unique
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import type { ADNEpisodes, ADNEpisodesFetch } from './Types'
|
import type { ADNEpisodes, ADNEpisodesFetch } from './Types'
|
||||||
|
|
||||||
export async function getEpisodesWithShowIdADN(id: number, lang: 'de' | 'fr') {
|
export async function getEpisodesWithShowIdADN(id: number, lang: 'de' | 'fr') {
|
||||||
const { data, error } = await useFetch<ADNEpisodesFetch>(`https://gw.api.animationdigitalnetwork.fr/video/show/${id}?offset=0&limit=-1&order=asc`, {
|
const { data, error } = await useFetch<ADNEpisodesFetch>(`https://gw.api.animationdigitalnetwork.fr/video/show/${id}?offset=0&limit=-1&order=asc`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
"x-target-distribution": lang,
|
'x-target-distribution': lang
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (error.value || !data.value) {
|
if (error.value || !data.value) {
|
||||||
console.log(error.value)
|
console.log(error.value)
|
||||||
alert(error.value)
|
alert(error.value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.value.videos
|
return data.value.videos
|
||||||
}
|
}
|
||||||
|
@ -1,85 +1,85 @@
|
|||||||
export interface ADNSearchFetch {
|
export interface ADNSearchFetch {
|
||||||
shows: Array<{
|
shows: Array<{
|
||||||
id: number
|
id: number
|
||||||
url: string
|
url: string
|
||||||
title: string
|
title: string
|
||||||
image2x: string
|
image2x: string
|
||||||
episodeCount: number,
|
episodeCount: number
|
||||||
languages: Array<string>
|
languages: Array<string>
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ADNEpisodesFetch {
|
export interface ADNEpisodesFetch {
|
||||||
videos: Array<ADNEpisode>
|
videos: Array<ADNEpisode>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ADNEpisode {
|
export interface ADNEpisode {
|
||||||
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: string,
|
releaseDate: string
|
||||||
duration: number,
|
duration: number
|
||||||
url: string,
|
url: string
|
||||||
urlPath: string,
|
urlPath: string
|
||||||
embeddedUrl: string,
|
embeddedUrl: string
|
||||||
languages: Array<string>,
|
languages: Array<string>
|
||||||
qualities: Array<string>,
|
qualities: Array<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: {
|
||||||
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: Array<string>,
|
languages: Array<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: Array<string>,
|
genres: Array<string>
|
||||||
copyright: string,
|
copyright: string
|
||||||
rating: number,
|
rating: number
|
||||||
ratingsCount: number,
|
ratingsCount: number
|
||||||
commentsCount: number,
|
commentsCount: number
|
||||||
qualities: Array<string>,
|
qualities: Array<string>
|
||||||
simulcast: boolean,
|
simulcast: boolean
|
||||||
free: boolean,
|
free: boolean
|
||||||
available: boolean,
|
available: boolean
|
||||||
download: boolean,
|
download: boolean
|
||||||
basedOn: string,
|
basedOn: string
|
||||||
tagline: Array<string>,
|
tagline: Array<string>
|
||||||
firstReleaseYear: string,
|
firstReleaseYear: string
|
||||||
productionStudio: string,
|
productionStudio: string
|
||||||
countryOfOrigin: string,
|
countryOfOrigin: string
|
||||||
productionTeam: Array<{
|
productionTeam: Array<{
|
||||||
role: string,
|
role: string
|
||||||
name: string,
|
name: string
|
||||||
}>,
|
}>
|
||||||
nextVideoReleaseDate: string,
|
nextVideoReleaseDate: string
|
||||||
|
indexable: boolean
|
||||||
|
}
|
||||||
indexable: boolean
|
indexable: boolean
|
||||||
}
|
|
||||||
indexable: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ADNEpisodes extends Array<ADNEpisode> {}
|
export interface ADNEpisodes extends Array<ADNEpisode> {}
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
import type { CrunchyLogin } from './Types'
|
import type { CrunchyLogin } from './Types'
|
||||||
|
|
||||||
export async function crunchyLogin() {
|
export async function crunchyLogin() {
|
||||||
const { data, error } = await useFetch<CrunchyLogin>('http://localhost:8080/api/crunchyroll/login', {
|
const { data, error } = await useFetch<CrunchyLogin>('http://localhost:8080/api/crunchyroll/login', {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
})
|
})
|
||||||
|
|
||||||
return { data, error }
|
return { data, error }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkAccount(service: string) {
|
export async function checkAccount(service: string) {
|
||||||
const { data, error } = await useFetch<CrunchyLogin>(`http://localhost:8080/api/service/check/${service}`, {
|
const { data, error } = await useFetch<CrunchyLogin>(`http://localhost:8080/api/service/check/${service}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
|
|
||||||
return { data, error }
|
return { data, error }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginAccount(user: string, password: string, service: string) {
|
export async function loginAccount(user: string, password: string, service: string) {
|
||||||
const { data, error } = await useFetch<CrunchyLogin>(`http://localhost:8080/api/service/login/${service}`, {
|
const { data, error } = await useFetch<CrunchyLogin>(`http://localhost:8080/api/service/login/${service}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
user: user,
|
user: user,
|
||||||
password: password
|
password: password
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { data, error }
|
return { data, error }
|
||||||
}
|
}
|
||||||
|
@ -3,87 +3,87 @@ import { crunchyLogin } from './Account'
|
|||||||
import type { CrunchyAnimeFetch, CrunchySearchFetch } from './Types'
|
import type { CrunchyAnimeFetch, CrunchySearchFetch } from './Types'
|
||||||
|
|
||||||
export async function searchCrunchy(q: string) {
|
export async function searchCrunchy(q: string) {
|
||||||
const { data: token, error: tokenerror } = await crunchyLogin()
|
const { data: token, error: tokenerror } = await crunchyLogin()
|
||||||
|
|
||||||
if (!token.value) {
|
if (!token.value) {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
const { data, error } = await useFetch<CrunchySearchFetch>(`https://beta-api.crunchyroll.com/content/v2/discover/search`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token.value.access_token}`
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
q: q,
|
|
||||||
n: 100,
|
|
||||||
type: 'series',
|
|
||||||
ratings: false
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (error.value) {
|
const { data, error } = await useFetch<CrunchySearchFetch>(`https://beta-api.crunchyroll.com/content/v2/discover/search`, {
|
||||||
console.error(error.value)
|
method: 'GET',
|
||||||
throw new Error(JSON.stringify(error.value))
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${token.value.access_token}`
|
||||||
|
},
|
||||||
if (!data.value) return
|
query: {
|
||||||
|
q: q,
|
||||||
var results: CrunchyrollSearchResults = []
|
n: 100,
|
||||||
|
type: 'series',
|
||||||
for (const result of data.value.data[0].items) {
|
ratings: false
|
||||||
results.push({
|
}
|
||||||
ID: result.id,
|
|
||||||
Url: `https://www.crunchyroll.com/series/${result.id}/${result.slug_title}`,
|
|
||||||
Title: result.title,
|
|
||||||
Description: result.description,
|
|
||||||
Dubs: result.series_metadata.audio_locales,
|
|
||||||
Subs: result.series_metadata.subtitle_locales,
|
|
||||||
Episodes: result.series_metadata.episode_count,
|
|
||||||
Seasons: result.series_metadata.season_count,
|
|
||||||
PEGI: result.series_metadata.maturity_ratings,
|
|
||||||
Year: result.series_metadata.series_launch_year,
|
|
||||||
Images: result.images
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
if (error.value) {
|
||||||
|
console.error(error.value)
|
||||||
|
throw new Error(JSON.stringify(error.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.value) return
|
||||||
|
|
||||||
|
var results: CrunchyrollSearchResults = []
|
||||||
|
|
||||||
|
for (const result of data.value.data[0].items) {
|
||||||
|
results.push({
|
||||||
|
ID: result.id,
|
||||||
|
Url: `https://www.crunchyroll.com/series/${result.id}/${result.slug_title}`,
|
||||||
|
Title: result.title,
|
||||||
|
Description: result.description,
|
||||||
|
Dubs: result.series_metadata.audio_locales,
|
||||||
|
Subs: result.series_metadata.subtitle_locales,
|
||||||
|
Episodes: result.series_metadata.episode_count,
|
||||||
|
Seasons: result.series_metadata.season_count,
|
||||||
|
PEGI: result.series_metadata.maturity_ratings,
|
||||||
|
Year: result.series_metadata.series_launch_year,
|
||||||
|
Images: result.images
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCRSeries(q: string) {
|
export async function getCRSeries(q: string) {
|
||||||
const { data: token, error: tokenerror } = await crunchyLogin()
|
const { data: token, error: tokenerror } = await crunchyLogin()
|
||||||
|
|
||||||
if (!token.value) {
|
if (!token.value) {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
const { data, error } = await useFetch<CrunchyAnimeFetch>(`https://beta-api.crunchyroll.com/content/v2/cms/series/${q}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token.value.access_token}`
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (error.value) {
|
const { data, error } = await useFetch<CrunchyAnimeFetch>(`https://beta-api.crunchyroll.com/content/v2/cms/series/${q}`, {
|
||||||
console.error(error.value)
|
method: 'GET',
|
||||||
throw new Error(JSON.stringify(error.value))
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${token.value.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (!data.value) return
|
if (error.value) {
|
||||||
|
console.error(error.value)
|
||||||
|
throw new Error(JSON.stringify(error.value))
|
||||||
|
}
|
||||||
|
|
||||||
const anime = data.value.data[0]
|
if (!data.value) return
|
||||||
|
|
||||||
return {
|
const anime = data.value.data[0]
|
||||||
ID: anime.id,
|
|
||||||
Url: `https://www.crunchyroll.com/series/${anime.id}/${anime.slug_title}`,
|
return {
|
||||||
Title: anime.title,
|
ID: anime.id,
|
||||||
Description: anime.description,
|
Url: `https://www.crunchyroll.com/series/${anime.id}/${anime.slug_title}`,
|
||||||
Dubs: anime.audio_locales,
|
Title: anime.title,
|
||||||
Subs: anime.subtitle_locales,
|
Description: anime.description,
|
||||||
Episodes: anime.episode_count,
|
Dubs: anime.audio_locales,
|
||||||
Seasons: anime.season_count,
|
Subs: anime.subtitle_locales,
|
||||||
PEGI: anime.maturity_ratings,
|
Episodes: anime.episode_count,
|
||||||
Year: anime.series_launch_year,
|
Seasons: anime.season_count,
|
||||||
Images: anime.images
|
PEGI: anime.maturity_ratings,
|
||||||
}
|
Year: anime.series_launch_year,
|
||||||
|
Images: anime.images
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,25 +2,25 @@ import { crunchyLogin } from './Account'
|
|||||||
import type { CrunchyEpisodesFetch } from './Types'
|
import type { CrunchyEpisodesFetch } from './Types'
|
||||||
|
|
||||||
export async function listEpisodeCrunchy(q: string) {
|
export async function listEpisodeCrunchy(q: string) {
|
||||||
const { data: token, error: tokenerror } = await crunchyLogin()
|
const { data: token, error: tokenerror } = await crunchyLogin()
|
||||||
|
|
||||||
if (!token.value) {
|
if (!token.value) {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
const { data, error } = await useFetch<CrunchyEpisodesFetch>(`https://beta-api.crunchyroll.com/content/v2/cms/seasons/${q}/episodes`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token.value.access_token}`
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (error.value) {
|
const { data, error } = await useFetch<CrunchyEpisodesFetch>(`https://beta-api.crunchyroll.com/content/v2/cms/seasons/${q}/episodes`, {
|
||||||
console.error(error.value)
|
method: 'GET',
|
||||||
throw new Error(JSON.stringify(error.value))
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${token.value.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (!data.value) return
|
if (error.value) {
|
||||||
|
console.error(error.value)
|
||||||
|
throw new Error(JSON.stringify(error.value))
|
||||||
|
}
|
||||||
|
|
||||||
return data.value.data
|
if (!data.value) return
|
||||||
|
|
||||||
|
return data.value.data
|
||||||
}
|
}
|
||||||
|
@ -2,27 +2,27 @@ import { crunchyLogin } from './Account'
|
|||||||
import type { CrunchySeasonsFetch } from './Types'
|
import type { CrunchySeasonsFetch } from './Types'
|
||||||
|
|
||||||
export async function listSeasonCrunchy(q: string) {
|
export async function listSeasonCrunchy(q: string) {
|
||||||
const { data: token, error: tokenerror } = await crunchyLogin()
|
const { data: token, error: tokenerror } = await crunchyLogin()
|
||||||
|
|
||||||
if (!token.value) {
|
if (!token.value) {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
console.log(q)
|
|
||||||
|
|
||||||
const { data, error } = await useFetch<CrunchySeasonsFetch>(`https://beta-api.crunchyroll.com/content/v2/cms/series/${q}/seasons`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token.value.access_token}`
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (error.value) {
|
console.log(q)
|
||||||
console.error(error.value)
|
|
||||||
throw new Error(JSON.stringify(error.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.value) return
|
const { data, error } = await useFetch<CrunchySeasonsFetch>(`https://beta-api.crunchyroll.com/content/v2/cms/series/${q}/seasons`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token.value.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return data.value.data
|
if (error.value) {
|
||||||
|
console.error(error.value)
|
||||||
|
throw new Error(JSON.stringify(error.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.value) return
|
||||||
|
|
||||||
|
return data.value.data
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,73 @@
|
|||||||
export interface CrunchySearchFetch {
|
export interface CrunchySearchFetch {
|
||||||
total: number
|
total: number
|
||||||
data: Array<{
|
data: Array<{
|
||||||
type: string
|
type: string
|
||||||
count: number
|
count: number
|
||||||
items: Array<{
|
items: Array<{
|
||||||
promo_description: string
|
promo_description: string
|
||||||
title: string
|
title: string
|
||||||
promo_title: string
|
promo_title: string
|
||||||
channel_id: string
|
channel_id: string
|
||||||
slug_title: string
|
slug_title: string
|
||||||
search_metadata: {
|
search_metadata: {
|
||||||
score: number
|
score: number
|
||||||
}
|
}
|
||||||
series_metadata: {
|
series_metadata: {
|
||||||
|
audio_locales: Array<string>
|
||||||
|
availability_notes: string
|
||||||
|
episode_count: number
|
||||||
|
extended_description: string
|
||||||
|
extended_maturity_rating: string
|
||||||
|
is_dubbed: boolean
|
||||||
|
is_mature: boolean
|
||||||
|
is_simulcast: boolean
|
||||||
|
is_subbed: boolean
|
||||||
|
mature_blocked: boolean
|
||||||
|
maturity_ratings: Array<string>
|
||||||
|
season_count: number
|
||||||
|
series_launch_year: number
|
||||||
|
subtitle_locales: Array<string>
|
||||||
|
}
|
||||||
|
id: string
|
||||||
|
slug: string
|
||||||
|
external_id: string
|
||||||
|
description: string
|
||||||
|
new: boolean
|
||||||
|
images: {
|
||||||
|
poster_tall: Array<
|
||||||
|
Array<{
|
||||||
|
height: number
|
||||||
|
source: string
|
||||||
|
type: string
|
||||||
|
width: number
|
||||||
|
}>
|
||||||
|
>
|
||||||
|
poster_wide: Array<
|
||||||
|
Array<{
|
||||||
|
height: number
|
||||||
|
source: string
|
||||||
|
type: string
|
||||||
|
width: number
|
||||||
|
}>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
linked_resource_key: string
|
||||||
|
type: string
|
||||||
|
}>
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CrunchyAnimeFetch {
|
||||||
|
total: number
|
||||||
|
data: Array<{
|
||||||
|
promo_description: string
|
||||||
|
title: string
|
||||||
|
promo_title: string
|
||||||
|
channel_id: string
|
||||||
|
slug_title: string
|
||||||
|
search_metadata: {
|
||||||
|
score: number
|
||||||
|
}
|
||||||
audio_locales: Array<string>
|
audio_locales: Array<string>
|
||||||
availability_notes: string
|
availability_notes: string
|
||||||
episode_count: number
|
episode_count: number
|
||||||
@ -27,212 +82,157 @@ export interface CrunchySearchFetch {
|
|||||||
season_count: number
|
season_count: number
|
||||||
series_launch_year: number
|
series_launch_year: number
|
||||||
subtitle_locales: Array<string>
|
subtitle_locales: Array<string>
|
||||||
}
|
id: string
|
||||||
id: string
|
slug: string
|
||||||
slug: string
|
external_id: string
|
||||||
external_id: string
|
description: string
|
||||||
description: string
|
new: boolean
|
||||||
new: boolean
|
images: {
|
||||||
images: {
|
poster_tall: Array<
|
||||||
poster_tall: Array<
|
Array<{
|
||||||
Array<{
|
height: number
|
||||||
height: number
|
source: string
|
||||||
source: string
|
type: string
|
||||||
type: string
|
width: number
|
||||||
width: number
|
}>
|
||||||
}>
|
>
|
||||||
>
|
poster_wide: Array<
|
||||||
poster_wide: Array<
|
Array<{
|
||||||
Array<{
|
height: number
|
||||||
height: number
|
source: string
|
||||||
source: string
|
type: string
|
||||||
type: string
|
width: number
|
||||||
width: number
|
}>
|
||||||
}>
|
>
|
||||||
>
|
}
|
||||||
}
|
linked_resource_key: string
|
||||||
linked_resource_key: string
|
type: string
|
||||||
type: string
|
|
||||||
}>
|
}>
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CrunchyAnimeFetch {
|
|
||||||
total: number
|
|
||||||
data: Array<{
|
|
||||||
promo_description: string
|
|
||||||
title: string
|
|
||||||
promo_title: string
|
|
||||||
channel_id: string
|
|
||||||
slug_title: string
|
|
||||||
search_metadata: {
|
|
||||||
score: number
|
|
||||||
}
|
|
||||||
audio_locales: Array<string>
|
|
||||||
availability_notes: string
|
|
||||||
episode_count: number
|
|
||||||
extended_description: string
|
|
||||||
extended_maturity_rating: string
|
|
||||||
is_dubbed: boolean
|
|
||||||
is_mature: boolean
|
|
||||||
is_simulcast: boolean
|
|
||||||
is_subbed: boolean
|
|
||||||
mature_blocked: boolean
|
|
||||||
maturity_ratings: Array<string>
|
|
||||||
season_count: number
|
|
||||||
series_launch_year: number
|
|
||||||
subtitle_locales: Array<string>
|
|
||||||
id: string
|
|
||||||
slug: string
|
|
||||||
external_id: string
|
|
||||||
description: string
|
|
||||||
new: boolean
|
|
||||||
images: {
|
|
||||||
poster_tall: Array<
|
|
||||||
Array<{
|
|
||||||
height: number
|
|
||||||
source: string
|
|
||||||
type: string
|
|
||||||
width: number
|
|
||||||
}>
|
|
||||||
>
|
|
||||||
poster_wide: Array<
|
|
||||||
Array<{
|
|
||||||
height: number
|
|
||||||
source: string
|
|
||||||
type: string
|
|
||||||
width: number
|
|
||||||
}>
|
|
||||||
>
|
|
||||||
}
|
|
||||||
linked_resource_key: string
|
|
||||||
type: string
|
|
||||||
}>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchyLogin {
|
export interface CrunchyLogin {
|
||||||
access_token: string
|
access_token: string
|
||||||
refresh_token: string
|
refresh_token: string
|
||||||
expires_in: number
|
expires_in: number
|
||||||
token_type: string
|
token_type: string
|
||||||
scope: string
|
scope: string
|
||||||
country: string
|
country: string
|
||||||
account_id: string
|
account_id: string
|
||||||
profile_id: string
|
profile_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchySeasonsFetch {
|
export interface CrunchySeasonsFetch {
|
||||||
total: number
|
total: number
|
||||||
data: Array<{
|
data: Array<{
|
||||||
identifier: string
|
identifier: string
|
||||||
description: string
|
description: string
|
||||||
is_simulcast: boolean
|
is_simulcast: boolean
|
||||||
subtitle_locales: Array<string>
|
subtitle_locales: Array<string>
|
||||||
series_id: string
|
series_id: string
|
||||||
id: string
|
id: string
|
||||||
audio_locales: Array<string>
|
audio_locales: Array<string>
|
||||||
title: string
|
title: string
|
||||||
versions: Array<{
|
versions: Array<{
|
||||||
audio_locale: string
|
audio_locale: string
|
||||||
guid: string
|
guid: string
|
||||||
original: boolean
|
original: boolean
|
||||||
variant: string
|
variant: string
|
||||||
|
}>
|
||||||
|
season_sequence_number: number
|
||||||
|
season_number: number
|
||||||
|
maturity_ratings: Array<string>
|
||||||
|
mature_blocked: boolean
|
||||||
|
channel_id: string
|
||||||
|
is_subbed: boolean
|
||||||
|
audio_locale: string
|
||||||
|
season_display_number: string
|
||||||
|
is_complete: boolean
|
||||||
|
season_tags: Array<string>
|
||||||
|
is_mature: boolean
|
||||||
|
is_dubbed: boolean
|
||||||
|
slug_title: string
|
||||||
|
availability_notes: string
|
||||||
|
number_of_episodes: boolean
|
||||||
}>
|
}>
|
||||||
season_sequence_number: number
|
meta: {
|
||||||
season_number: number
|
versions_considered: boolean
|
||||||
maturity_ratings: Array<string>
|
}
|
||||||
mature_blocked: boolean
|
|
||||||
channel_id: string
|
|
||||||
is_subbed: boolean
|
|
||||||
audio_locale: string
|
|
||||||
season_display_number: string
|
|
||||||
is_complete: boolean
|
|
||||||
season_tags: Array<string>
|
|
||||||
is_mature: boolean
|
|
||||||
is_dubbed: boolean
|
|
||||||
slug_title: string
|
|
||||||
availability_notes: string
|
|
||||||
number_of_episodes: boolean
|
|
||||||
}>
|
|
||||||
meta: {
|
|
||||||
versions_considered: boolean
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchyEpisodesFetch {
|
export interface CrunchyEpisodesFetch {
|
||||||
total: number
|
total: number
|
||||||
data: Array<{
|
data: Array<{
|
||||||
closed_captions_available: boolean
|
closed_captions_available: boolean
|
||||||
availability_notes: string
|
availability_notes: string
|
||||||
next_episode_title: string
|
next_episode_title: string
|
||||||
upload_date: string
|
upload_date: string
|
||||||
versions: Array<{
|
versions: Array<{
|
||||||
audio_locale: string
|
audio_locale: string
|
||||||
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
|
||||||
}>
|
|
||||||
season_slug_title: string
|
|
||||||
series_title: string
|
|
||||||
season_title: string
|
|
||||||
sequence_number: number
|
|
||||||
maturity_ratings: Array<string>
|
|
||||||
slug_title: string
|
|
||||||
is_premium_only: boolean
|
|
||||||
availability_ends: string
|
|
||||||
identifier: string
|
|
||||||
recent_variant: string
|
|
||||||
free_available_date: string
|
|
||||||
subtitle_locales: Array<string>
|
|
||||||
series_id: string
|
|
||||||
mature_blocked: boolean
|
|
||||||
duration_ms: number
|
|
||||||
availability_starts: string
|
|
||||||
audio_locale: string
|
|
||||||
images: {
|
|
||||||
thumbnail: Array<
|
|
||||||
Array<{
|
|
||||||
height: number
|
|
||||||
source: string
|
|
||||||
type: string
|
|
||||||
width: number
|
|
||||||
}>
|
}>
|
||||||
>
|
season_slug_title: string
|
||||||
|
series_title: string
|
||||||
|
season_title: string
|
||||||
|
sequence_number: number
|
||||||
|
maturity_ratings: Array<string>
|
||||||
|
slug_title: string
|
||||||
|
is_premium_only: boolean
|
||||||
|
availability_ends: string
|
||||||
|
identifier: string
|
||||||
|
recent_variant: string
|
||||||
|
free_available_date: string
|
||||||
|
subtitle_locales: Array<string>
|
||||||
|
series_id: string
|
||||||
|
mature_blocked: boolean
|
||||||
|
duration_ms: number
|
||||||
|
availability_starts: string
|
||||||
|
audio_locale: string
|
||||||
|
images: {
|
||||||
|
thumbnail: Array<
|
||||||
|
Array<{
|
||||||
|
height: number
|
||||||
|
source: string
|
||||||
|
type: string
|
||||||
|
width: number
|
||||||
|
}>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
season_sequence_number: number
|
||||||
|
season_id: string
|
||||||
|
episode_number: number
|
||||||
|
listing_id: string
|
||||||
|
available_date: string
|
||||||
|
channel_id: string
|
||||||
|
season_number: number
|
||||||
|
hd_flag: boolean
|
||||||
|
recent_audio_locale: string
|
||||||
|
available_offline: boolean
|
||||||
|
episode: string
|
||||||
|
is_subbed: boolean
|
||||||
|
media_type: string
|
||||||
|
is_clip: boolean
|
||||||
|
title: string
|
||||||
|
streams_link: string
|
||||||
|
slug: string
|
||||||
|
id: string
|
||||||
|
production_episode_id: string
|
||||||
|
is_dubbed: boolean
|
||||||
|
next_episode_id: string
|
||||||
|
series_slug_title: string
|
||||||
|
season_tags: Array<string>
|
||||||
|
premium_date: string
|
||||||
|
is_mature: boolean
|
||||||
|
premium_available_date: string
|
||||||
|
description: string
|
||||||
|
episode_air_date: string
|
||||||
|
eligible_region: string
|
||||||
|
}>
|
||||||
|
meta: {
|
||||||
|
versions_considered: boolean
|
||||||
}
|
}
|
||||||
season_sequence_number: number
|
|
||||||
season_id: string
|
|
||||||
episode_number: number
|
|
||||||
listing_id: string
|
|
||||||
available_date: string
|
|
||||||
channel_id: string
|
|
||||||
season_number: number
|
|
||||||
hd_flag: boolean
|
|
||||||
recent_audio_locale: string
|
|
||||||
available_offline: boolean
|
|
||||||
episode: string
|
|
||||||
is_subbed: boolean
|
|
||||||
media_type: string
|
|
||||||
is_clip: boolean
|
|
||||||
title: string
|
|
||||||
streams_link: string
|
|
||||||
slug: string
|
|
||||||
id: string
|
|
||||||
production_episode_id: string
|
|
||||||
is_dubbed: boolean
|
|
||||||
next_episode_id: string
|
|
||||||
series_slug_title: string
|
|
||||||
season_tags: Array<string>
|
|
||||||
premium_date: string
|
|
||||||
is_mature: boolean
|
|
||||||
premium_available_date: string
|
|
||||||
description: string
|
|
||||||
episode_air_date: string
|
|
||||||
eligible_region: string
|
|
||||||
}>
|
|
||||||
meta: {
|
|
||||||
versions_considered: boolean
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,73 @@
|
|||||||
export interface CrunchyEpisode {
|
export interface CrunchyEpisode {
|
||||||
closed_captions_available: boolean,
|
closed_captions_available: boolean
|
||||||
availability_notes: string,
|
availability_notes: string
|
||||||
next_episode_title: string,
|
next_episode_title: string
|
||||||
upload_date: string,
|
upload_date: string
|
||||||
versions: Array<{
|
versions: Array<{
|
||||||
audio_locale: string,
|
audio_locale: string
|
||||||
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
|
||||||
}>,
|
}>
|
||||||
season_slug_title: string,
|
season_slug_title: string
|
||||||
series_title: string,
|
series_title: string
|
||||||
season_title: string,
|
season_title: string
|
||||||
sequence_number: number,
|
sequence_number: number
|
||||||
maturity_ratings: Array<string>,
|
maturity_ratings: Array<string>
|
||||||
slug_title: string,
|
slug_title: string
|
||||||
is_premium_only: boolean,
|
is_premium_only: boolean
|
||||||
availability_ends: string,
|
availability_ends: string
|
||||||
identifier: string,
|
identifier: string
|
||||||
recent_variant: string,
|
recent_variant: string
|
||||||
free_available_date: string,
|
free_available_date: string
|
||||||
subtitle_locales: Array<string>,
|
subtitle_locales: Array<string>
|
||||||
series_id: string,
|
series_id: string
|
||||||
mature_blocked: boolean,
|
mature_blocked: boolean
|
||||||
duration_ms: number,
|
duration_ms: number
|
||||||
availability_starts: string,
|
availability_starts: string
|
||||||
audio_locale: string,
|
audio_locale: string
|
||||||
images: {
|
images: {
|
||||||
thumbnail: Array<Array<{
|
thumbnail: Array<
|
||||||
height: number,
|
Array<{
|
||||||
source: string,
|
height: number
|
||||||
type: string,
|
source: string
|
||||||
width: number
|
type: string
|
||||||
}>>
|
width: number
|
||||||
},
|
}>
|
||||||
season_sequence_number: number,
|
>
|
||||||
season_id: string,
|
}
|
||||||
episode_number: number,
|
season_sequence_number: number
|
||||||
listing_id: string,
|
season_id: string
|
||||||
available_date: string,
|
episode_number: number
|
||||||
channel_id: string,
|
listing_id: string
|
||||||
season_number: number,
|
available_date: string
|
||||||
hd_flag: boolean,
|
channel_id: string
|
||||||
recent_audio_locale: string,
|
season_number: number
|
||||||
available_offline: boolean,
|
hd_flag: boolean
|
||||||
episode: string,
|
recent_audio_locale: string
|
||||||
is_subbed: boolean,
|
available_offline: boolean
|
||||||
media_type: string,
|
episode: string
|
||||||
is_clip: boolean,
|
is_subbed: boolean
|
||||||
title: string,
|
media_type: string
|
||||||
streams_link: string,
|
is_clip: boolean
|
||||||
slug: string,
|
title: string
|
||||||
id: string,
|
streams_link: string
|
||||||
production_episode_id: string,
|
slug: string
|
||||||
is_dubbed: boolean,
|
id: string
|
||||||
next_episode_id: string,
|
production_episode_id: string
|
||||||
series_slug_title: string,
|
is_dubbed: boolean
|
||||||
season_tags: Array<string>,
|
next_episode_id: string
|
||||||
premium_date: string,
|
series_slug_title: string
|
||||||
is_mature: boolean,
|
season_tags: Array<string>
|
||||||
premium_available_date: string,
|
premium_date: string
|
||||||
description: string,
|
is_mature: boolean
|
||||||
episode_air_date: string,
|
premium_available_date: string
|
||||||
|
description: string
|
||||||
|
episode_air_date: string
|
||||||
eligible_region: string
|
eligible_region: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchyEpisodes extends Array<CrunchyEpisode> {}
|
export interface CrunchyEpisodes extends Array<CrunchyEpisode> {}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
const isProduction = process.env.NODE_ENV !== 'development'
|
const isProduction = process.env.NODE_ENV !== 'development'
|
||||||
|
|
||||||
export function openNewWindow(urlprod: string, urldev: string, w: string) {
|
export function openNewWindow(urlprod: string, urldev: string, w: string) {
|
||||||
const newWindow = window.open(isProduction ? urlprod : urldev, '_blank', w)
|
const newWindow = window.open(isProduction ? urlprod : urldev, '_blank', w)
|
||||||
if (newWindow) {
|
if (newWindow) {
|
||||||
newWindow.focus()
|
newWindow.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fixed w-full flex flex-row px-2 bg-[#11111189] h-14 z-10 gap-1" style="-webkit-app-region: drag">
|
<div class="fixed w-full flex flex-row px-2 bg-[#11111189] h-14 z-10 gap-1" style="-webkit-app-region: drag">
|
||||||
<div class="w-full flex gap-10 flex-row items-center justify-center px-5">
|
<div class="w-full flex gap-10 flex-row items-center justify-center px-5">
|
||||||
<button @click="openAddAnime" class="flex items-center justify-center px-2 py-2 gap-1 transition-all bg-[#ffffff16] hover:bg-[#ffffff25] rounded-lg select-none" style="-webkit-app-region: no-drag">
|
<button
|
||||||
<Icon name="ph:plus-bold" class="h-3.5 w-3.5 text-white" />
|
@click="openAddAnime"
|
||||||
<div class="text-[11px] text-white font-dm">
|
class="flex items-center justify-center px-2 py-2 gap-1 transition-all bg-[#ffffff16] hover:bg-[#ffffff25] rounded-lg select-none"
|
||||||
ADD DOWNLOAD
|
style="-webkit-app-region: no-drag"
|
||||||
|
>
|
||||||
|
<Icon name="ph:plus-bold" class="h-3.5 w-3.5 text-white" />
|
||||||
|
<div class="text-[11px] text-white font-dm"> ADD DOWNLOAD </div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
<div class="w-full flex flex-row items-center justify-center">
|
||||||
</div>
|
<img src="/logo.png" class="h-8" />
|
||||||
<div class="w-full flex flex-row items-center justify-center">
|
<div class="text-[10px] leading-[10px] text-opacity-90 text-white select-none"
|
||||||
<img src="/logo.png" class="h-8">
|
>Crunchyroll <br />
|
||||||
<div class="text-[10px] leading-[10px] text-opacity-90 text-white select-none">Crunchyroll <br> Downloader</div>
|
Downloader</div
|
||||||
</div>
|
>
|
||||||
<div class="w-full flex gap-2 flex-row items-center justify-center">
|
</div>
|
||||||
<!-- <button class="flex items-center justify-center px-2 py-2 gap-1 transition-all bg-[#ffffff16] hover:bg-[#ffffff25] rounded-lg select-none" style="-webkit-app-region: no-drag">
|
<div class="w-full flex gap-2 flex-row items-center justify-center">
|
||||||
|
<!-- <button class="flex items-center justify-center px-2 py-2 gap-1 transition-all bg-[#ffffff16] hover:bg-[#ffffff25] rounded-lg select-none" style="-webkit-app-region: no-drag">
|
||||||
<Icon name="iconamoon:playlist" class="h-3.5 w-3.5 text-white" />
|
<Icon name="iconamoon:playlist" class="h-3.5 w-3.5 text-white" />
|
||||||
<div class="text-[11px] text-white font-dm">
|
<div class="text-[11px] text-white font-dm">
|
||||||
PLAYLIST
|
PLAYLIST
|
||||||
</div>
|
</div>
|
||||||
</button> -->
|
</button> -->
|
||||||
<button @click="openSettings" class="flex items-center justify-center px-2 py-2 gap-1 transition-all bg-[#ffffff16] hover:bg-[#ffffff25] rounded-lg select-none" style="-webkit-app-region: no-drag">
|
<button
|
||||||
<Icon name="ic:round-settings" class="h-3.5 w-3.5 text-white" />
|
@click="openSettings"
|
||||||
<div class="text-[11px] text-white font-dm">
|
class="flex items-center justify-center px-2 py-2 gap-1 transition-all bg-[#ffffff16] hover:bg-[#ffffff25] rounded-lg select-none"
|
||||||
SETTINGS
|
style="-webkit-app-region: no-drag"
|
||||||
|
>
|
||||||
|
<Icon name="ic:round-settings" class="h-3.5 w-3.5 text-white" />
|
||||||
|
<div class="text-[11px] text-white font-dm"> SETTINGS </div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -36,35 +43,35 @@ import { openNewWindow } from './Functions/WindowHandler'
|
|||||||
const isProduction = process.env.NODE_ENV !== 'development'
|
const isProduction = process.env.NODE_ENV !== 'development'
|
||||||
|
|
||||||
async function openSettings() {
|
async function openSettings() {
|
||||||
(window as any).myAPI.openWindow({
|
;(window as any).myAPI.openWindow({
|
||||||
title: "Settings",
|
title: 'Settings',
|
||||||
url: isProduction ? 'http://localhost:8079/settings' : 'http://localhost:3000/settings',
|
url: isProduction ? 'http://localhost:8079/settings' : 'http://localhost:3000/settings',
|
||||||
width: 600,
|
width: 600,
|
||||||
height: 700,
|
height: 700,
|
||||||
backgroundColor: "#111111"
|
backgroundColor: '#111111'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openAddAnime() {
|
async function openAddAnime() {
|
||||||
// const { data, error } = await checkAccount()
|
// const { data, error } = await checkAccount()
|
||||||
|
|
||||||
// if (error.value) {
|
// if (error.value) {
|
||||||
// (window as any).myAPI.openWindow({
|
// (window as any).myAPI.openWindow({
|
||||||
// title: "Crunchyroll Login",
|
// title: "Crunchyroll Login",
|
||||||
// url: isProduction ? 'http://localhost:8079/crunchylogin' : 'http://localhost:3000/crunchylogin',
|
// url: isProduction ? 'http://localhost:8079/crunchylogin' : 'http://localhost:3000/crunchylogin',
|
||||||
// width: 600,
|
// width: 600,
|
||||||
// height: 300,
|
// height: 300,
|
||||||
// backgroundColor: "#111111"
|
// backgroundColor: "#111111"
|
||||||
// })
|
// })
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
(window as any).myAPI.openWindow({
|
;(window as any).myAPI.openWindow({
|
||||||
title: "Add Anime",
|
title: 'Add Anime',
|
||||||
url: isProduction ? 'http://localhost:8079/addanime' : 'http://localhost:3000/addanime',
|
url: isProduction ? 'http://localhost:8079/addanime' : 'http://localhost:3000/addanime',
|
||||||
width: 700,
|
width: 700,
|
||||||
height: 450,
|
height: 450,
|
||||||
backgroundColor: "#111111"
|
backgroundColor: '#111111'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
export interface CrunchyrollSearchResult {
|
export interface CrunchyrollSearchResult {
|
||||||
ID: string
|
ID: string
|
||||||
Url: string
|
Url: string
|
||||||
Title: string
|
Title: string
|
||||||
Description: string
|
Description: string
|
||||||
Dubs: Array<string>
|
Dubs: Array<string>
|
||||||
Subs: Array<string>
|
Subs: Array<string>
|
||||||
Episodes: number
|
Episodes: number
|
||||||
Seasons: number
|
Seasons: number
|
||||||
PEGI: Array<string>
|
PEGI: Array<string>
|
||||||
Year: number
|
Year: number
|
||||||
Images: {
|
Images: {
|
||||||
poster_tall: Array<
|
poster_tall: Array<
|
||||||
Array<{
|
Array<{
|
||||||
height: number
|
height: number
|
||||||
source: string
|
source: string
|
||||||
type: string
|
type: string
|
||||||
width: number
|
width: number
|
||||||
}>
|
}>
|
||||||
>
|
>
|
||||||
poster_wide: Array<
|
poster_wide: Array<
|
||||||
Array<{
|
Array<{
|
||||||
height: number
|
height: number
|
||||||
source: string
|
source: string
|
||||||
type: string
|
type: string
|
||||||
width: number
|
width: number
|
||||||
}>
|
}>
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchyrollSearchResults extends Array<CrunchyrollSearchResult> {}
|
export interface CrunchyrollSearchResults extends Array<CrunchyrollSearchResult> {}
|
||||||
|
|
||||||
export interface ADNSearchResult {
|
export interface ADNSearchResult {
|
||||||
id: number
|
id: number
|
||||||
url: string
|
url: string
|
||||||
title: string
|
title: string
|
||||||
image2x: string
|
image2x: string
|
||||||
episodeCount: number
|
episodeCount: number
|
||||||
languages: Array<string>
|
languages: Array<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ADNSearchResults extends Array<ADNSearchResult> {}
|
export interface ADNSearchResults extends Array<ADNSearchResult> {}
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
export interface CrunchySeason {
|
export interface CrunchySeason {
|
||||||
identifier: string
|
identifier: string
|
||||||
description: string
|
description: string
|
||||||
is_simulcast: boolean
|
is_simulcast: boolean
|
||||||
subtitle_locales: Array<string>
|
subtitle_locales: Array<string>
|
||||||
series_id: string
|
series_id: string
|
||||||
id: string
|
id: string
|
||||||
audio_locales: Array<string>
|
audio_locales: Array<string>
|
||||||
title: string
|
title: string
|
||||||
versions: Array<{
|
versions: Array<{
|
||||||
|
audio_locale: string
|
||||||
|
guid: string
|
||||||
|
original: boolean
|
||||||
|
variant: string
|
||||||
|
}>
|
||||||
|
season_sequence_number: number
|
||||||
|
season_number: number
|
||||||
|
maturity_ratings: Array<string>
|
||||||
|
mature_blocked: boolean
|
||||||
|
channel_id: string
|
||||||
|
is_subbed: boolean
|
||||||
audio_locale: string
|
audio_locale: string
|
||||||
guid: string
|
season_display_number: string
|
||||||
original: boolean
|
is_complete: boolean
|
||||||
variant: string
|
season_tags: Array<string>
|
||||||
}>
|
is_mature: boolean
|
||||||
season_sequence_number: number
|
is_dubbed: boolean
|
||||||
season_number: number
|
slug_title: string
|
||||||
maturity_ratings: Array<string>
|
availability_notes: string
|
||||||
mature_blocked: boolean
|
number_of_episodes: boolean
|
||||||
channel_id: string
|
|
||||||
is_subbed: boolean
|
|
||||||
audio_locale: string
|
|
||||||
season_display_number: string
|
|
||||||
is_complete: boolean
|
|
||||||
season_tags: Array<string>
|
|
||||||
is_mature: boolean
|
|
||||||
is_dubbed: boolean
|
|
||||||
slug_title: string
|
|
||||||
availability_notes: string
|
|
||||||
number_of_episodes: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchySeasons extends Array<CrunchySeason> {}
|
export interface CrunchySeasons extends Array<CrunchySeason> {}
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="fixed bottom-3 right-5 p-3 flex flex-col bg-[#111111dc] w-80 min-h-24 rounded-xl font-dm text-white transition-all duration-300"
|
class="fixed bottom-3 right-5 p-3 flex flex-col bg-[#111111dc] w-80 min-h-24 rounded-xl font-dm text-white transition-all duration-300"
|
||||||
:class="data?.status === 'update-available' && !ignoreUpdate || data?.status === 'downloading' || data?.status === 'update-downloaded' ? 'opacity-100' : 'opacity-0 pointer-events-none'"
|
:class="
|
||||||
>
|
(data?.status === 'update-available' && !ignoreUpdate) || data?.status === 'downloading' || data?.status === 'update-downloaded'
|
||||||
<button @click="ignoreUpdate = true" class="absolute right-3 top-2">
|
? 'opacity-100'
|
||||||
<Icon name="akar-icons:cross" class="h-4 w-4 text-white" />
|
: 'opacity-0 pointer-events-none'
|
||||||
</button>
|
"
|
||||||
<div class="text-base text-center"> Update available </div>
|
>
|
||||||
<div class="text-sm text-center"> A new update is available </div>
|
<button @click="ignoreUpdate = true" class="absolute right-3 top-2">
|
||||||
<div v-if="data && data.info && data.info.version" class="text-sm text-center"> v{{ data.info.version }} </div>
|
<Icon name="akar-icons:cross" class="h-4 w-4 text-white" />
|
||||||
<button @click="startDownload" v-if="data && data.status === 'update-available'" class="text-sm py-3 bg-[#363434] mt-5 rounded-xl" :disabled="downloading">
|
</button>
|
||||||
Download Update
|
<div class="text-base text-center"> Update available </div>
|
||||||
</button>
|
<div class="text-sm text-center"> A new update is available </div>
|
||||||
<button v-if="data && data.status === 'downloading'" class="relative text-sm py-3 bg-[#363434] mt-5 rounded-xl overflow-hidden">
|
<div v-if="data && data.info && data.info.version" class="text-sm text-center"> v{{ data.info.version }} </div>
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-[#a1a1a141] transition-all duration-300" :style="`width: calc((${data.info.percent} / 100) * 100%);`"></div>
|
<button @click="startDownload" v-if="data && data.status === 'update-available'" class="text-sm py-3 bg-[#363434] mt-5 rounded-xl" :disabled="downloading">
|
||||||
Downloading...
|
Download Update
|
||||||
</button>
|
</button>
|
||||||
<button @click="startInstall" v-if="data && data.status === 'update-downloaded'" class="text-sm py-3 bg-[#363434] mt-5 rounded-xl" :disabled="installing">
|
<button v-if="data && data.status === 'downloading'" class="relative text-sm py-3 bg-[#363434] mt-5 rounded-xl overflow-hidden">
|
||||||
Install Update
|
<div class="absolute top-0 left-0 w-full h-full bg-[#a1a1a141] transition-all duration-300" :style="`width: calc((${data.info.percent} / 100) * 100%);`"></div>
|
||||||
</button>
|
Downloading...
|
||||||
</div>
|
</button>
|
||||||
|
<button @click="startInstall" v-if="data && data.status === 'update-downloaded'" class="text-sm py-3 bg-[#363434] mt-5 rounded-xl" :disabled="installing">
|
||||||
|
Install Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -29,25 +33,25 @@ const installing = ref<boolean>(false)
|
|||||||
const ignoreUpdate = ref<boolean>(false)
|
const ignoreUpdate = ref<boolean>(false)
|
||||||
|
|
||||||
const checkUpdate = () => {
|
const checkUpdate = () => {
|
||||||
;(window as any).myAPI.getUpdateStatus().then((result: any) => {
|
;(window as any).myAPI.getUpdateStatus().then((result: any) => {
|
||||||
data.value = result
|
data.value = result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const startDownload = () => {
|
const startDownload = () => {
|
||||||
downloading.value = true
|
downloading.value = true
|
||||||
;(window as any).myAPI.startUpdateDownload()
|
;(window as any).myAPI.startUpdateDownload()
|
||||||
}
|
}
|
||||||
|
|
||||||
const startInstall = () => {
|
const startInstall = () => {
|
||||||
installing.value = true
|
installing.value = true
|
||||||
;(window as any).myAPI.startUpdateInstall()
|
;(window as any).myAPI.startUpdateInstall()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
|
|
||||||
setInterval(checkUpdate, 2000)
|
setInterval(checkUpdate, 2000)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
130
package.json
130
package.json
@ -1,67 +1,67 @@
|
|||||||
{
|
{
|
||||||
"name": "crunchyroll-downloader",
|
"name": "crunchyroll-downloader",
|
||||||
"author": "Stratum",
|
"author": "Stratum",
|
||||||
"description": "Crunchyroll Downloader",
|
"description": "Crunchyroll Downloader",
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": ".output/src/electron/background.js",
|
"main": ".output/src/electron/background.js",
|
||||||
"repository": "https://github.com/stratuma/Crunchyroll-Downloader-v4.0",
|
"repository": "https://github.com/stratuma/Crunchyroll-Downloader-v4.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt dev -o",
|
"dev": "nuxt dev -o",
|
||||||
"build": "nuxt generate",
|
"build": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare && electron-builder install-app-deps",
|
"postinstall": "nuxt prepare && electron-builder install-app-deps",
|
||||||
"transpile-src": "tsc -p ./src --outDir .output/src",
|
"transpile-src": "tsc -p ./src --outDir .output/src",
|
||||||
"dev:electron": "NODE_ENV=development concurrently --kill-others \"nuxt dev\" \"tsc-watch -p ./src --outDir .output/src --onSuccess 'electron ./.output/src/electron/background.js'\"",
|
"dev:electron": "NODE_ENV=development concurrently --kill-others \"nuxt dev\" \"tsc-watch -p ./src --outDir .output/src --onSuccess 'electron ./.output/src/electron/background.js'\"",
|
||||||
"dev:electron:win": "set NODE_ENV=development& concurrently --kill-others \"nuxt dev\" \"tsc-watch -p ./src --outDir .output/src --onSuccess run.electron\"",
|
"dev:electron:win": "set NODE_ENV=development& concurrently --kill-others \"nuxt dev\" \"tsc-watch -p ./src --outDir .output/src --onSuccess run.electron\"",
|
||||||
"build:electron": "pnpm build && pnpm transpile-src && node build.js"
|
"build:electron": "pnpm build && pnpm transpile-src && node build.ts",
|
||||||
},
|
"prettier:fix": "pnpm prettier src --write && pnpm prettier components --write && pnpm prettier pages --write && pnpm prettier build.ts --write"
|
||||||
"devDependencies": {
|
},
|
||||||
"7zip-bin": "^5.2.0",
|
"devDependencies": {
|
||||||
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
"7zip-bin": "^5.2.0",
|
||||||
"@nuxtjs/google-fonts": "^3.2.0",
|
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
||||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
"@nuxtjs/google-fonts": "^3.2.0",
|
||||||
"@pinia/nuxt": "^0.4.11",
|
"@nuxtjs/tailwindcss": "^6.12.0",
|
||||||
"@types/express": "^4.17.21",
|
"@pinia/nuxt": "^0.4.11",
|
||||||
"concurrently": "^8.2.2",
|
"@types/express": "^4.17.21",
|
||||||
"dotenv": "^16.4.5",
|
"concurrently": "^8.2.2",
|
||||||
"electron": "^30.0.1",
|
"dotenv": "^16.4.5",
|
||||||
"electron-builder": "^24.13.3",
|
"electron": "^30.0.1",
|
||||||
"eslint": "^8.57.0",
|
"electron-builder": "^24.13.3",
|
||||||
"eslint-config-prettier": "^8.10.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"nuxt": "^3.11.2",
|
"nuxt": "^3.11.2",
|
||||||
"nuxt-icon": "^0.6.10",
|
"nuxt-icon": "^0.6.10",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^3.2.5",
|
||||||
"sass": "^1.75.0",
|
"sass": "^1.75.0",
|
||||||
"sass-loader": "^13.3.3",
|
"sass-loader": "^13.3.3",
|
||||||
"tsc-watch": "^6.2.0",
|
"tsc-watch": "^6.2.0",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"wait-on": "^7.2.0"
|
"wait-on": "^7.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^9.0.1",
|
"@fastify/cors": "^9.0.1",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/fluent-ffmpeg": "^2.1.24",
|
"@types/fluent-ffmpeg": "^2.1.24",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"ass-compiler": "^0.1.11",
|
"ass-compiler": "^0.1.11",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"electron-log": "^5.1.2",
|
"electron-log": "^5.1.2",
|
||||||
"electron-settings": "^4.0.4",
|
"electron-settings": "^4.0.4",
|
||||||
"electron-updater": "^6.1.8",
|
"electron-updater": "^6.1.8",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"fastify": "^4.26.2",
|
"fastify": "^4.26.2",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"mpd-parser": "^1.3.0",
|
"mpd-parser": "^1.3.0",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"sequelize": "^6.37.3",
|
"sequelize": "^6.37.3",
|
||||||
"sqlite3": "5.1.6"
|
"sqlite3": "5.1.6"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
"./ffmpeg/**"
|
"./ffmpeg/**"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1190
pages/addanime.vue
1190
pages/addanime.vue
File diff suppressed because it is too large
Load Diff
@ -1,28 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-screen overflow-hidden bg-[#11111189] flex flex-col p-5 text-white font-dm" style="-webkit-app-region: drag">
|
<div class="h-screen overflow-hidden bg-[#11111189] flex flex-col p-5 text-white font-dm" style="-webkit-app-region: drag">
|
||||||
<div class="relative flex flex-row items-center justify-center">
|
<div class="relative flex flex-row items-center justify-center">
|
||||||
<div class="text-2xl">ADN Login</div>
|
<div class="text-2xl">ADN Login</div>
|
||||||
</div>
|
|
||||||
<div class="flex flex-col mt-5 gap-3.5 h-full" style="-webkit-app-region: no-drag">
|
|
||||||
<div class="relative flex flex-col">
|
|
||||||
<input v-model="username" type="text" name="text" placeholder="Email" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
|
||||||
</div>
|
|
||||||
<div class="relative flex flex-col">
|
|
||||||
<input v-model="password" type="password" name="text" placeholder="Password" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative flex flex-col mt-auto">
|
|
||||||
<button @click="login" class="relative py-3 border-2 rounded-xl flex flex-row items-center justify-center" style="-webkit-app-region: no-drag">
|
|
||||||
<div class="flex flex-row items-center justify-center transition-all" :class="isLoggingIn ? 'opacity-0' : 'opacity-100'">
|
|
||||||
<div class="text-xl">Login</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute flex flex-row items-center justify-center gap-1 transition-all" :class="isLoggingIn ? 'opacity-100' : 'opacity-0'">
|
<div class="flex flex-col mt-5 gap-3.5 h-full" style="-webkit-app-region: no-drag">
|
||||||
<Icon name="mdi:loading" class="h-6 w-6 animate-spin" />
|
<div class="relative flex flex-col">
|
||||||
<div class="text-xl">Logging in</div>
|
<input v-model="username" type="text" name="text" placeholder="Email" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
||||||
|
</div>
|
||||||
|
<div class="relative flex flex-col">
|
||||||
|
<input v-model="password" type="password" name="text" placeholder="Password" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex flex-col mt-auto">
|
||||||
|
<button @click="login" class="relative py-3 border-2 rounded-xl flex flex-row items-center justify-center" style="-webkit-app-region: no-drag">
|
||||||
|
<div class="flex flex-row items-center justify-center transition-all" :class="isLoggingIn ? 'opacity-0' : 'opacity-100'">
|
||||||
|
<div class="text-xl">Login</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute flex flex-row items-center justify-center gap-1 transition-all" :class="isLoggingIn ? 'opacity-100' : 'opacity-0'">
|
||||||
|
<Icon name="mdi:loading" class="h-6 w-6 animate-spin" />
|
||||||
|
<div class="text-xl">Logging in</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -35,54 +35,54 @@ const password = ref<string>()
|
|||||||
const isLoggingIn = ref<number>(0)
|
const isLoggingIn = ref<number>(0)
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
isLoggingIn.value++
|
isLoggingIn.value++
|
||||||
if (!username.value) {
|
if (!username.value) {
|
||||||
isLoggingIn.value--
|
isLoggingIn.value--
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!password.value) {
|
if (!password.value) {
|
||||||
isLoggingIn.value--
|
isLoggingIn.value--
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await loginAccount(username.value, password.value, 'ADN')
|
const { data, error } = await loginAccount(username.value, password.value, 'ADN')
|
||||||
|
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
|
isLoggingIn.value--
|
||||||
|
return
|
||||||
|
}
|
||||||
isLoggingIn.value--
|
isLoggingIn.value--
|
||||||
return
|
close()
|
||||||
}
|
|
||||||
isLoggingIn.value--
|
|
||||||
close()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.font-dm {
|
.font-dm {
|
||||||
font-family: "DM Sans", sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-protest {
|
.font-protest {
|
||||||
font-family: "Protest Riot", sans-serif;
|
font-family: 'Protest Riot', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-dm-big {
|
.font-dm-big {
|
||||||
font-family: "DM Sans", sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
font-weight: 1000;
|
font-weight: 1000;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
animation: fadein 0.5s;
|
animation: fadein 0.5s;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-screen overflow-hidden bg-[#11111189] flex flex-col p-5 text-white font-dm" style="-webkit-app-region: drag">
|
<div class="h-screen overflow-hidden bg-[#11111189] flex flex-col p-5 text-white font-dm" style="-webkit-app-region: drag">
|
||||||
<div class="relative flex flex-row items-center justify-center">
|
<div class="relative flex flex-row items-center justify-center">
|
||||||
<div class="text-2xl">Crunchyroll Login</div>
|
<div class="text-2xl">Crunchyroll Login</div>
|
||||||
</div>
|
|
||||||
<div class="flex flex-col mt-5 gap-3.5 h-full" style="-webkit-app-region: no-drag">
|
|
||||||
<div class="relative flex flex-col">
|
|
||||||
<input v-model="username" type="text" name="text" placeholder="Email" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
|
||||||
</div>
|
|
||||||
<div class="relative flex flex-col">
|
|
||||||
<input v-model="password" type="password" name="text" placeholder="Password" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative flex flex-col mt-auto">
|
|
||||||
<button @click="login" class="relative py-3 border-2 rounded-xl flex flex-row items-center justify-center" style="-webkit-app-region: no-drag">
|
|
||||||
<div class="flex flex-row items-center justify-center transition-all" :class="isLoggingIn ? 'opacity-0' : 'opacity-100'">
|
|
||||||
<div class="text-xl">Login</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute flex flex-row items-center justify-center gap-1 transition-all" :class="isLoggingIn ? 'opacity-100' : 'opacity-0'">
|
<div class="flex flex-col mt-5 gap-3.5 h-full" style="-webkit-app-region: no-drag">
|
||||||
<Icon name="mdi:loading" class="h-6 w-6 animate-spin" />
|
<div class="relative flex flex-col">
|
||||||
<div class="text-xl">Logging in</div>
|
<input v-model="username" type="text" name="text" placeholder="Email" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
||||||
|
</div>
|
||||||
|
<div class="relative flex flex-col">
|
||||||
|
<input v-model="password" type="password" name="text" placeholder="Password" class="bg-[#5c5b5b] focus:outline-none px-3 py-3 rounded-xl text-sm text-center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex flex-col mt-auto">
|
||||||
|
<button @click="login" class="relative py-3 border-2 rounded-xl flex flex-row items-center justify-center" style="-webkit-app-region: no-drag">
|
||||||
|
<div class="flex flex-row items-center justify-center transition-all" :class="isLoggingIn ? 'opacity-0' : 'opacity-100'">
|
||||||
|
<div class="text-xl">Login</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute flex flex-row items-center justify-center gap-1 transition-all" :class="isLoggingIn ? 'opacity-100' : 'opacity-0'">
|
||||||
|
<Icon name="mdi:loading" class="h-6 w-6 animate-spin" />
|
||||||
|
<div class="text-xl">Logging in</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -35,54 +35,54 @@ const password = ref<string>()
|
|||||||
const isLoggingIn = ref<number>(0)
|
const isLoggingIn = ref<number>(0)
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
isLoggingIn.value++
|
isLoggingIn.value++
|
||||||
if (!username.value) {
|
if (!username.value) {
|
||||||
isLoggingIn.value--
|
isLoggingIn.value--
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!password.value) {
|
if (!password.value) {
|
||||||
isLoggingIn.value--
|
isLoggingIn.value--
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await loginAccount(username.value, password.value, 'CR')
|
const { data, error } = await loginAccount(username.value, password.value, 'CR')
|
||||||
|
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
|
isLoggingIn.value--
|
||||||
|
return
|
||||||
|
}
|
||||||
isLoggingIn.value--
|
isLoggingIn.value--
|
||||||
return
|
close()
|
||||||
}
|
|
||||||
isLoggingIn.value--
|
|
||||||
close()
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.font-dm {
|
.font-dm {
|
||||||
font-family: "DM Sans", sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-protest {
|
.font-protest {
|
||||||
font-family: "Protest Riot", sans-serif;
|
font-family: 'Protest Riot', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-dm-big {
|
.font-dm-big {
|
||||||
font-family: "DM Sans", sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
font-weight: 1000;
|
font-weight: 1000;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
animation: fadein 0.5s;
|
animation: fadein 0.5s;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
288
pages/index.vue
288
pages/index.vue
@ -1,70 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative h-screen overflow-hidden">
|
<div class="relative h-screen overflow-hidden">
|
||||||
<Updater />
|
<Updater />
|
||||||
<MainHeader />
|
<MainHeader />
|
||||||
<div class="flex flex-col text-white gap-5 mt-14 p-5 overflow-y-scroll h-[calc(100vh-3.5rem)]">
|
<div class="flex flex-col text-white gap-5 mt-14 p-5 overflow-y-scroll h-[calc(100vh-3.5rem)]">
|
||||||
<!-- <button @click="deletePlaylist">
|
<!-- <button @click="deletePlaylist">
|
||||||
Delete Playlist
|
Delete Playlist
|
||||||
</button> -->
|
</button> -->
|
||||||
<div v-for="p in playlist" class="relative flex flex-row gap-4 min-h-36 bg-[#63636383] rounded-xl font-dm overflow-hidden">
|
<div v-for="p in playlist" class="relative flex flex-row gap-4 min-h-36 bg-[#63636383] rounded-xl font-dm overflow-hidden">
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-[#a1a1a141] transition-all duration-300" :style="`width: calc((${p.partsdownloaded} / ${p.partsleft}) * 100%);`"></div>
|
<div
|
||||||
<div class="absolute h-full w-full flex flex-row gap-3 p-3.5">
|
class="absolute top-0 left-0 w-full h-full bg-[#a1a1a141] transition-all duration-300"
|
||||||
<div v-if="p.service === 'CR'" class="flex w-48 min-w-48">
|
:style="`width: calc((${p.partsdownloaded} / ${p.partsleft}) * 100%);`"
|
||||||
<img :src="(p.media as CrunchyEpisode).images.thumbnail[0].find((p) => p.height === 1080)?.source" alt="Image" class="object-cover rounded-lg" />
|
></div>
|
||||||
</div>
|
<div class="absolute h-full w-full flex flex-row gap-3 p-3.5">
|
||||||
<div v-if="p.service === 'ADN'" class="flex min-w-52 w-52">
|
<div v-if="p.service === 'CR'" class="flex w-48 min-w-48">
|
||||||
<img :src="(p.media as ADNEpisode).image2x" alt="Image" class="object-cover rounded-lg" />
|
<img :src="(p.media as CrunchyEpisode).images.thumbnail[0].find((p) => p.height === 1080)?.source" alt="Image" class="object-cover rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full">
|
<div v-if="p.service === 'ADN'" class="flex min-w-52 w-52">
|
||||||
<div class="flex flex-row h-full">
|
<img :src="(p.media as ADNEpisode).image2x" alt="Image" class="object-cover rounded-lg" />
|
||||||
<div class="flex flex-col">
|
</div>
|
||||||
<div v-if="p.status === 'waiting'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
|
<div class="flex flex-col w-full">
|
||||||
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
|
<div class="flex flex-row h-full">
|
||||||
{{ p.status }}
|
<div class="flex flex-col">
|
||||||
|
<div v-if="p.status === 'waiting'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
|
||||||
|
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
|
||||||
|
{{ p.status }}
|
||||||
|
</div>
|
||||||
|
<div v-if="p.status === 'preparing'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
|
||||||
|
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
|
||||||
|
{{ p.status }}
|
||||||
|
</div>
|
||||||
|
<div v-if="p.status === 'downloading'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#60501b] rounded-lg">
|
||||||
|
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
|
||||||
|
{{ p.status }}
|
||||||
|
</div>
|
||||||
|
<div v-if="p.status === 'merging'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
|
||||||
|
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
|
||||||
|
{{ p.status }}
|
||||||
|
</div>
|
||||||
|
<div v-if="p.status === 'completed'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#266326] rounded-lg">
|
||||||
|
<Icon name="material-symbols:check" class="h-3.5 w-3.5 text-white" />
|
||||||
|
{{ p.status }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs capitalize ml-auto">
|
||||||
|
{{ p.service === 'CR' ? 'Crunchyroll' : 'ADN' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="p.service === 'CR'" class="text-base capitalize h-full flex items-center">
|
||||||
|
{{ (p.media as CrunchyEpisode).series_title }} Season {{ (p.media as CrunchyEpisode).season_number }} Episode
|
||||||
|
{{ (p.media as CrunchyEpisode).episode_number }}
|
||||||
|
</div>
|
||||||
|
<div v-if="p.service === 'ADN'" class="text-base capitalize h-full">
|
||||||
|
{{ (p.media as ADNEpisode).show.title }} Season {{ (p.media as ADNEpisode).season ? (p.media as ADNEpisode).season : 1 }} Episode
|
||||||
|
{{ (p.media as ADNEpisode).shortNumber }}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-2 h-full items-end">
|
||||||
|
<div class="text-xs">{{ p.quality }}p</div>
|
||||||
|
<div class="text-xs uppercase">{{ p.format }}</div>
|
||||||
|
<div class="text-xs">Dubs: {{ p.dub.map((t) => t.name).join(', ') }}</div>
|
||||||
|
<div class="text-xs">Subs: {{ p.sub.length !== 0 ? p.sub.map((t) => t.name).join(', ') : '-' }}</div>
|
||||||
|
<div class="flex flex-col ml-auto gap-0.5">
|
||||||
|
<div v-if="p.partsleft && p.status === 'downloading'" class="text-xs ml-auto">{{ p.partsdownloaded }}/{{ p.partsleft }}</div>
|
||||||
|
<div v-if="p.downloadspeed && p.status === 'downloading'" class="text-xs">{{ p.downloadspeed }} MB/s</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="p.status === 'preparing'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
|
|
||||||
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
|
|
||||||
{{ p.status }}
|
|
||||||
</div>
|
|
||||||
<div v-if="p.status === 'downloading'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#60501b] rounded-lg">
|
|
||||||
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
|
|
||||||
{{ p.status }}
|
|
||||||
</div>
|
|
||||||
<div v-if="p.status === 'merging'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
|
|
||||||
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
|
|
||||||
{{ p.status }}
|
|
||||||
</div>
|
|
||||||
<div v-if="p.status === 'completed'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#266326] rounded-lg">
|
|
||||||
<Icon name="material-symbols:check" class="h-3.5 w-3.5 text-white" />
|
|
||||||
{{ p.status }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs capitalize ml-auto">
|
|
||||||
{{ p.service === 'CR' ? 'Crunchyroll' : 'ADN' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="p.service === 'CR'" class="text-base capitalize h-full flex items-center">
|
|
||||||
{{ (p.media as CrunchyEpisode).series_title }} Season {{ (p.media as CrunchyEpisode).season_number }} Episode {{ (p.media as CrunchyEpisode).episode_number }}
|
|
||||||
</div>
|
|
||||||
<div v-if="p.service === 'ADN'" class="text-base capitalize h-full">
|
|
||||||
{{ (p.media as ADNEpisode).show.title }} Season {{ (p.media as ADNEpisode).season ? (p.media as ADNEpisode).season : 1 }} Episode
|
|
||||||
{{ (p.media as ADNEpisode).shortNumber }}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-2 h-full items-end">
|
|
||||||
<div class="text-xs">{{ p.quality }}p</div>
|
|
||||||
<div class="text-xs uppercase">{{ p.format }}</div>
|
|
||||||
<div class="text-xs">Dubs: {{ p.dub.map((t) => t.name).join(', ') }}</div>
|
|
||||||
<div class="text-xs">Subs: {{ p.sub.length !== 0 ? p.sub.map((t) => t.name).join(', ') : '-' }}</div>
|
|
||||||
<div class="flex flex-col ml-auto gap-0.5">
|
|
||||||
<div v-if="p.partsleft && p.status === 'downloading'" class="text-xs ml-auto">{{ p.partsdownloaded }}/{{ p.partsleft }}</div>
|
|
||||||
<div v-if="p.downloadspeed && p.status === 'downloading'" class="text-xs">{{ p.downloadspeed }} MB/s</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -73,144 +77,144 @@ import type { CrunchyEpisode } from '~/components/Episode/Types'
|
|||||||
import Updater from '~/components/Updater.vue'
|
import Updater from '~/components/Updater.vue'
|
||||||
|
|
||||||
const playlist = ref<
|
const playlist = ref<
|
||||||
Array<{
|
Array<{
|
||||||
id: number
|
id: number
|
||||||
status: string
|
status: string
|
||||||
media: CrunchyEpisode | ADNEpisode
|
media: CrunchyEpisode | ADNEpisode
|
||||||
dub: Array<{ locale: string; name: string }>
|
dub: Array<{ locale: string; name: string }>
|
||||||
sub: Array<{ locale: string; name: string }>
|
sub: Array<{ locale: string; name: string }>
|
||||||
dir: string
|
dir: string
|
||||||
partsleft: number
|
partsleft: number
|
||||||
partsdownloaded: number
|
partsdownloaded: number
|
||||||
downloadspeed: number
|
downloadspeed: number
|
||||||
quality: number
|
quality: number
|
||||||
service: string
|
service: string
|
||||||
format: string
|
format: string
|
||||||
}>
|
}>
|
||||||
>()
|
>()
|
||||||
|
|
||||||
const getPlaylist = async () => {
|
const getPlaylist = async () => {
|
||||||
const { data, error } = await useFetch<
|
const { data, error } = await useFetch<
|
||||||
Array<{
|
Array<{
|
||||||
id: number
|
id: number
|
||||||
status: string
|
status: string
|
||||||
media: CrunchyEpisode | ADNEpisode
|
media: CrunchyEpisode | ADNEpisode
|
||||||
dub: Array<{ locale: string; name: string }>
|
dub: Array<{ locale: string; name: string }>
|
||||||
sub: Array<{ locale: string; name: string }>
|
sub: Array<{ locale: string; name: string }>
|
||||||
dir: string
|
dir: string
|
||||||
partsleft: number
|
partsleft: number
|
||||||
partsdownloaded: number
|
partsdownloaded: number
|
||||||
downloadspeed: number
|
downloadspeed: number
|
||||||
quality: number
|
quality: number
|
||||||
service: string
|
service: string
|
||||||
format: string
|
format: string
|
||||||
}>
|
}>
|
||||||
>('http://localhost:8080/api/service/playlist')
|
>('http://localhost:8080/api/service/playlist')
|
||||||
|
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
alert(error.value)
|
alert(error.value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.value) {
|
if (!data.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist.value = data.value
|
playlist.value = data.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletePlaylist = async () => {
|
const deletePlaylist = async () => {
|
||||||
const { data, error } = await useFetch('http://localhost:8080/api/service/playlist', {
|
const { data, error } = await useFetch('http://localhost:8080/api/service/playlist', {
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
alert(error.value)
|
alert(error.value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.value) {
|
if (!data.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getPlaylist()
|
getPlaylist()
|
||||||
|
|
||||||
setInterval(getPlaylist, 1000)
|
setInterval(getPlaylist, 1000)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background: none;
|
background: none;
|
||||||
background-color: none;
|
background-color: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-dm {
|
.font-dm {
|
||||||
font-family: 'DM Sans', sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-protest {
|
.font-protest {
|
||||||
font-family: 'Protest Riot', sans-serif;
|
font-family: 'Protest Riot', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-dm-big {
|
.font-dm-big {
|
||||||
font-family: 'DM Sans', sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
font-weight: 1000;
|
font-weight: 1000;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-a {
|
.loading-a {
|
||||||
animation: animation infinite 3s;
|
animation: animation infinite 3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: #383838;
|
background: #383838;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle */
|
/* Handle */
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #cac9c9;
|
background: #cac9c9;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle on hover */
|
/* Handle on hover */
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #555;
|
background: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes animation {
|
@keyframes animation {
|
||||||
0% {
|
0% {
|
||||||
left: 0%;
|
left: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
left: 88%;
|
left: 88%;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
left: 0%;
|
left: 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
animation: fadein 0.5s;
|
animation: fadein 0.5s;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-screen bg-[#111111] flex flex-col p-5 text-white" style="-webkit-app-region: drag">
|
<div class="h-screen bg-[#111111] flex flex-col p-5 text-white" style="-webkit-app-region: drag">
|
||||||
<div class="flex flex-row items-center justify-center">
|
<div class="flex flex-row items-center justify-center">
|
||||||
<div class="text-2xl">Settings</div>
|
<div class="text-2xl">Settings</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row mt-2" style="-webkit-app-region: no-drag">
|
||||||
|
<button
|
||||||
|
v-for="(option, index) in options"
|
||||||
|
@click="activeIndex = index"
|
||||||
|
class="w-full flex items-center justify-center py-2 border-b-2 transition-all"
|
||||||
|
:class="activeIndex === index ? 'border-[#ce6104]' : 'border-[#ce620428]'"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row mt-2" style="-webkit-app-region: no-drag">
|
|
||||||
<button
|
|
||||||
v-for="(option, index) in options"
|
|
||||||
@click="activeIndex = index"
|
|
||||||
class="w-full flex items-center justify-center py-2 border-b-2 transition-all"
|
|
||||||
:class="activeIndex === index ? 'border-[#ce6104]' : 'border-[#ce620428]'"
|
|
||||||
>
|
|
||||||
{{ option }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -23,15 +23,15 @@ const activeIndex = ref(0)
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
animation: fadein 0.5s;
|
animation: fadein 0.5s;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
1760
pnpm-lock.yaml
1760
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
|||||||
@echo off
|
|
||||||
wait-on http://localhost:3000 && electron ./.output/src/electron/background.js
|
|
@ -1,46 +1,45 @@
|
|||||||
import fastify from "fastify";
|
import fastify from 'fastify'
|
||||||
import cors from "@fastify/cors";
|
import cors from '@fastify/cors'
|
||||||
import NodeCache from "node-cache";
|
import NodeCache from 'node-cache'
|
||||||
import crunchyrollRoutes from "./routes/crunchyroll/crunchyroll.route";
|
import crunchyrollRoutes from './routes/crunchyroll/crunchyroll.route'
|
||||||
import { sequelize } from "./db/database";
|
import { sequelize } from './db/database'
|
||||||
import serviceRoutes from "./routes/service/service.route";
|
import serviceRoutes from './routes/service/service.route'
|
||||||
|
;(async () => {
|
||||||
(async () => {
|
|
||||||
try {
|
try {
|
||||||
await sequelize.authenticate();
|
await sequelize.authenticate()
|
||||||
console.log("Connection has been established successfully.");
|
console.log('Connection has been established successfully.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to connect to the database:", error);
|
console.error('Unable to connect to the database:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sequelize.sync();
|
await sequelize.sync()
|
||||||
console.log("All models were synchronized successfully.");
|
console.log('All models were synchronized successfully.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to synchronize Models");
|
console.log('Failed to synchronize Models')
|
||||||
}
|
}
|
||||||
})();
|
})()
|
||||||
|
|
||||||
const CacheController = new NodeCache({ stdTTL: 100, checkperiod: 120 });
|
const CacheController = new NodeCache({ stdTTL: 100, checkperiod: 120 })
|
||||||
|
|
||||||
export const server = fastify();
|
export const server = fastify()
|
||||||
|
|
||||||
// Cors registration
|
// Cors registration
|
||||||
server.register(cors, {
|
server.register(cors, {
|
||||||
origin: "*",
|
origin: '*',
|
||||||
methods: ["GET", "POST", "PUT", "DELETE"],
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||||
allowedHeaders: ["Content-Type", "Authorization"],
|
allowedHeaders: ['Content-Type', 'Authorization']
|
||||||
});
|
})
|
||||||
|
|
||||||
// Cache Controller Type
|
// Cache Controller Type
|
||||||
declare module "fastify" {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
CacheController: NodeCache;
|
CacheController: NodeCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache Controller
|
// Cache Controller
|
||||||
server.decorate("CacheController", CacheController);
|
server.decorate('CacheController', CacheController)
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
server.register(crunchyrollRoutes, { prefix: 'api/crunchyroll' })
|
server.register(crunchyrollRoutes, { prefix: 'api/crunchyroll' })
|
||||||
@ -49,11 +48,11 @@ server.register(serviceRoutes, { prefix: 'api/service' })
|
|||||||
function startAPI() {
|
function startAPI() {
|
||||||
server.listen({ port: 8080 }, (err, address) => {
|
server.listen({ port: 8080 }, (err, address) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
console.log(`Server is listening on ${address}`);
|
console.log(`Server is listening on ${address}`)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default startAPI;
|
export default startAPI
|
||||||
|
@ -4,139 +4,139 @@ import { CrunchyEpisode } from '../types/crunchyroll'
|
|||||||
import { ADNEpisode } from '../types/adn'
|
import { ADNEpisode } from '../types/adn'
|
||||||
|
|
||||||
const sequelize = new Sequelize({
|
const sequelize = new Sequelize({
|
||||||
dialect: 'sqlite',
|
dialect: 'sqlite',
|
||||||
storage: app.getPath('documents') + '/crd-dbv2.db'
|
storage: app.getPath('documents') + '/crd-dbv2.db'
|
||||||
})
|
})
|
||||||
|
|
||||||
interface AccountAttributes {
|
interface AccountAttributes {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
service: string
|
service: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AccountCreateAttributes {
|
interface AccountCreateAttributes {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
service: string
|
service: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AccountCreateAttributes {
|
interface AccountCreateAttributes {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
service: string
|
service: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlaylistAttributes {
|
interface PlaylistAttributes {
|
||||||
id: number
|
id: number
|
||||||
status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'completed' | 'failed'
|
status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'completed' | 'failed'
|
||||||
media: CrunchyEpisode | ADNEpisode
|
media: CrunchyEpisode | ADNEpisode
|
||||||
dub: Array<string>
|
dub: Array<string>
|
||||||
sub: Array<string>
|
sub: Array<string>
|
||||||
hardsub: boolean
|
hardsub: boolean
|
||||||
quality: 1080 | 720 | 480 | 360 | 240
|
quality: 1080 | 720 | 480 | 360 | 240
|
||||||
dir: string
|
dir: string
|
||||||
failedreason: string,
|
failedreason: string
|
||||||
service: 'CR' | 'ADN',
|
service: 'CR' | 'ADN'
|
||||||
format: 'mp4' | 'mkv'
|
format: 'mp4' | 'mkv'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlaylistCreateAttributes {
|
interface PlaylistCreateAttributes {
|
||||||
media: CrunchyEpisode | ADNEpisode
|
media: CrunchyEpisode | ADNEpisode
|
||||||
dub: Array<string>
|
dub: Array<string>
|
||||||
sub: Array<string>
|
sub: Array<string>
|
||||||
dir: string
|
dir: string
|
||||||
quality: 1080 | 720 | 480 | 360 | 240
|
quality: 1080 | 720 | 480 | 360 | 240
|
||||||
hardsub: boolean
|
hardsub: boolean
|
||||||
status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'completed' | 'failed',
|
status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'completed' | 'failed'
|
||||||
service: 'CR' | 'ADN',
|
service: 'CR' | 'ADN'
|
||||||
format: 'mp4' | 'mkv'
|
format: 'mp4' | 'mkv'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Account: ModelDefined<AccountAttributes, AccountCreateAttributes> = sequelize.define('Accounts', {
|
const Account: ModelDefined<AccountAttributes, AccountCreateAttributes> = sequelize.define('Accounts', {
|
||||||
id: {
|
id: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
autoIncrement: true,
|
autoIncrement: true,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
type: DataTypes.INTEGER
|
type: DataTypes.INTEGER
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
service: {
|
service: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.DATE
|
type: DataTypes.DATE
|
||||||
},
|
},
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.DATE
|
type: DataTypes.DATE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const Playlist: ModelDefined<PlaylistAttributes, PlaylistCreateAttributes> = sequelize.define('Playlist', {
|
const Playlist: ModelDefined<PlaylistAttributes, PlaylistCreateAttributes> = sequelize.define('Playlist', {
|
||||||
id: {
|
id: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
autoIncrement: true,
|
autoIncrement: true,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
type: DataTypes.INTEGER
|
type: DataTypes.INTEGER
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
media: {
|
media: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.JSON
|
type: DataTypes.JSON
|
||||||
},
|
},
|
||||||
dub: {
|
dub: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.JSON
|
type: DataTypes.JSON
|
||||||
},
|
},
|
||||||
sub: {
|
sub: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.JSON
|
type: DataTypes.JSON
|
||||||
},
|
},
|
||||||
dir: {
|
dir: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
hardsub: {
|
hardsub: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.BOOLEAN
|
type: DataTypes.BOOLEAN
|
||||||
},
|
},
|
||||||
failedreason: {
|
failedreason: {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
quality: {
|
quality: {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
type: DataTypes.BOOLEAN
|
type: DataTypes.BOOLEAN
|
||||||
},
|
},
|
||||||
service: {
|
service: {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
format: {
|
format: {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.DATE
|
type: DataTypes.DATE
|
||||||
},
|
},
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.DATE
|
type: DataTypes.DATE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export { sequelize, Account, Playlist }
|
export { sequelize, Account, Playlist }
|
||||||
|
@ -8,302 +8,301 @@ import { loggedInCheck } from '../service/service.service'
|
|||||||
import { parse as mpdParse, parse } from 'mpd-parser'
|
import { parse as mpdParse, parse } from 'mpd-parser'
|
||||||
|
|
||||||
export async function adnLogin(user: string, passw: string) {
|
export async function adnLogin(user: string, passw: string) {
|
||||||
const cachedData:
|
const cachedData:
|
||||||
| {
|
| {
|
||||||
accessToken: string
|
accessToken: string
|
||||||
}
|
}
|
||||||
| undefined = server.CacheController.get('adntoken')
|
| undefined = server.CacheController.get('adntoken')
|
||||||
|
|
||||||
if (!cachedData) {
|
if (!cachedData) {
|
||||||
var { data, error } = await adnLoginFetch(user, passw)
|
var { data, error } = await adnLoginFetch(user, passw)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to ADN', error.error as string)
|
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to ADN', error.error as string)
|
||||||
return { data: null, error: error.error }
|
return { data: null, error: error.error }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to ADN', 'ADN returned null')
|
||||||
|
return { data: null, error: 'ADN returned null' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.accessToken) {
|
||||||
|
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to ADN', 'ADN returned malformed data')
|
||||||
|
return { data: null, error: 'ADN returned malformed data' }
|
||||||
|
}
|
||||||
|
|
||||||
|
server.CacheController.set('adntoken', data, 300)
|
||||||
|
|
||||||
|
return { data: data, error: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
return { data: cachedData, error: null }
|
||||||
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to ADN', 'ADN returned null')
|
|
||||||
return { data: null, error: 'ADN returned null' }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.accessToken) {
|
|
||||||
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to ADN', 'ADN returned malformed data')
|
|
||||||
return { data: null, error: 'ADN returned malformed data' }
|
|
||||||
}
|
|
||||||
|
|
||||||
server.CacheController.set('adntoken', data, 300)
|
|
||||||
|
|
||||||
return { data: data, error: null }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { data: cachedData, error: null }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function adnLoginFetch(user: string, passw: string) {
|
async function adnLoginFetch(user: string, passw: string) {
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
username: user,
|
username: user,
|
||||||
password: passw,
|
password: passw,
|
||||||
source: 'Web',
|
source: 'Web',
|
||||||
rememberMe: true
|
rememberMe: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await useFetch<{
|
const { data, error } = await useFetch<{
|
||||||
accessToken: string
|
accessToken: string
|
||||||
}>('https://gw.api.animationdigitalnetwork.fr/authentication/login', {
|
}>('https://gw.api.animationdigitalnetwork.fr/authentication/login', {
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
header: headers
|
header: headers
|
||||||
})
|
})
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return { data: null, error: error }
|
return { data: null, error: error }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return { data: null, error: null }
|
return { data: null, error: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: data, error: null }
|
return { data: data, error: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEpisodeADN(q: number) {
|
export async function getEpisodeADN(q: number) {
|
||||||
const cachedData = server.CacheController.get(`getepisodeadn-${q}`)
|
const cachedData = server.CacheController.get(`getepisodeadn-${q}`)
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
return cachedData
|
return cachedData
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/video/${q}/public`, {
|
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/video/${q}/public`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'x-target-distribution': 'de'
|
'x-target-distribution': 'de'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: {
|
const data: {
|
||||||
video: Array<any>
|
video: Array<any>
|
||||||
} = JSON.parse(await response.text())
|
} = JSON.parse(await response.text())
|
||||||
|
|
||||||
server.CacheController.set(`getepisodeadn-${q}`, data.video, 1000)
|
server.CacheController.set(`getepisodeadn-${q}`, data.video, 1000)
|
||||||
|
|
||||||
return data.video
|
return data.video
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to fetch ADN')
|
throw new Error('Failed to fetch ADN')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e as string)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e as string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPlayerConfigADN(id: number, geo: 'de' | 'fr') {
|
export async function getPlayerConfigADN(id: number, geo: 'de' | 'fr') {
|
||||||
|
const account = await loggedInCheck('ADN')
|
||||||
|
|
||||||
const account = await loggedInCheck('ADN')
|
if (!account) return
|
||||||
|
|
||||||
if (!account) return
|
const token = await adnLogin(account.username, account.password)
|
||||||
|
|
||||||
const token = await adnLogin(account.username, account.password)
|
if (!token.data?.accessToken) return
|
||||||
|
|
||||||
if (!token.data?.accessToken) return
|
try {
|
||||||
|
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/video/${id}/configuration`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'x-target-distribution': geo,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token.data.accessToken}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
if (response.ok) {
|
||||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/video/${id}/configuration`, {
|
const data: ADNPlayerConfig = JSON.parse(await response.text())
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'x-target-distribution': geo,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${token.data.accessToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.ok) {
|
return data
|
||||||
const data: ADNPlayerConfig = JSON.parse(await response.text())
|
} else {
|
||||||
|
throw new Error('Failed to fetch ADN')
|
||||||
return data
|
}
|
||||||
} else {
|
} catch (e) {
|
||||||
throw new Error('Failed to fetch ADN')
|
throw new Error(e as string)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e as string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPlayerToken(id: number, geo: 'de' | 'fr') {
|
async function getPlayerToken(id: number, geo: 'de' | 'fr') {
|
||||||
const r = await getPlayerConfigADN(id, geo)
|
const r = await getPlayerConfigADN(id, geo)
|
||||||
|
|
||||||
if (!r) return
|
if (!r) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/refresh/token`, {
|
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/refresh/token`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'x-target-distribution': geo,
|
'x-target-distribution': geo,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Player-Refresh-Token': r.player.options.user.refreshToken
|
'X-Player-Refresh-Token': r.player.options.user.refreshToken
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: {
|
const data: {
|
||||||
token: string
|
token: string
|
||||||
accessToken: string
|
accessToken: string
|
||||||
refreshToken: string
|
refreshToken: string
|
||||||
} = JSON.parse(await response.text())
|
} = JSON.parse(await response.text())
|
||||||
|
|
||||||
return data
|
return data
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to fetch ADN')
|
throw new Error('Failed to fetch ADN')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e as string)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e as string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomHexaString(length: number) {
|
function randomHexaString(length: number) {
|
||||||
const characters = '0123456789abcdef'
|
const characters = '0123456789abcdef'
|
||||||
let result = ''
|
let result = ''
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
result += characters[Math.floor(Math.random() * characters.length)]
|
result += characters[Math.floor(Math.random() * characters.length)]
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPlayerEncryptedToken(id: number, geo: 'de' | 'fr') {
|
async function getPlayerEncryptedToken(id: number, geo: 'de' | 'fr') {
|
||||||
const token = await getPlayerToken(id, geo)
|
const token = await getPlayerToken(id, geo)
|
||||||
|
|
||||||
if (!token) return
|
if (!token) return
|
||||||
|
|
||||||
var key = new JSEncrypt()
|
var key = new JSEncrypt()
|
||||||
var random = randomHexaString(16)
|
var random = randomHexaString(16)
|
||||||
|
|
||||||
key.setPublicKey(
|
key.setPublicKey(
|
||||||
'-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbQrCJBRmaXM4gJidDmcpWDssgnumHinCLHAgS4buMtdH7dEGGEUfBofLzoEdt1jqcrCDT6YNhM0aFCqbLOPFtx9cg/X2G/G5bPVu8cuFM0L+ehp8s6izK1kjx3OOPH/kWzvstM5tkqgJkNyNEvHdeJl6KhS+IFEqwvZqgbBpKuwIDAQAB-----END PUBLIC KEY-----'
|
'-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbQrCJBRmaXM4gJidDmcpWDssgnumHinCLHAgS4buMtdH7dEGGEUfBofLzoEdt1jqcrCDT6YNhM0aFCqbLOPFtx9cg/X2G/G5bPVu8cuFM0L+ehp8s6izK1kjx3OOPH/kWzvstM5tkqgJkNyNEvHdeJl6KhS+IFEqwvZqgbBpKuwIDAQAB-----END PUBLIC KEY-----'
|
||||||
)
|
)
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
k: random,
|
k: random,
|
||||||
t: String(token.token)
|
t: String(token.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
const finisheddata = JSON.stringify(data)
|
const finisheddata = JSON.stringify(data)
|
||||||
|
|
||||||
const encryptedData = key.encrypt(finisheddata) || ''
|
const encryptedData = key.encrypt(finisheddata) || ''
|
||||||
|
|
||||||
return { data: encryptedData, random: random }
|
return { data: encryptedData, random: random }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function adnGetPlaylist(animeid: number, geo: 'de' | 'fr') {
|
export async function adnGetPlaylist(animeid: number, geo: 'de' | 'fr') {
|
||||||
const token = await getPlayerEncryptedToken(animeid, geo)
|
const token = await getPlayerEncryptedToken(animeid, geo)
|
||||||
|
|
||||||
if (!token) return
|
if (!token) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/video/${animeid}/link`, {
|
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/video/${animeid}/link`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'x-target-distribution': geo,
|
'x-target-distribution': geo,
|
||||||
'X-Player-Token': token.data
|
'X-Player-Token': token.data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: ADNLink = await JSON.parse(await response.text())
|
const data: ADNLink = await JSON.parse(await response.text())
|
||||||
return { data: data, secret: token.random }
|
return { data: data, secret: token.random }
|
||||||
} else {
|
} else {
|
||||||
const data: { message: string, code: string, statusCode: string} = JSON.parse(await response.text())
|
const data: { message: string; code: string; statusCode: string } = JSON.parse(await response.text())
|
||||||
|
|
||||||
messageBox('error', ['Cancel'], 2, 'Failed to fetch Playlist', 'Failed to fetch ADN Playlist', `${data.message} - ${data.code}`)
|
messageBox('error', ['Cancel'], 2, 'Failed to fetch Playlist', 'Failed to fetch ADN Playlist', `${data.message} - ${data.code}`)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e as string)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e as string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function adnGetM3U8Playlist(url: string) {
|
export async function adnGetM3U8Playlist(url: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: { location: string } = await JSON.parse(await response.text())
|
const data: { location: string } = await JSON.parse(await response.text())
|
||||||
|
|
||||||
const mu8 = await fetch(data.location, {
|
const mu8 = await fetch(data.location, {
|
||||||
method: 'GET',
|
method: 'GET'
|
||||||
})
|
})
|
||||||
|
|
||||||
const playlist = await mu8.text()
|
const playlist = await mu8.text()
|
||||||
|
|
||||||
const url = await extractURLFromPlaylist(playlist)
|
const url = await extractURLFromPlaylist(playlist)
|
||||||
|
|
||||||
const partsraw = await fetch(url, {
|
const partsraw = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET'
|
||||||
})
|
})
|
||||||
|
|
||||||
const parts = await partsraw.text()
|
const parts = await partsraw.text()
|
||||||
|
|
||||||
const baseurl = await extractBaseURL(url);
|
const baseurl = await extractBaseURL(url)
|
||||||
|
|
||||||
const partsArray = await extractSequenceURLs(parts, baseurl)
|
const partsArray = await extractSequenceURLs(parts, baseurl)
|
||||||
|
|
||||||
return partsArray
|
return partsArray
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e as string)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e as string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractURLFromPlaylist(playlist: string) {
|
async function extractURLFromPlaylist(playlist: string) {
|
||||||
var startIndex = playlist.indexOf("http");
|
var startIndex = playlist.indexOf('http')
|
||||||
var endIndex = playlist.indexOf(" ", startIndex);
|
var endIndex = playlist.indexOf(' ', startIndex)
|
||||||
var extractedURL = playlist.slice(startIndex, endIndex);
|
var extractedURL = playlist.slice(startIndex, endIndex)
|
||||||
return extractedURL;
|
return extractedURL
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractBaseURL(playlistURL: string) {
|
async function extractBaseURL(playlistURL: string) {
|
||||||
var baseURL = playlistURL.substring(0, playlistURL.lastIndexOf("/") + 1);
|
var baseURL = playlistURL.substring(0, playlistURL.lastIndexOf('/') + 1)
|
||||||
return baseURL;
|
return baseURL
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractSequenceURLs(playlistText: string, baseURL: string) {
|
async function extractSequenceURLs(playlistText: string, baseURL: string) {
|
||||||
var sequenceURLs: Array<{ filename: string, url: string }> = [];
|
var sequenceURLs: Array<{ filename: string; url: string }> = []
|
||||||
var matches = playlistText.match(/sequence_\d+\.ts/g);
|
var matches = playlistText.match(/sequence_\d+\.ts/g)
|
||||||
if (matches) {
|
if (matches) {
|
||||||
matches.forEach(function(match) {
|
matches.forEach(function (match) {
|
||||||
sequenceURLs.push({ filename: match, url: baseURL + match });
|
sequenceURLs.push({ filename: match, url: baseURL + match })
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
return sequenceURLs;
|
return sequenceURLs
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseSubs(url: string, secret: string) {
|
export async function parseSubs(url: string, secret: string) {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
|
|
||||||
const data = await response.text()
|
const data = await response.text()
|
||||||
|
|
||||||
var key = secret + '7fac1178830cfe0c'
|
var key = secret + '7fac1178830cfe0c'
|
||||||
|
|
||||||
console.log(key)
|
console.log(key)
|
||||||
|
|
||||||
var parsedSubtitle = CryptoJS.enc.Base64.parse(data.substring(0, 24))
|
var parsedSubtitle = CryptoJS.enc.Base64.parse(data.substring(0, 24))
|
||||||
var sec = CryptoJS.enc.Hex.parse(key)
|
var sec = CryptoJS.enc.Hex.parse(key)
|
||||||
var som = data.substring(24)
|
var som = data.substring(24)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fuck You ADN
|
// Fuck You ADN
|
||||||
var decrypted: any = CryptoJS.AES.decrypt(som, sec, { iv: parsedSubtitle })
|
var decrypted: any = CryptoJS.AES.decrypt(som, sec, { iv: parsedSubtitle })
|
||||||
decrypted = decrypted.toString(CryptoJS.enc.Utf8)
|
decrypted = decrypted.toString(CryptoJS.enc.Utf8)
|
||||||
return decrypted
|
return decrypted
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error decrypting subtitles:', error)
|
console.error('Error decrypting subtitles:', error)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,17 @@ import { CrunchyEpisodes, CrunchySeason } from '../../types/crunchyroll'
|
|||||||
import { loggedInCheck } from '../service/service.service'
|
import { loggedInCheck } from '../service/service.service'
|
||||||
|
|
||||||
export async function loginController(request: FastifyRequest, reply: FastifyReply) {
|
export async function loginController(request: FastifyRequest, reply: FastifyReply) {
|
||||||
const account = await loggedInCheck('CR')
|
const account = await loggedInCheck('CR')
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return reply.code(401).send({ message: 'Not Logged in' })
|
return reply.code(401).send({ message: 'Not Logged in' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await crunchyLogin(account.username, account.password)
|
const { data, error } = await crunchyLogin(account.username, account.password)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
reply.code(400).send(error)
|
reply.code(400).send(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.code(200).send(data)
|
return reply.code(200).send(data)
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,20 @@ import type { FastifyInstance } from 'fastify'
|
|||||||
import { loginController } from './crunchyroll.controller'
|
import { loginController } from './crunchyroll.controller'
|
||||||
|
|
||||||
async function crunchyrollRoutes(server: FastifyInstance) {
|
async function crunchyrollRoutes(server: FastifyInstance) {
|
||||||
server.post(
|
server.post(
|
||||||
'/login',
|
'/login',
|
||||||
{
|
{
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
'4xx': {
|
'4xx': {
|
||||||
error: { type: 'string' },
|
error: { type: 'string' },
|
||||||
message: { type: 'string' }
|
message: { type: 'string' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loginController
|
loginController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default crunchyrollRoutes
|
export default crunchyrollRoutes
|
||||||
|
@ -7,15 +7,76 @@ import { loggedInCheck } from '../service/service.service'
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
const crErrors = [
|
const crErrors = [
|
||||||
{
|
{
|
||||||
error: 'invalid_grant',
|
error: 'invalid_grant',
|
||||||
response: 'Email/Password is wrong'
|
response: 'Email/Password is wrong'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export async function crunchyLogin(user: string, passw: string) {
|
export async function crunchyLogin(user: string, passw: string) {
|
||||||
const cachedData:
|
const cachedData:
|
||||||
| {
|
| {
|
||||||
|
access_token: string
|
||||||
|
refresh_token: string
|
||||||
|
expires_in: number
|
||||||
|
token_type: string
|
||||||
|
scope: string
|
||||||
|
country: string
|
||||||
|
account_id: string
|
||||||
|
profile_id: string
|
||||||
|
}
|
||||||
|
| undefined = server.CacheController.get('crtoken')
|
||||||
|
|
||||||
|
if (!cachedData) {
|
||||||
|
var { data, error } = await crunchyLoginFetch(user, passw)
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
messageBox(
|
||||||
|
'error',
|
||||||
|
['Cancel'],
|
||||||
|
2,
|
||||||
|
'Failed to login',
|
||||||
|
'Failed to login to Crunchyroll',
|
||||||
|
crErrors.find((r) => r.error === (error?.error as string)) ? crErrors.find((r) => r.error === (error?.error as string))?.response : (error.error as string)
|
||||||
|
)
|
||||||
|
return { data: null, error: error.error }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned null')
|
||||||
|
return { data: null, error: 'Crunchyroll returned null' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.access_token) {
|
||||||
|
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned malformed data')
|
||||||
|
return { data: null, error: 'Crunchyroll returned malformed data' }
|
||||||
|
}
|
||||||
|
|
||||||
|
server.CacheController.set('crtoken', data, data.expires_in - 30)
|
||||||
|
|
||||||
|
return { data: data, error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: cachedData, error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function crunchyLoginFetch(user: string, passw: string) {
|
||||||
|
const headers = {
|
||||||
|
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'User-Agent': 'Crunchyroll/3.46.2 Android/13 okhttp/4.12.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: any = {
|
||||||
|
username: user,
|
||||||
|
password: passw,
|
||||||
|
grant_type: 'password',
|
||||||
|
scope: 'offline_access',
|
||||||
|
device_name: 'RMX2170',
|
||||||
|
device_type: 'realme RMX2170'
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await useFetch<{
|
||||||
access_token: string
|
access_token: string
|
||||||
refresh_token: string
|
refresh_token: string
|
||||||
expires_in: number
|
expires_in: number
|
||||||
@ -24,157 +85,96 @@ export async function crunchyLogin(user: string, passw: string) {
|
|||||||
country: string
|
country: string
|
||||||
account_id: string
|
account_id: string
|
||||||
profile_id: string
|
profile_id: string
|
||||||
}
|
}>('https://beta-api.crunchyroll.com/auth/v1/token', {
|
||||||
| undefined = server.CacheController.get('crtoken')
|
type: 'POST',
|
||||||
|
body: new URLSearchParams(body).toString(),
|
||||||
if (!cachedData) {
|
header: headers,
|
||||||
var { data, error } = await crunchyLoginFetch(user, passw)
|
credentials: 'same-origin'
|
||||||
|
})
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
messageBox(
|
return { data: null, error: error }
|
||||||
'error',
|
|
||||||
['Cancel'],
|
|
||||||
2,
|
|
||||||
'Failed to login',
|
|
||||||
'Failed to login to Crunchyroll',
|
|
||||||
crErrors.find((r) => r.error === (error?.error as string)) ? crErrors.find((r) => r.error === (error?.error as string))?.response : (error.error as string)
|
|
||||||
)
|
|
||||||
return { data: null, error: error.error }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned null')
|
return { data: null, error: null }
|
||||||
return { data: null, error: 'Crunchyroll returned null' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.access_token) {
|
|
||||||
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned malformed data')
|
|
||||||
return { data: null, error: 'Crunchyroll returned malformed data' }
|
|
||||||
}
|
|
||||||
|
|
||||||
server.CacheController.set('crtoken', data, data.expires_in - 30)
|
|
||||||
|
|
||||||
return { data: data, error: null }
|
return { data: data, error: null }
|
||||||
}
|
|
||||||
|
|
||||||
return { data: cachedData, error: null }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function crunchyLoginFetch(user: string, passw: string) {
|
|
||||||
const headers = {
|
|
||||||
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=',
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'User-Agent': 'Crunchyroll/3.46.2 Android/13 okhttp/4.12.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
const body: any = {
|
|
||||||
username: user,
|
|
||||||
password: passw,
|
|
||||||
grant_type: 'password',
|
|
||||||
scope: 'offline_access',
|
|
||||||
device_name: 'RMX2170',
|
|
||||||
device_type: 'realme RMX2170'
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data, error } = await useFetch<{
|
|
||||||
access_token: string
|
|
||||||
refresh_token: string
|
|
||||||
expires_in: number
|
|
||||||
token_type: string
|
|
||||||
scope: string
|
|
||||||
country: string
|
|
||||||
account_id: string
|
|
||||||
profile_id: string
|
|
||||||
}>('https://beta-api.crunchyroll.com/auth/v1/token', {
|
|
||||||
type: 'POST',
|
|
||||||
body: new URLSearchParams(body).toString(),
|
|
||||||
header: headers,
|
|
||||||
credentials: 'same-origin'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return { data: null, error: error }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return { data: null, error: null }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { data: data, error: null }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function crunchyGetPlaylist(q: string) {
|
export async function crunchyGetPlaylist(q: string) {
|
||||||
const account = await loggedInCheck('CR')
|
const account = await loggedInCheck('CR')
|
||||||
|
|
||||||
if (!account) return
|
if (!account) return
|
||||||
|
|
||||||
const { data, error } = await crunchyLogin(account.username, account.password)
|
const { data, error } = await crunchyLogin(account.username, account.password)
|
||||||
|
|
||||||
if (!data) return
|
if (!data) return
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Authorization: `Bearer ${data.access_token}`,
|
Authorization: `Bearer ${data.access_token}`,
|
||||||
'X-Cr-Disable-Drm': 'true'
|
'X-Cr-Disable-Drm': 'true'
|
||||||
}
|
}
|
||||||
|
|
||||||
const query: any = {
|
const query: any = {
|
||||||
q: q,
|
q: q,
|
||||||
n: 100,
|
n: 100,
|
||||||
type: 'series',
|
type: 'series',
|
||||||
ratings: false,
|
ratings: false,
|
||||||
locale: 'de-DE'
|
locale: 'de-DE'
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/console/switch/play`, {
|
const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/console/switch/play`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: headers
|
headers: headers
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: VideoPlaylist = JSON.parse(await response.text())
|
const data: VideoPlaylist = JSON.parse(await response.text())
|
||||||
|
|
||||||
data.hardSubs = Object.values((data as any).hardSubs)
|
data.hardSubs = Object.values((data as any).hardSubs)
|
||||||
|
|
||||||
data.subtitles = Object.values((data as any).subtitles)
|
data.subtitles = Object.values((data as any).subtitles)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
} else {
|
} else {
|
||||||
throw new Error(await response.text())
|
throw new Error(await response.text())
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e as string)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e as string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function crunchyGetPlaylistMPD(q: string) {
|
export async function crunchyGetPlaylistMPD(q: string) {
|
||||||
const account = await loggedInCheck('CR')
|
const account = await loggedInCheck('CR')
|
||||||
|
|
||||||
if (!account) return
|
if (!account) return
|
||||||
|
|
||||||
const { data, error } = await crunchyLogin(account.username, account.password)
|
const { data, error } = await crunchyLogin(account.username, account.password)
|
||||||
|
|
||||||
if (!data) return
|
if (!data) return
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Authorization: `Bearer ${data.access_token}`,
|
Authorization: `Bearer ${data.access_token}`,
|
||||||
'X-Cr-Disable-Drm': 'true'
|
'X-Cr-Disable-Drm': 'true'
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(q, {
|
const response = await fetch(q, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: headers
|
headers: headers
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const parsed = mpdParse(await response.text())
|
const parsed = mpdParse(await response.text())
|
||||||
|
|
||||||
return parsed
|
return parsed
|
||||||
} else {
|
} else {
|
||||||
throw new Error(await response.text())
|
throw new Error(await response.text())
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e as string)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw new Error(e as string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,110 +1,111 @@
|
|||||||
import { FastifyReply, FastifyRequest } from "fastify"
|
import { FastifyReply, FastifyRequest } from 'fastify'
|
||||||
import { crunchyLogin } from "../crunchyroll/crunchyroll.service"
|
import { crunchyLogin } from '../crunchyroll/crunchyroll.service'
|
||||||
import { addEpisodeToPlaylist, getDownloading, getPlaylist, loggedInCheck, safeLoginData } from "./service.service"
|
import { addEpisodeToPlaylist, getDownloading, getPlaylist, loggedInCheck, safeLoginData } from './service.service'
|
||||||
import { CrunchyEpisodes } from "../../types/crunchyroll"
|
import { CrunchyEpisodes } from '../../types/crunchyroll'
|
||||||
import { adnLogin } from "../adn/adn.service"
|
import { adnLogin } from '../adn/adn.service'
|
||||||
|
|
||||||
export async function checkLoginController(request: FastifyRequest<{
|
export async function checkLoginController(
|
||||||
Params: {
|
request: FastifyRequest<{
|
||||||
id: string
|
Params: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
const account = await loggedInCheck(request.params.id)
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return reply.code(401).send({ message: 'Not Logged in' })
|
||||||
}
|
}
|
||||||
}>, reply: FastifyReply) {
|
|
||||||
const account = await loggedInCheck(request.params.id)
|
|
||||||
|
|
||||||
if (!account) {
|
return reply.code(200).send({ message: 'Logged in' })
|
||||||
return reply.code(401).send({ message: 'Not Logged in' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return reply.code(200).send({ message: 'Logged in' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginController(
|
export async function loginController(
|
||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Body: {
|
Body: {
|
||||||
user: string
|
user: string
|
||||||
password: string
|
password: string
|
||||||
},
|
}
|
||||||
Params: {
|
Params: {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const body = request.body
|
const body = request.body
|
||||||
const params = request.params
|
const params = request.params
|
||||||
|
|
||||||
const account = await loggedInCheck(params.id)
|
const account = await loggedInCheck(params.id)
|
||||||
|
|
||||||
if (account) {
|
if (account) {
|
||||||
return reply.code(404).send({ message: 'Already Logged In' })
|
return reply.code(404).send({ message: 'Already Logged In' })
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseData
|
var responseData
|
||||||
var responseError
|
var responseError
|
||||||
|
|
||||||
if (params.id === 'CR') {
|
if (params.id === 'CR') {
|
||||||
const { data, error } = await crunchyLogin(body.user, body.password)
|
const { data, error } = await crunchyLogin(body.user, body.password)
|
||||||
responseError = error,
|
;(responseError = error), (responseData = data)
|
||||||
responseData = data
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (params.id === 'ADN') {
|
if (params.id === 'ADN') {
|
||||||
const { data, error } = await adnLogin(body.user, body.password)
|
const { data, error } = await adnLogin(body.user, body.password)
|
||||||
responseError = error,
|
;(responseError = error), (responseData = data)
|
||||||
responseData = data
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (responseError || !responseData) {
|
if (responseError || !responseData) {
|
||||||
return reply.code(404).send({
|
return reply.code(404).send({
|
||||||
message: 'Invalid Email or Password'
|
message: 'Invalid Email or Password'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await safeLoginData(body.user, body.password, params.id)
|
await safeLoginData(body.user, body.password, params.id)
|
||||||
|
|
||||||
return reply.code(200).send()
|
return reply.code(200).send()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addPlaylistController(
|
export async function addPlaylistController(
|
||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Body: {
|
Body: {
|
||||||
episodes: CrunchyEpisodes
|
episodes: CrunchyEpisodes
|
||||||
dubs: Array<string>
|
dubs: Array<string>
|
||||||
subs: Array<string>
|
subs: Array<string>
|
||||||
dir: string
|
dir: string
|
||||||
hardsub: boolean
|
hardsub: boolean
|
||||||
quality: 1080 | 720 | 480 | 360 | 240
|
quality: 1080 | 720 | 480 | 360 | 240
|
||||||
service: 'CR' | 'ADN'
|
service: 'CR' | 'ADN'
|
||||||
format: 'mp4' | 'mkv'
|
format: 'mp4' | 'mkv'
|
||||||
}
|
}
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const body = request.body
|
const body = request.body
|
||||||
|
|
||||||
for (const e of body.episodes) {
|
for (const e of body.episodes) {
|
||||||
await addEpisodeToPlaylist(e, body.subs, body.dubs, body.dir, body.hardsub, 'waiting', body.quality, body.service, body.format)
|
await addEpisodeToPlaylist(e, body.subs, body.dubs, body.dir, body.hardsub, 'waiting', body.quality, body.service, body.format)
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPlaylistController(request: FastifyRequest, reply: FastifyReply) {
|
export async function getPlaylistController(request: FastifyRequest, reply: FastifyReply) {
|
||||||
const playlist = await getPlaylist()
|
const playlist = await getPlaylist()
|
||||||
|
|
||||||
for (const v of playlist) {
|
for (const v of playlist) {
|
||||||
if (v.dataValues.status === 'downloading') {
|
if (v.dataValues.status === 'downloading') {
|
||||||
const found = await getDownloading(v.dataValues.id)
|
const found = await getDownloading(v.dataValues.id)
|
||||||
if (found) {
|
if (found) {
|
||||||
;(v as any).dataValues = {
|
;(v as any).dataValues = {
|
||||||
...v.dataValues,
|
...v.dataValues,
|
||||||
partsleft: found.partsToDownload,
|
partsleft: found.partsToDownload,
|
||||||
partsdownloaded: found.downloadedParts,
|
partsdownloaded: found.downloadedParts,
|
||||||
downloadspeed: found.downloadSpeed.toFixed(2)
|
downloadspeed: found.downloadSpeed.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return reply.code(200).send(playlist.reverse())
|
return reply.code(200).send(playlist.reverse())
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,63 @@
|
|||||||
import { FastifyInstance } from "fastify"
|
import { FastifyInstance } from 'fastify'
|
||||||
import { addPlaylistController, checkLoginController, getPlaylistController, loginController } from "./service.controller"
|
import { addPlaylistController, checkLoginController, getPlaylistController, loginController } from './service.controller'
|
||||||
|
|
||||||
async function serviceRoutes(server: FastifyInstance) {
|
async function serviceRoutes(server: FastifyInstance) {
|
||||||
server.post(
|
server.post(
|
||||||
'/login/:id',
|
'/login/:id',
|
||||||
{
|
{
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
'4xx': {
|
'4xx': {
|
||||||
error: { type: 'string' },
|
error: { type: 'string' },
|
||||||
message: { type: 'string' }
|
message: { type: 'string' }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
loginController
|
||||||
},
|
|
||||||
loginController
|
|
||||||
),
|
),
|
||||||
|
server.get(
|
||||||
|
'/check/:id',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
'4xx': {
|
||||||
|
error: { type: 'string' },
|
||||||
|
message: { type: 'string' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkLoginController
|
||||||
|
),
|
||||||
|
server.post(
|
||||||
|
'/playlist',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
'4xx': {
|
||||||
|
error: { type: 'string' },
|
||||||
|
message: { type: 'string' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addPlaylistController
|
||||||
|
)
|
||||||
server.get(
|
server.get(
|
||||||
'/check/:id',
|
'/playlist',
|
||||||
{
|
{
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
'4xx': {
|
'4xx': {
|
||||||
error: { type: 'string' },
|
error: { type: 'string' },
|
||||||
message: { type: 'string' }
|
message: { type: 'string' }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
getPlaylistController
|
||||||
},
|
|
||||||
checkLoginController
|
|
||||||
),
|
|
||||||
server.post(
|
|
||||||
'/playlist',
|
|
||||||
{
|
|
||||||
schema: {
|
|
||||||
response: {
|
|
||||||
'4xx': {
|
|
||||||
error: { type: 'string' },
|
|
||||||
message: { type: 'string' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addPlaylistController
|
|
||||||
)
|
)
|
||||||
server.get(
|
}
|
||||||
'/playlist',
|
|
||||||
{
|
export default serviceRoutes
|
||||||
schema: {
|
|
||||||
response: {
|
|
||||||
'4xx': {
|
|
||||||
error: { type: 'string' },
|
|
||||||
message: { type: 'string' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getPlaylistController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default serviceRoutes
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,38 +1,38 @@
|
|||||||
type ErrorType = {
|
type ErrorType = {
|
||||||
error: string
|
error: string
|
||||||
} | null
|
} | null
|
||||||
|
|
||||||
export async function useFetch<T>(
|
export async function useFetch<T>(
|
||||||
url: string,
|
url: string,
|
||||||
options: {
|
options: {
|
||||||
type: 'GET' | 'PUT' | 'POST' | 'DELETE'
|
type: 'GET' | 'PUT' | 'POST' | 'DELETE'
|
||||||
header: HeadersInit
|
header: HeadersInit
|
||||||
body: BodyInit
|
body: BodyInit
|
||||||
query?: { [key: string]: string }
|
query?: { [key: string]: string }
|
||||||
credentials?: RequestCredentials
|
credentials?: RequestCredentials
|
||||||
}
|
|
||||||
): Promise<{ data: T | null; error: ErrorType }> {
|
|
||||||
const querystring = new URLSearchParams(options.query)
|
|
||||||
|
|
||||||
const raw = await fetch(`${url}${querystring ? querystring : ''}`, {
|
|
||||||
method: options.type,
|
|
||||||
headers: options.header,
|
|
||||||
body: options.body,
|
|
||||||
credentials: options.credentials
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!raw.ok) {
|
|
||||||
const errorText = await raw.text()
|
|
||||||
let errorData: ErrorType = null
|
|
||||||
try {
|
|
||||||
errorData = JSON.parse(errorText)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing error text:', error)
|
|
||||||
}
|
}
|
||||||
return { data: null, error: errorData }
|
): Promise<{ data: T | null; error: ErrorType }> {
|
||||||
}
|
const querystring = new URLSearchParams(options.query)
|
||||||
|
|
||||||
const data = await raw.json()
|
const raw = await fetch(`${url}${querystring ? querystring : ''}`, {
|
||||||
|
method: options.type,
|
||||||
|
headers: options.header,
|
||||||
|
body: options.body,
|
||||||
|
credentials: options.credentials
|
||||||
|
})
|
||||||
|
|
||||||
return { data: data, error: null }
|
if (!raw.ok) {
|
||||||
|
const errorText = await raw.text()
|
||||||
|
let errorData: ErrorType = null
|
||||||
|
try {
|
||||||
|
errorData = JSON.parse(errorText)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing error text:', error)
|
||||||
|
}
|
||||||
|
return { data: null, error: errorData }
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await raw.json()
|
||||||
|
|
||||||
|
return { data: data, error: null }
|
||||||
}
|
}
|
||||||
|
@ -7,84 +7,84 @@ import { getFFMPEGPath } from './ffmpeg'
|
|||||||
const ffmpegP = getFFMPEGPath()
|
const ffmpegP = getFFMPEGPath()
|
||||||
|
|
||||||
export async function downloadMPDAudio(parts: { filename: string; url: string }[], dir: string, name: string) {
|
export async function downloadMPDAudio(parts: { filename: string; url: string }[], dir: string, name: string) {
|
||||||
const path = await createFolder()
|
const path = await createFolder()
|
||||||
|
|
||||||
const maxParallelDownloads = 5
|
const maxParallelDownloads = 5
|
||||||
const downloadPromises = []
|
const downloadPromises = []
|
||||||
|
|
||||||
for (const [index, part] of parts.entries()) {
|
for (const [index, part] of parts.entries()) {
|
||||||
let retries = 0
|
let retries = 0
|
||||||
|
|
||||||
const downloadPromise = async () => {
|
const downloadPromise = async () => {
|
||||||
let downloadSuccess = false
|
let downloadSuccess = false
|
||||||
while (!downloadSuccess) {
|
while (!downloadSuccess) {
|
||||||
try {
|
try {
|
||||||
const stream = fs.createWriteStream(`${path}/${part.filename}`)
|
const stream = fs.createWriteStream(`${path}/${part.filename}`)
|
||||||
await fetchAndPipe(part.url, stream, index + 1)
|
await fetchAndPipe(part.url, stream, index + 1)
|
||||||
downloadSuccess = true
|
downloadSuccess = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
retries++
|
retries++
|
||||||
console.error(`Failed to download part ${part.filename}, retrying (${retries})...`)
|
console.error(`Failed to download part ${part.filename}, retrying (${retries})...`)
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadPromises.push(downloadPromise())
|
||||||
|
|
||||||
|
if (downloadPromises.length === maxParallelDownloads || index === parts.length - 1) {
|
||||||
|
await Promise.all(downloadPromises)
|
||||||
|
downloadPromises.length = 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadPromises.push(downloadPromise())
|
return await mergePartsAudio(parts, path, dir, name)
|
||||||
|
|
||||||
if (downloadPromises.length === maxParallelDownloads || index === parts.length - 1) {
|
|
||||||
await Promise.all(downloadPromises)
|
|
||||||
downloadPromises.length = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await mergePartsAudio(parts, path, dir, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAndPipe(url: string, stream: fs.WriteStream, index: number) {
|
async function fetchAndPipe(url: string, stream: fs.WriteStream, index: number) {
|
||||||
const { body } = await fetch(url)
|
const { body } = await fetch(url)
|
||||||
const readableStream = Readable.from(body as any)
|
const readableStream = Readable.from(body as any)
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
readableStream
|
readableStream
|
||||||
.pipe(stream)
|
.pipe(stream)
|
||||||
.on('finish', () => {
|
.on('finish', () => {
|
||||||
console.log(`Fragment ${index} downloaded`)
|
console.log(`Fragment ${index} downloaded`)
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
.on('error', (error) => {
|
.on('error', (error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mergePartsAudio(parts: { filename: string; url: string }[], tmp: string, dir: string, name: string) {
|
async function mergePartsAudio(parts: { filename: string; url: string }[], tmp: string, dir: string, name: string) {
|
||||||
try {
|
try {
|
||||||
const list: Array<string> = []
|
const list: Array<string> = []
|
||||||
|
|
||||||
for (const [index, part] of parts.entries()) {
|
for (const [index, part] of parts.entries()) {
|
||||||
list.push(`${tmp}/${part.filename}`)
|
list.push(`${tmp}/${part.filename}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const concatenatedFile = `${tmp}/main.m4s`
|
const concatenatedFile = `${tmp}/main.m4s`
|
||||||
await concatenateTSFiles(list, concatenatedFile)
|
await concatenateTSFiles(list, concatenatedFile)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!ffmpegP.ffmpeg || !ffmpegP.ffprobe) return
|
if (!ffmpegP.ffmpeg || !ffmpegP.ffprobe) return
|
||||||
Ffmpeg()
|
Ffmpeg()
|
||||||
.setFfmpegPath(ffmpegP.ffmpeg)
|
.setFfmpegPath(ffmpegP.ffmpeg)
|
||||||
.setFfprobePath(ffmpegP.ffprobe)
|
.setFfprobePath(ffmpegP.ffprobe)
|
||||||
.input(concatenatedFile)
|
.input(concatenatedFile)
|
||||||
.outputOptions('-c copy')
|
.outputOptions('-c copy')
|
||||||
.save(`${dir}/${name}.aac`)
|
.save(`${dir}/${name}.aac`)
|
||||||
.on('end', async () => {
|
.on('end', async () => {
|
||||||
console.log('Merging finished')
|
console.log('Merging finished')
|
||||||
await deleteFolder(tmp)
|
await deleteFolder(tmp)
|
||||||
|
|
||||||
return resolve(`${dir}/${name}.aac`)
|
return resolve(`${dir}/${name}.aac`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
} catch (error) {
|
||||||
} catch (error) {
|
console.error('Error merging parts:', error)
|
||||||
console.error('Error merging parts:', error)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,37 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
export async function concatenateTSFiles(inputFiles: Array<string>, outputFile: string) {
|
export async function concatenateTSFiles(inputFiles: Array<string>, outputFile: string) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const writeStream = fs.createWriteStream(outputFile)
|
const writeStream = fs.createWriteStream(outputFile)
|
||||||
|
|
||||||
writeStream.on('error', (error) => {
|
writeStream.on('error', (error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
writeStream.on('finish', () => {
|
||||||
|
console.log('TS files concatenated successfully!')
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
const processNextFile = (index: number) => {
|
||||||
|
if (index >= inputFiles.length) {
|
||||||
|
writeStream.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const readStream = fs.createReadStream(inputFiles[index])
|
||||||
|
|
||||||
|
readStream.on('error', (error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
readStream.pipe(writeStream, { end: false })
|
||||||
|
|
||||||
|
readStream.on('end', () => {
|
||||||
|
processNextFile(index + 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextFile(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
writeStream.on('finish', () => {
|
|
||||||
console.log('TS files concatenated successfully!')
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
|
|
||||||
const processNextFile = (index: number) => {
|
|
||||||
if (index >= inputFiles.length) {
|
|
||||||
writeStream.end()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const readStream = fs.createReadStream(inputFiles[index])
|
|
||||||
|
|
||||||
readStream.on('error', (error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
|
|
||||||
readStream.pipe(writeStream, { end: false })
|
|
||||||
|
|
||||||
readStream.on('end', () => {
|
|
||||||
processNextFile(index + 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
processNextFile(0)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -6,22 +6,22 @@ const appPath = app.getAppPath()
|
|||||||
const resourcesPath = path.dirname(appPath)
|
const resourcesPath = path.dirname(appPath)
|
||||||
const ffmpegPath = path.join(resourcesPath, 'ffmpeg')
|
const ffmpegPath = path.join(resourcesPath, 'ffmpeg')
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFFMPEGPath() {
|
export function getFFMPEGPath() {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const ffmpeg = process.env.FFMPEG_PATH
|
const ffmpeg = process.env.FFMPEG_PATH
|
||||||
const ffprobe = process.env.FFPROBE_PATH
|
const ffprobe = process.env.FFPROBE_PATH
|
||||||
|
|
||||||
console.log(ffmpeg)
|
console.log(ffmpeg)
|
||||||
console.log(ffprobe)
|
console.log(ffprobe)
|
||||||
|
|
||||||
return { ffmpeg: ffmpeg, ffprobe: ffprobe }
|
return { ffmpeg: ffmpeg, ffprobe: ffprobe }
|
||||||
} else {
|
} else {
|
||||||
const ffmpeg = path.join(ffmpegPath, 'ffmpeg.exe')
|
const ffmpeg = path.join(ffmpegPath, 'ffmpeg.exe')
|
||||||
const ffprobe = path.join(ffmpegPath, 'ffprobe.exe')
|
const ffprobe = path.join(ffmpegPath, 'ffprobe.exe')
|
||||||
|
|
||||||
return { ffmpeg: ffmpeg, ffprobe: ffprobe }
|
return { ffmpeg: ffmpeg, ffprobe: ffprobe }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,71 +3,71 @@ import { app } from 'electron'
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
export async function createFolder() {
|
export async function createFolder() {
|
||||||
const tempFolderPath = path.join(app.getPath('documents'), `crd-tmp-${(Math.random() + 1).toString(36).substring(2)}`)
|
const tempFolderPath = path.join(app.getPath('documents'), `crd-tmp-${(Math.random() + 1).toString(36).substring(2)}`)
|
||||||
try {
|
try {
|
||||||
await fs.promises.mkdir(tempFolderPath, { recursive: true })
|
await fs.promises.mkdir(tempFolderPath, { recursive: true })
|
||||||
return tempFolderPath
|
return tempFolderPath
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating temporary folder:', error)
|
console.error('Error creating temporary folder:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDirectoryExistence(dir: string) {
|
export async function checkDirectoryExistence(dir: string) {
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(dir)
|
await fs.promises.access(dir)
|
||||||
console.log(`Directory ${dir} exists.`)
|
console.log(`Directory ${dir} exists.`)
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Directory ${dir} does not exist.`)
|
console.log(`Directory ${dir} does not exist.`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createFolderName(name: string, dir: string) {
|
export async function createFolderName(name: string, dir: string) {
|
||||||
var folderPath
|
var folderPath
|
||||||
|
|
||||||
const dirExists = await checkDirectoryExistence(dir)
|
const dirExists = await checkDirectoryExistence(dir)
|
||||||
|
|
||||||
if (dirExists) {
|
if (dirExists) {
|
||||||
folderPath = path.join(dir, name)
|
folderPath = path.join(dir, name)
|
||||||
} else {
|
} else {
|
||||||
folderPath = path.join(app.getPath('documents'), name)
|
folderPath = path.join(app.getPath('documents'), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(folderPath)
|
await fs.promises.access(folderPath)
|
||||||
return folderPath
|
return folderPath
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
try {
|
try {
|
||||||
await fs.promises.mkdir(folderPath, { recursive: true })
|
await fs.promises.mkdir(folderPath, { recursive: true })
|
||||||
return folderPath
|
return folderPath
|
||||||
} catch (mkdirError) {
|
} catch (mkdirError) {
|
||||||
console.error('Error creating season folder:', mkdirError)
|
console.error('Error creating season folder:', mkdirError)
|
||||||
throw mkdirError
|
throw mkdirError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteFolder(folderPath: string) {
|
export async function deleteFolder(folderPath: string) {
|
||||||
fs.rmSync(folderPath, { recursive: true, force: true })
|
fs.rmSync(folderPath, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTemporaryFolders() {
|
export async function deleteTemporaryFolders() {
|
||||||
const documentsPath = app.getPath('documents');
|
const documentsPath = app.getPath('documents')
|
||||||
const folderPrefix = 'crd-tmp-';
|
const folderPrefix = 'crd-tmp-'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = await fs.promises.readdir(documentsPath);
|
const files = await fs.promises.readdir(documentsPath)
|
||||||
const tempFolders = files.filter(file => file.startsWith(folderPrefix));
|
const tempFolders = files.filter((file) => file.startsWith(folderPrefix))
|
||||||
|
|
||||||
for (const folder of tempFolders) {
|
for (const folder of tempFolders) {
|
||||||
const folderPath = path.join(documentsPath, folder);
|
const folderPath = path.join(documentsPath, folder)
|
||||||
await deleteFolder(folderPath);
|
await deleteFolder(folderPath)
|
||||||
console.log(`Temporary folder ${folder} deleted.`);
|
console.log(`Temporary folder ${folder} deleted.`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting temporary folders:', error)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting temporary folders:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,144 +5,144 @@ import { finished } from 'stream/promises'
|
|||||||
import CryptoJS from 'crypto-js'
|
import CryptoJS from 'crypto-js'
|
||||||
|
|
||||||
export async function downloadCRSub(
|
export async function downloadCRSub(
|
||||||
sub: {
|
sub: {
|
||||||
format: string
|
format: string
|
||||||
language: string
|
language: string
|
||||||
url: string
|
url: string
|
||||||
isDub: boolean
|
isDub: boolean
|
||||||
},
|
},
|
||||||
dir: string,
|
dir: string,
|
||||||
qual: 1080 | 720 | 480 | 360 | 240
|
qual: 1080 | 720 | 480 | 360 | 240
|
||||||
) {
|
) {
|
||||||
const path = `${dir}/${sub.language}${sub.isDub ? `-FORCED` : ''}.${sub.format}`
|
const path = `${dir}/${sub.language}${sub.isDub ? `-FORCED` : ''}.${sub.format}`
|
||||||
var qualX
|
var qualX
|
||||||
var qualY
|
var qualY
|
||||||
|
|
||||||
switch (qual) {
|
switch (qual) {
|
||||||
case 1080:
|
case 1080:
|
||||||
qualX = 1920
|
qualX = 1920
|
||||||
qualY = 1080
|
qualY = 1080
|
||||||
break
|
break
|
||||||
case 720:
|
case 720:
|
||||||
qualX = 1280
|
qualX = 1280
|
||||||
qualY = 720
|
qualY = 720
|
||||||
break
|
break
|
||||||
case 480:
|
case 480:
|
||||||
qualX = 720
|
qualX = 720
|
||||||
qualY = 480
|
qualY = 480
|
||||||
break
|
break
|
||||||
case 360:
|
case 360:
|
||||||
qualX = 640
|
qualX = 640
|
||||||
qualY = 360
|
qualY = 360
|
||||||
break
|
break
|
||||||
case 240:
|
case 240:
|
||||||
qualX = 426
|
qualX = 426
|
||||||
qualY = 240
|
qualY = 240
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = fs.createWriteStream(path)
|
const stream = fs.createWriteStream(path)
|
||||||
const response = await fetch(sub.url)
|
const response = await fetch(sub.url)
|
||||||
|
|
||||||
var parsedASS = parse(await response.text())
|
var parsedASS = parse(await response.text())
|
||||||
|
|
||||||
parsedASS.info['Original Script'] = 'crd [https://github.com/stratuma/]'
|
parsedASS.info['Original Script'] = 'crd [https://github.com/stratuma/]'
|
||||||
|
|
||||||
for (const s of parsedASS.styles.style) {
|
for (const s of parsedASS.styles.style) {
|
||||||
;(s.Fontsize = String(Math.round((parseInt(s.Fontsize) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
;(s.Fontsize = String(Math.round((parseInt(s.Fontsize) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
||||||
(s.Outline = String(Math.round((parseInt(s.Outline) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
(s.Outline = String(Math.round((parseInt(s.Outline) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
||||||
(s.MarginL = String(Math.round((parseInt(s.MarginL) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
(s.MarginL = String(Math.round((parseInt(s.MarginL) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
||||||
(s.MarginR = String(Math.round((parseInt(s.MarginR) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
(s.MarginR = String(Math.round((parseInt(s.MarginR) / parseInt(parsedASS.info.PlayResY)) * qualY))),
|
||||||
(s.MarginV = String(Math.round((parseInt(s.MarginV) / parseInt(parsedASS.info.PlayResY)) * qualY)))
|
(s.MarginV = String(Math.round((parseInt(s.MarginV) / parseInt(parsedASS.info.PlayResY)) * qualY)))
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedASS.info.PlayResX = String(qualX)
|
parsedASS.info.PlayResX = String(qualX)
|
||||||
parsedASS.info.PlayResY = String(qualY)
|
parsedASS.info.PlayResY = String(qualY)
|
||||||
|
|
||||||
const fixed = stringify(parsedASS)
|
const fixed = stringify(parsedASS)
|
||||||
|
|
||||||
const resampledSubs = resamplePOSSubtitle(fixed, 640, 360, qualX, qualY)
|
const resampledSubs = resamplePOSSubtitle(fixed, 640, 360, qualX, qualY)
|
||||||
|
|
||||||
const readableStream = Readable.from([resampledSubs])
|
const readableStream = Readable.from([resampledSubs])
|
||||||
|
|
||||||
await finished(readableStream.pipe(stream))
|
await finished(readableStream.pipe(stream))
|
||||||
console.log(`Sub ${sub.language}.${sub.format} downloaded`)
|
console.log(`Sub ${sub.language}.${sub.format} downloaded`)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
function resamplePOSSubtitle(subtitle: string, ox: number, oy: number, nx: number, ny: number) {
|
function resamplePOSSubtitle(subtitle: string, ox: number, oy: number, nx: number, ny: number) {
|
||||||
let lines = subtitle.split('\n')
|
let lines = subtitle.split('\n')
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
let line = lines[i]
|
let line = lines[i]
|
||||||
|
|
||||||
if (line.includes('pos(')) {
|
if (line.includes('pos(')) {
|
||||||
let posMatches = line.matchAll(/pos\((-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)\)/g)
|
let posMatches = line.matchAll(/pos\((-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)\)/g)
|
||||||
for (let posMatch of posMatches) {
|
for (let posMatch of posMatches) {
|
||||||
let oldX = parseInt(posMatch[1])
|
let oldX = parseInt(posMatch[1])
|
||||||
let oldY = parseInt(posMatch[2])
|
let oldY = parseInt(posMatch[2])
|
||||||
|
|
||||||
let newX = Math.round((oldX / ox) * nx)
|
let newX = Math.round((oldX / ox) * nx)
|
||||||
let newY = Math.round((oldY / oy) * ny)
|
let newY = Math.round((oldY / oy) * ny)
|
||||||
|
|
||||||
let newPos = `pos(${newX},${newY})`
|
let newPos = `pos(${newX},${newY})`
|
||||||
|
|
||||||
line = line.replace(posMatch[0], newPos)
|
line = line.replace(posMatch[0], newPos)
|
||||||
}
|
}
|
||||||
lines[i] = line
|
lines[i] = line
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.includes('move(')) {
|
||||||
|
let posMatches = line.matchAll(/move\((-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g)
|
||||||
|
for (let posMatch of posMatches) {
|
||||||
|
let fromX = parseInt(posMatch[1])
|
||||||
|
let fromY = parseInt(posMatch[2])
|
||||||
|
let toX = parseInt(posMatch[3])
|
||||||
|
let toY = parseInt(posMatch[4])
|
||||||
|
let time1 = parseInt(posMatch[5])
|
||||||
|
let time2 = parseInt(posMatch[6])
|
||||||
|
|
||||||
|
let newFromX = Math.round((fromX / ox) * nx)
|
||||||
|
let newFromY = Math.round((fromY / oy) * ny)
|
||||||
|
let newToX = Math.round((toX / ox) * nx)
|
||||||
|
let newToY = Math.round((toY / oy) * ny)
|
||||||
|
|
||||||
|
let newMov = `move(${newFromX},${newFromY},${newToX},${newToY},${time1},${time2})`
|
||||||
|
|
||||||
|
line = line.replace(posMatch[0], newMov)
|
||||||
|
}
|
||||||
|
lines[i] = line
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.includes('\\fs')) {
|
||||||
|
let posMatches = line.matchAll(/\\fs(-?\d+(?:\.\d+)?)/g)
|
||||||
|
for (let posMatch of posMatches) {
|
||||||
|
let font = parseInt(posMatch[1])
|
||||||
|
let newFontSize = Math.round((font / oy) * ny)
|
||||||
|
let newFont = `\\fs${newFontSize}`
|
||||||
|
line = line.replace(posMatch[0], newFont)
|
||||||
|
}
|
||||||
|
lines[i] = line
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.includes('\\bord')) {
|
||||||
|
let posMatches = line.matchAll(/\\bord(-?\d+(?:\.\d+)?)/g)
|
||||||
|
for (let posMatch of posMatches) {
|
||||||
|
let oldBord = parseInt(posMatch[1])
|
||||||
|
let newBord = Math.round((oldBord / oy) * ny)
|
||||||
|
let bord = `\\bord${newBord}`
|
||||||
|
line = line.replace(posMatch[0], bord)
|
||||||
|
}
|
||||||
|
lines[i] = line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.includes('move(')) {
|
return lines.join('\n')
|
||||||
let posMatches = line.matchAll(/move\((-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g)
|
|
||||||
for (let posMatch of posMatches) {
|
|
||||||
let fromX = parseInt(posMatch[1])
|
|
||||||
let fromY = parseInt(posMatch[2])
|
|
||||||
let toX = parseInt(posMatch[3])
|
|
||||||
let toY = parseInt(posMatch[4])
|
|
||||||
let time1 = parseInt(posMatch[5])
|
|
||||||
let time2 = parseInt(posMatch[6])
|
|
||||||
|
|
||||||
let newFromX = Math.round((fromX / ox) * nx)
|
|
||||||
let newFromY = Math.round((fromY / oy) * ny)
|
|
||||||
let newToX = Math.round((toX / ox) * nx)
|
|
||||||
let newToY = Math.round((toY / oy) * ny)
|
|
||||||
|
|
||||||
let newMov = `move(${newFromX},${newFromY},${newToX},${newToY},${time1},${time2})`
|
|
||||||
|
|
||||||
line = line.replace(posMatch[0], newMov)
|
|
||||||
}
|
|
||||||
lines[i] = line
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.includes('\\fs')) {
|
|
||||||
let posMatches = line.matchAll(/\\fs(-?\d+(?:\.\d+)?)/g)
|
|
||||||
for (let posMatch of posMatches) {
|
|
||||||
let font = parseInt(posMatch[1])
|
|
||||||
let newFontSize = Math.round((font / oy) * ny)
|
|
||||||
let newFont = `\\fs${newFontSize}`
|
|
||||||
line = line.replace(posMatch[0], newFont)
|
|
||||||
}
|
|
||||||
lines[i] = line
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.includes('\\bord')) {
|
|
||||||
let posMatches = line.matchAll(/\\bord(-?\d+(?:\.\d+)?)/g)
|
|
||||||
for (let posMatch of posMatches) {
|
|
||||||
let oldBord = parseInt(posMatch[1])
|
|
||||||
let newBord = Math.round((oldBord / oy) * ny)
|
|
||||||
let bord = `\\bord${newBord}`
|
|
||||||
line = line.replace(posMatch[0], bord)
|
|
||||||
}
|
|
||||||
lines[i] = line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join('\n')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadADNSub(link: string, dir: string, secret: string, language: string) {
|
export async function downloadADNSub(link: string, dir: string, secret: string, language: string) {
|
||||||
var templateASS = `[Script Info]
|
var templateASS = `[Script Info]
|
||||||
; Script generated by Aegisub 3.2.2
|
; Script generated by Aegisub 3.2.2
|
||||||
; http://www.aegisub.org/
|
; http://www.aegisub.org/
|
||||||
Title: Deutsch
|
Title: Deutsch
|
||||||
@ -167,135 +167,135 @@ Style: Default,Arial,56,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100
|
|||||||
|
|
||||||
[Events]
|
[Events]
|
||||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`
|
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`
|
||||||
const path = `${dir}/${language}.ass`
|
const path = `${dir}/${language}.ass`
|
||||||
|
|
||||||
const stream = fs.createWriteStream(path)
|
const stream = fs.createWriteStream(path)
|
||||||
const subURLFetch = await fetch(link)
|
const subURLFetch = await fetch(link)
|
||||||
const subURL: {
|
const subURL: {
|
||||||
location: string
|
location: string
|
||||||
} = JSON.parse(await subURLFetch.text())
|
} = JSON.parse(await subURLFetch.text())
|
||||||
|
|
||||||
const rawSubsFetch = await fetch(subURL.location)
|
const rawSubsFetch = await fetch(subURL.location)
|
||||||
const rawSubs = await rawSubsFetch.text()
|
const rawSubs = await rawSubsFetch.text()
|
||||||
|
|
||||||
const subs = await ADNparseSub(rawSubs, secret)
|
const subs = await ADNparseSub(rawSubs, secret)
|
||||||
|
|
||||||
const parsedSubs: {
|
const parsedSubs: {
|
||||||
vde: Array<{
|
vde: Array<{
|
||||||
startTime: number
|
startTime: number
|
||||||
endTime: number
|
endTime: number
|
||||||
positionAligh: string
|
positionAligh: string
|
||||||
lineAlign: string
|
lineAlign: string
|
||||||
text: string
|
text: string
|
||||||
}>
|
}>
|
||||||
vostde: Array<{
|
vostde: Array<{
|
||||||
startTime: number
|
startTime: number
|
||||||
endTime: number
|
endTime: number
|
||||||
positionAligh: string
|
positionAligh: string
|
||||||
lineAlign: string
|
lineAlign: string
|
||||||
text: string
|
text: string
|
||||||
}>
|
}>
|
||||||
vostf: Array<{
|
vostf: Array<{
|
||||||
startTime: number
|
startTime: number
|
||||||
endTime: number
|
endTime: number
|
||||||
positionAligh: string
|
positionAligh: string
|
||||||
lineAlign: string
|
lineAlign: string
|
||||||
text: string
|
text: string
|
||||||
}>
|
}>
|
||||||
vf: Array<{
|
vf: Array<{
|
||||||
startTime: number
|
startTime: number
|
||||||
endTime: number
|
endTime: number
|
||||||
positionAligh: string
|
positionAligh: string
|
||||||
lineAlign: string
|
lineAlign: string
|
||||||
text: string
|
text: string
|
||||||
}>
|
}>
|
||||||
} = await JSON.parse(subs)
|
} = await JSON.parse(subs)
|
||||||
|
|
||||||
// if (parsedSubs.vde) {
|
// if (parsedSubs.vde) {
|
||||||
// for (const s of parsedSubs.vde) {
|
// for (const s of parsedSubs.vde) {
|
||||||
// const convertedStart = convertToTimeFormat(s.startTime)
|
// const convertedStart = convertToTimeFormat(s.startTime)
|
||||||
// const convertedEnd = convertToTimeFormat(s.endTime)
|
// const convertedEnd = convertToTimeFormat(s.endTime)
|
||||||
|
|
||||||
// templateASS =
|
// templateASS =
|
||||||
// templateASS + `Dialogue: 0,${convertedStart},${convertedEnd},Default,,0,0,0,,${s.text.replace('\n', '\\N').replace('<i>', '{\\i1}').replace('</i>', '{\\i0}')}\n`
|
// templateASS + `Dialogue: 0,${convertedStart},${convertedEnd},Default,,0,0,0,,${s.text.replace('\n', '\\N').replace('<i>', '{\\i1}').replace('</i>', '{\\i0}')}\n`
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (parsedSubs.vostde) {
|
if (parsedSubs.vostde) {
|
||||||
for (const s of parsedSubs.vostde) {
|
for (const s of parsedSubs.vostde) {
|
||||||
const convertedStart = convertToTimeFormat(s.startTime)
|
const convertedStart = convertToTimeFormat(s.startTime)
|
||||||
const convertedEnd = convertToTimeFormat(s.endTime)
|
const convertedEnd = convertToTimeFormat(s.endTime)
|
||||||
|
|
||||||
templateASS =
|
templateASS =
|
||||||
templateASS + `Dialogue: 0,${convertedStart},${convertedEnd},Default,,0,0,0,,${s.text.replace('\n', '\\N').replace('<i>', '{\\i1}').replace('</i>', '{\\i0}')}\n`
|
templateASS + `Dialogue: 0,${convertedStart},${convertedEnd},Default,,0,0,0,,${s.text.replace('\n', '\\N').replace('<i>', '{\\i1}').replace('</i>', '{\\i0}')}\n`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedSubs.vostf) {
|
if (parsedSubs.vostf) {
|
||||||
for (const s of parsedSubs.vostf) {
|
for (const s of parsedSubs.vostf) {
|
||||||
const convertedStart = convertToTimeFormat(s.startTime)
|
const convertedStart = convertToTimeFormat(s.startTime)
|
||||||
const convertedEnd = convertToTimeFormat(s.endTime)
|
const convertedEnd = convertToTimeFormat(s.endTime)
|
||||||
|
|
||||||
templateASS =
|
templateASS =
|
||||||
templateASS + `Dialogue: 0,${convertedStart},${convertedEnd},Default,,0,0,0,,${s.text.replace('\n', '\\N').replace('<i>', '{\\i1}').replace('</i>', '{\\i0}')}\n`
|
templateASS + `Dialogue: 0,${convertedStart},${convertedEnd},Default,,0,0,0,,${s.text.replace('\n', '\\N').replace('<i>', '{\\i1}').replace('</i>', '{\\i0}')}\n`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Disabling Changing ASS because still broken in vlc
|
// Disabling Changing ASS because still broken in vlc
|
||||||
|
|
||||||
// parsedASS.info.PlayResX = "1920";
|
// parsedASS.info.PlayResX = "1920";
|
||||||
// parsedASS.info.PlayResY = "1080";
|
// parsedASS.info.PlayResY = "1080";
|
||||||
|
|
||||||
// for (const s of parsedASS.styles.style) {
|
// for (const s of parsedASS.styles.style) {
|
||||||
// (s.Fontsize = "54"), (s.Outline = "4");
|
// (s.Fontsize = "54"), (s.Outline = "4");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// const fixed = stringify(parsedASS)
|
// const fixed = stringify(parsedASS)
|
||||||
|
|
||||||
const readableStream = Readable.from([templateASS])
|
const readableStream = Readable.from([templateASS])
|
||||||
|
|
||||||
await finished(readableStream.pipe(stream))
|
await finished(readableStream.pipe(stream))
|
||||||
console.log(`Sub downloaded`)
|
console.log(`Sub downloaded`)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToTimeFormat(time: number) {
|
function convertToTimeFormat(time: number) {
|
||||||
var seconds: number | string = Math.floor(time)
|
var seconds: number | string = Math.floor(time)
|
||||||
var milliseconds = Math.round((time - seconds) * 1000)
|
var milliseconds = Math.round((time - seconds) * 1000)
|
||||||
|
|
||||||
var hours: number | string = Math.floor(seconds / 3600)
|
var hours: number | string = Math.floor(seconds / 3600)
|
||||||
var minutes: number | string = Math.floor((seconds % 3600) / 60)
|
var minutes: number | string = Math.floor((seconds % 3600) / 60)
|
||||||
seconds = seconds % 60
|
seconds = seconds % 60
|
||||||
|
|
||||||
hours = String(hours).padStart(2, '0')
|
hours = String(hours).padStart(2, '0')
|
||||||
minutes = String(minutes).padStart(2, '0')
|
minutes = String(minutes).padStart(2, '0')
|
||||||
seconds = String(seconds).padStart(2, '0')
|
seconds = String(seconds).padStart(2, '0')
|
||||||
|
|
||||||
milliseconds = Math.round(milliseconds / 10)
|
milliseconds = Math.round(milliseconds / 10)
|
||||||
|
|
||||||
var formattedMilliseconds = milliseconds < 10 ? '0' + milliseconds : milliseconds
|
var formattedMilliseconds = milliseconds < 10 ? '0' + milliseconds : milliseconds
|
||||||
|
|
||||||
var formattedTime = hours + ':' + minutes + ':' + seconds + '.' + formattedMilliseconds
|
var formattedTime = hours + ':' + minutes + ':' + seconds + '.' + formattedMilliseconds
|
||||||
return formattedTime
|
return formattedTime
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ADNparseSub(raw: string, secret: string) {
|
export async function ADNparseSub(raw: string, secret: string) {
|
||||||
var key = secret + '7fac1178830cfe0c'
|
var key = secret + '7fac1178830cfe0c'
|
||||||
|
|
||||||
console.log(key)
|
console.log(key)
|
||||||
|
|
||||||
var parsedSubtitle = CryptoJS.enc.Base64.parse(raw.substring(0, 24))
|
var parsedSubtitle = CryptoJS.enc.Base64.parse(raw.substring(0, 24))
|
||||||
var sec = CryptoJS.enc.Hex.parse(key)
|
var sec = CryptoJS.enc.Hex.parse(key)
|
||||||
var som = raw.substring(24)
|
var som = raw.substring(24)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fuck You ADN
|
// Fuck You ADN
|
||||||
var decrypted: any = CryptoJS.AES.decrypt(som, sec, { iv: parsedSubtitle })
|
var decrypted: any = CryptoJS.AES.decrypt(som, sec, { iv: parsedSubtitle })
|
||||||
decrypted = decrypted.toString(CryptoJS.enc.Utf8)
|
decrypted = decrypted.toString(CryptoJS.enc.Utf8)
|
||||||
return decrypted
|
return decrypted
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error decrypting subtitles:', error)
|
console.error('Error decrypting subtitles:', error)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,145 +1,145 @@
|
|||||||
export interface ADNPlayerConfig {
|
export interface ADNPlayerConfig {
|
||||||
player: {
|
player: {
|
||||||
image: string
|
image: string
|
||||||
options: {
|
options: {
|
||||||
user: {
|
user: {
|
||||||
hasAccess: true
|
hasAccess: true
|
||||||
profileId: number
|
profileId: number
|
||||||
refreshToken: string
|
refreshToken: string
|
||||||
refreshTokenUrl: string
|
refreshTokenUrl: string
|
||||||
}
|
}
|
||||||
chromecast: {
|
chromecast: {
|
||||||
appId: string
|
appId: string
|
||||||
refreshTokenUrl: string
|
refreshTokenUrl: string
|
||||||
}
|
}
|
||||||
ios: {
|
ios: {
|
||||||
videoUrl: string
|
videoUrl: string
|
||||||
appUrl: string
|
appUrl: string
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
video: {
|
video: {
|
||||||
startDate: string
|
startDate: string
|
||||||
currentDate: string
|
currentDate: string
|
||||||
available: boolean
|
available: boolean
|
||||||
free: boolean
|
free: boolean
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
dock: Array<string>
|
dock: Array<string>
|
||||||
preference: {
|
preference: {
|
||||||
quality: string
|
quality: string
|
||||||
autoplay: boolean
|
autoplay: boolean
|
||||||
language: string
|
language: string
|
||||||
green: boolean
|
green: boolean
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ADNLink {
|
export interface ADNLink {
|
||||||
links: {
|
links: {
|
||||||
streaming: {
|
streaming: {
|
||||||
vostde: {
|
vostde: {
|
||||||
mobile: string
|
mobile: string
|
||||||
sd: string
|
sd: string
|
||||||
hd: string
|
hd: string
|
||||||
fhd: string
|
fhd: string
|
||||||
auto: string
|
auto: string
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
subtitles: {
|
||||||
|
all: string
|
||||||
|
}
|
||||||
|
history: string
|
||||||
|
nextVideoUrl: string
|
||||||
|
previousVideoUrl: string
|
||||||
}
|
}
|
||||||
subtitles: {
|
video: {
|
||||||
all: string
|
guid: string
|
||||||
|
id: number
|
||||||
|
currentTime: number
|
||||||
|
duration: number
|
||||||
|
url: string
|
||||||
|
image: string
|
||||||
|
tcEpisodeStart: string
|
||||||
|
tcEpisodeEnd: string
|
||||||
|
tcIntroStart: string
|
||||||
|
tcIntroEnd: string
|
||||||
|
tcEndingStart: string
|
||||||
|
tcEndingEnd: string
|
||||||
|
}
|
||||||
|
metadata: {
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
summary: string
|
||||||
|
rating: number
|
||||||
}
|
}
|
||||||
history: string
|
|
||||||
nextVideoUrl: string
|
|
||||||
previousVideoUrl: string
|
|
||||||
}
|
|
||||||
video: {
|
|
||||||
guid: string
|
|
||||||
id: number
|
|
||||||
currentTime: number
|
|
||||||
duration: number
|
|
||||||
url: string
|
|
||||||
image: string
|
|
||||||
tcEpisodeStart: string
|
|
||||||
tcEpisodeEnd: string
|
|
||||||
tcIntroStart: string
|
|
||||||
tcIntroEnd: string
|
|
||||||
tcEndingStart: string
|
|
||||||
tcEndingEnd: string
|
|
||||||
}
|
|
||||||
metadata: {
|
|
||||||
title: string
|
|
||||||
subtitle: string
|
|
||||||
summary: string
|
|
||||||
rating: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ADNEpisode {
|
export interface ADNEpisode {
|
||||||
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: string,
|
releaseDate: string
|
||||||
duration: number,
|
duration: number
|
||||||
url: string,
|
url: string
|
||||||
urlPath: string,
|
urlPath: string
|
||||||
embeddedUrl: string,
|
embeddedUrl: string
|
||||||
languages: Array<string>,
|
languages: Array<string>
|
||||||
qualities: Array<string>,
|
qualities: Array<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: {
|
||||||
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: Array<string>,
|
languages: Array<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: Array<string>,
|
genres: Array<string>
|
||||||
copyright: string,
|
copyright: string
|
||||||
rating: number,
|
rating: number
|
||||||
ratingsCount: number,
|
ratingsCount: number
|
||||||
commentsCount: number,
|
commentsCount: number
|
||||||
qualities: Array<string>,
|
qualities: Array<string>
|
||||||
simulcast: boolean,
|
simulcast: boolean
|
||||||
free: boolean,
|
free: boolean
|
||||||
available: boolean,
|
available: boolean
|
||||||
download: boolean,
|
download: boolean
|
||||||
basedOn: string,
|
basedOn: string
|
||||||
tagline: Array<string>,
|
tagline: Array<string>
|
||||||
firstReleaseYear: string,
|
firstReleaseYear: string
|
||||||
productionStudio: string,
|
productionStudio: string
|
||||||
countryOfOrigin: string,
|
countryOfOrigin: string
|
||||||
productionTeam: Array<{
|
productionTeam: Array<{
|
||||||
role: string,
|
role: string
|
||||||
name: string,
|
name: string
|
||||||
}>,
|
}>
|
||||||
nextVideoReleaseDate: string,
|
nextVideoReleaseDate: string
|
||||||
indexable: boolean
|
indexable: boolean
|
||||||
}
|
}
|
||||||
indexable: boolean
|
indexable: boolean
|
||||||
}
|
}
|
||||||
|
@ -1,146 +1,146 @@
|
|||||||
export interface CrunchySeason {
|
export interface CrunchySeason {
|
||||||
identifier: string
|
identifier: string
|
||||||
description: string
|
description: string
|
||||||
is_simulcast: boolean
|
is_simulcast: boolean
|
||||||
subtitle_locales: Array<string>
|
subtitle_locales: Array<string>
|
||||||
series_id: string
|
series_id: string
|
||||||
id: string
|
id: string
|
||||||
audio_locales: Array<string>
|
audio_locales: Array<string>
|
||||||
title: string
|
title: string
|
||||||
versions: Array<{
|
versions: Array<{
|
||||||
|
audio_locale: string
|
||||||
|
guid: string
|
||||||
|
original: boolean
|
||||||
|
variant: string
|
||||||
|
}>
|
||||||
|
season_sequence_number: number
|
||||||
|
season_number: number
|
||||||
|
maturity_ratings: Array<string>
|
||||||
|
mature_blocked: boolean
|
||||||
|
channel_id: string
|
||||||
|
is_subbed: boolean
|
||||||
audio_locale: string
|
audio_locale: string
|
||||||
guid: string
|
season_display_number: string
|
||||||
original: boolean
|
is_complete: boolean
|
||||||
variant: string
|
season_tags: Array<string>
|
||||||
}>
|
is_mature: boolean
|
||||||
season_sequence_number: number
|
is_dubbed: boolean
|
||||||
season_number: number
|
slug_title: string
|
||||||
maturity_ratings: Array<string>
|
availability_notes: string
|
||||||
mature_blocked: boolean
|
number_of_episodes: boolean
|
||||||
channel_id: string
|
|
||||||
is_subbed: boolean
|
|
||||||
audio_locale: string
|
|
||||||
season_display_number: string
|
|
||||||
is_complete: boolean
|
|
||||||
season_tags: Array<string>
|
|
||||||
is_mature: boolean
|
|
||||||
is_dubbed: boolean
|
|
||||||
slug_title: string
|
|
||||||
availability_notes: string
|
|
||||||
number_of_episodes: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchySeasons extends Array<CrunchySeason> {}
|
export interface CrunchySeasons extends Array<CrunchySeason> {}
|
||||||
|
|
||||||
export interface CrunchyEpisode {
|
export interface CrunchyEpisode {
|
||||||
closed_captions_available: boolean
|
closed_captions_available: boolean
|
||||||
availability_notes: string
|
availability_notes: string
|
||||||
next_episode_title: string
|
next_episode_title: string
|
||||||
upload_date: string
|
upload_date: string
|
||||||
versions: Array<{
|
versions: Array<{
|
||||||
audio_locale: string
|
audio_locale: string
|
||||||
guid: string
|
guid: string
|
||||||
|
is_premium_only: boolean
|
||||||
|
media_guid: string
|
||||||
|
original: boolean
|
||||||
|
season_guid: string
|
||||||
|
variant: string
|
||||||
|
}>
|
||||||
|
season_slug_title: string
|
||||||
|
series_title: string
|
||||||
|
season_title: string
|
||||||
|
sequence_number: number
|
||||||
|
maturity_ratings: Array<string>
|
||||||
|
slug_title: string
|
||||||
is_premium_only: boolean
|
is_premium_only: boolean
|
||||||
media_guid: string
|
availability_ends: string
|
||||||
original: boolean
|
identifier: string
|
||||||
season_guid: string
|
recent_variant: string
|
||||||
variant: string
|
free_available_date: string
|
||||||
}>
|
subtitle_locales: Array<string>
|
||||||
season_slug_title: string
|
series_id: string
|
||||||
series_title: string
|
mature_blocked: boolean
|
||||||
season_title: string
|
duration_ms: number
|
||||||
sequence_number: number
|
availability_starts: string
|
||||||
maturity_ratings: Array<string>
|
audio_locale: string
|
||||||
slug_title: string
|
images: {
|
||||||
is_premium_only: boolean
|
thumbnail: Array<
|
||||||
availability_ends: string
|
Array<{
|
||||||
identifier: string
|
height: number
|
||||||
recent_variant: string
|
source: string
|
||||||
free_available_date: string
|
type: string
|
||||||
subtitle_locales: Array<string>
|
width: number
|
||||||
series_id: string
|
}>
|
||||||
mature_blocked: boolean
|
>
|
||||||
duration_ms: number
|
}
|
||||||
availability_starts: string
|
season_sequence_number: number
|
||||||
audio_locale: string
|
season_id: string
|
||||||
images: {
|
episode_number: number
|
||||||
thumbnail: Array<
|
listing_id: string
|
||||||
Array<{
|
available_date: string
|
||||||
height: number
|
channel_id: string
|
||||||
source: string
|
season_number: number
|
||||||
type: string
|
hd_flag: boolean
|
||||||
width: number
|
recent_audio_locale: string
|
||||||
}>
|
available_offline: boolean
|
||||||
>
|
episode: string
|
||||||
}
|
is_subbed: boolean
|
||||||
season_sequence_number: number
|
media_type: string
|
||||||
season_id: string
|
is_clip: boolean
|
||||||
episode_number: number
|
title: string
|
||||||
listing_id: string
|
streams_link: string
|
||||||
available_date: string
|
slug: string
|
||||||
channel_id: string
|
id: string
|
||||||
season_number: number
|
production_episode_id: string
|
||||||
hd_flag: boolean
|
is_dubbed: boolean
|
||||||
recent_audio_locale: string
|
next_episode_id: string
|
||||||
available_offline: boolean
|
series_slug_title: string
|
||||||
episode: string
|
season_tags: Array<string>
|
||||||
is_subbed: boolean
|
premium_date: string
|
||||||
media_type: string
|
is_mature: boolean
|
||||||
is_clip: boolean
|
premium_available_date: string
|
||||||
title: string
|
description: string
|
||||||
streams_link: string
|
episode_air_date: string
|
||||||
slug: string
|
eligible_region: string
|
||||||
id: string
|
|
||||||
production_episode_id: string
|
|
||||||
is_dubbed: boolean
|
|
||||||
next_episode_id: string
|
|
||||||
series_slug_title: string
|
|
||||||
season_tags: Array<string>
|
|
||||||
premium_date: string
|
|
||||||
is_mature: boolean
|
|
||||||
premium_available_date: string
|
|
||||||
description: string
|
|
||||||
episode_air_date: string
|
|
||||||
eligible_region: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrunchyEpisodes extends Array<CrunchyEpisode> {}
|
export interface CrunchyEpisodes extends Array<CrunchyEpisode> {}
|
||||||
|
|
||||||
export interface VideoPlaylist {
|
export interface VideoPlaylist {
|
||||||
assetId: number
|
assetId: number
|
||||||
audioLocale: string
|
audioLocale: string
|
||||||
bifs: string
|
bifs: string
|
||||||
burnedInLocale: string
|
burnedInLocale: string
|
||||||
captions: string
|
captions: string
|
||||||
hardSubs: Array<{
|
hardSubs: Array<{
|
||||||
hlang: string
|
hlang: string
|
||||||
|
url: string
|
||||||
|
quality: string
|
||||||
|
}>
|
||||||
|
playbackType: string
|
||||||
|
session: {
|
||||||
|
renewSeconds: number
|
||||||
|
noNetworkRetryIntervalSeconds: number
|
||||||
|
noNetworkTimeoutSeconds: number
|
||||||
|
maximumPauseSeconds: number
|
||||||
|
endOfVideoUnloadSeconds: number
|
||||||
|
sessionExpirationSeconds: number
|
||||||
|
usesStreamLimits: boolean
|
||||||
|
}
|
||||||
|
subtitles: Array<{
|
||||||
|
format: string
|
||||||
|
language: string
|
||||||
|
url: string
|
||||||
|
}>
|
||||||
|
token: string
|
||||||
url: string
|
url: string
|
||||||
quality: string
|
versions: Array<{
|
||||||
}>
|
audio_locale: string
|
||||||
playbackType: string
|
guid: string
|
||||||
session: {
|
is_premium_only: boolean
|
||||||
renewSeconds: number
|
media_guid: string
|
||||||
noNetworkRetryIntervalSeconds: number
|
original: boolean
|
||||||
noNetworkTimeoutSeconds: number
|
season_guid: string
|
||||||
maximumPauseSeconds: number
|
variant: string
|
||||||
endOfVideoUnloadSeconds: number
|
}>
|
||||||
sessionExpirationSeconds: number
|
|
||||||
usesStreamLimits: boolean
|
|
||||||
}
|
|
||||||
subtitles: Array<{
|
|
||||||
format: string
|
|
||||||
language: string
|
|
||||||
url: string
|
|
||||||
}>
|
|
||||||
token: string
|
|
||||||
url: string
|
|
||||||
versions: Array<{
|
|
||||||
audio_locale: string
|
|
||||||
guid: string
|
|
||||||
is_premium_only: boolean
|
|
||||||
media_guid: string
|
|
||||||
original: boolean
|
|
||||||
season_guid: string
|
|
||||||
variant: string
|
|
||||||
}>
|
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,71 @@
|
|||||||
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
|
||||||
}
|
|
||||||
number: number
|
|
||||||
presentationTime: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Playlist = {
|
|
||||||
attributes: {
|
|
||||||
NAME: string
|
|
||||||
BANDWIDTH: number
|
|
||||||
CODECS: string
|
|
||||||
'PROGRAM-ID': number
|
|
||||||
// Following for video only
|
|
||||||
'FRAME-RATE'?: number
|
|
||||||
AUDIO?: string // audio stream name
|
|
||||||
SUBTITLES?: string
|
|
||||||
RESOLUTION?: {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri: string
|
|
||||||
endList: boolean
|
|
||||||
timeline: number
|
|
||||||
resolvedUri: string
|
|
||||||
targetDuration: number
|
|
||||||
discontinuitySequence: number
|
|
||||||
discontinuityStarts: []
|
|
||||||
timelineStarts: {
|
|
||||||
start: number
|
|
||||||
timeline: number
|
|
||||||
}[]
|
|
||||||
mediaSequence: number
|
|
||||||
contentProtection?: {
|
|
||||||
[type: string]: {
|
|
||||||
pssh?: Uint8Array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
segments: Segment[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Manifest = {
|
|
||||||
allowCache: boolean
|
|
||||||
discontinuityStarts: []
|
|
||||||
segments: []
|
|
||||||
endList: true
|
|
||||||
duration: number
|
|
||||||
playlists: Playlist[]
|
|
||||||
mediaGroups: {
|
|
||||||
AUDIO: {
|
|
||||||
audio: {
|
|
||||||
[name: string]: {
|
|
||||||
language: string
|
|
||||||
autoselect: boolean
|
|
||||||
default: boolean
|
|
||||||
playlists: Playlist[]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
number: number
|
||||||
|
presentationTime: number
|
||||||
}
|
}
|
||||||
}
|
|
||||||
export function parse(manifest: string): Manifest
|
export type Playlist = {
|
||||||
|
attributes: {
|
||||||
|
NAME: string
|
||||||
|
BANDWIDTH: number
|
||||||
|
CODECS: string
|
||||||
|
'PROGRAM-ID': number
|
||||||
|
// Following for video only
|
||||||
|
'FRAME-RATE'?: number
|
||||||
|
AUDIO?: string // audio stream name
|
||||||
|
SUBTITLES?: string
|
||||||
|
RESOLUTION?: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uri: string
|
||||||
|
endList: boolean
|
||||||
|
timeline: number
|
||||||
|
resolvedUri: string
|
||||||
|
targetDuration: number
|
||||||
|
discontinuitySequence: number
|
||||||
|
discontinuityStarts: []
|
||||||
|
timelineStarts: {
|
||||||
|
start: number
|
||||||
|
timeline: number
|
||||||
|
}[]
|
||||||
|
mediaSequence: number
|
||||||
|
contentProtection?: {
|
||||||
|
[type: string]: {
|
||||||
|
pssh?: Uint8Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
segments: Segment[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Manifest = {
|
||||||
|
allowCache: boolean
|
||||||
|
discontinuityStarts: []
|
||||||
|
segments: []
|
||||||
|
endList: true
|
||||||
|
duration: number
|
||||||
|
playlists: Playlist[]
|
||||||
|
mediaGroups: {
|
||||||
|
AUDIO: {
|
||||||
|
audio: {
|
||||||
|
[name: string]: {
|
||||||
|
language: string
|
||||||
|
autoselect: boolean
|
||||||
|
default: boolean
|
||||||
|
playlists: Playlist[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function parse(manifest: string): Manifest
|
||||||
}
|
}
|
||||||
|
@ -17,197 +17,197 @@ const modules = [titleBarActionsModule, macMenuModule, updaterModule]
|
|||||||
var mainWindow: BrowserWindow
|
var mainWindow: BrowserWindow
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
console.log('System info', { isProduction, platform, architucture })
|
console.log('System info', { isProduction, platform, architucture })
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
title: 'Crunchyroll Downloader',
|
title: 'Crunchyroll Downloader',
|
||||||
icon: __dirname + '/icon/favicon.ico',
|
icon: __dirname + '/icon/favicon.ico',
|
||||||
width: 950,
|
width: 950,
|
||||||
height: 700,
|
height: 700,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
devTools: true,
|
devTools: true,
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
preload: path.join(__dirname, 'preload.js')
|
preload: path.join(__dirname, 'preload.js')
|
||||||
},
|
},
|
||||||
titleBarStyle: 'hidden',
|
titleBarStyle: 'hidden',
|
||||||
titleBarOverlay: {
|
titleBarOverlay: {
|
||||||
color: 'rgba(0,0,0,0)',
|
color: 'rgba(0,0,0,0)',
|
||||||
symbolColor: '#ffffff',
|
symbolColor: '#ffffff',
|
||||||
height: 40
|
height: 40
|
||||||
},
|
},
|
||||||
resizable: false,
|
resizable: false,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
vibrancy: 'fullscreen-ui',
|
vibrancy: 'fullscreen-ui',
|
||||||
// Not working when unfocusing the window somehow?
|
// Not working when unfocusing the window somehow?
|
||||||
backgroundMaterial: 'acrylic',
|
backgroundMaterial: 'acrylic',
|
||||||
show: false
|
show: false
|
||||||
})
|
|
||||||
|
|
||||||
// Show window after loading page
|
|
||||||
mainWindow.once('ready-to-show', () => {
|
|
||||||
mainWindow.show()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Closes all windows if mainwindow is being closed
|
|
||||||
mainWindow.on('closed', () => {
|
|
||||||
app.quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Lock app to single instance
|
|
||||||
if (singleInstance(app, mainWindow)) return
|
|
||||||
|
|
||||||
// Open the DevTools.
|
|
||||||
!isProduction &&
|
|
||||||
mainWindow.webContents.openDevTools({
|
|
||||||
mode: 'bottom'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return mainWindow
|
// Show window after loading page
|
||||||
|
mainWindow.once('ready-to-show', () => {
|
||||||
|
mainWindow.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Closes all windows if mainwindow is being closed
|
||||||
|
mainWindow.on('closed', () => {
|
||||||
|
app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Lock app to single instance
|
||||||
|
if (singleInstance(app, mainWindow)) return
|
||||||
|
|
||||||
|
// Open the DevTools.
|
||||||
|
!isProduction &&
|
||||||
|
mainWindow.webContents.openDevTools({
|
||||||
|
mode: 'bottom'
|
||||||
|
})
|
||||||
|
|
||||||
|
return mainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
// App events
|
// App events
|
||||||
// ==========
|
// ==========
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
startAPI()
|
startAPI()
|
||||||
|
|
||||||
const mainWindow = createWindow()
|
const mainWindow = createWindow()
|
||||||
if (!mainWindow) return
|
if (!mainWindow) return
|
||||||
|
|
||||||
// Load renderer process
|
// Load renderer process
|
||||||
await dynamicRenderer(mainWindow)
|
await dynamicRenderer(mainWindow)
|
||||||
|
|
||||||
// Initialize modules
|
// Initialize modules
|
||||||
console.log('-'.repeat(30) + '\n[+] Loading modules...')
|
console.log('-'.repeat(30) + '\n[+] Loading modules...')
|
||||||
modules.forEach((module) => {
|
modules.forEach((module) => {
|
||||||
try {
|
try {
|
||||||
module(mainWindow)
|
module(mainWindow)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log('[!] Module error: ', err.message || err)
|
console.log('[!] Module error: ', err.message || err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('[!] Loading modules: Done.' + '\r\n' + '-'.repeat(30))
|
console.log('[!] Loading modules: Done.' + '\r\n' + '-'.repeat(30))
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function messageBox(
|
export async function messageBox(
|
||||||
type: 'none' | 'info' | 'error' | 'question' | 'warning' | undefined,
|
type: 'none' | 'info' | 'error' | 'question' | 'warning' | undefined,
|
||||||
buttons: Array<'Cancel'>,
|
buttons: Array<'Cancel'>,
|
||||||
defaultId: number,
|
defaultId: number,
|
||||||
title: string,
|
title: string,
|
||||||
message: string,
|
message: string,
|
||||||
detail: string | undefined
|
detail: string | undefined
|
||||||
) {
|
) {
|
||||||
const options = {
|
const options = {
|
||||||
type: type as 'none' | 'info' | 'error' | 'question' | 'warning' | undefined,
|
type: type as 'none' | 'info' | 'error' | 'question' | 'warning' | undefined,
|
||||||
buttons: buttons,
|
buttons: buttons,
|
||||||
defaultId: defaultId,
|
defaultId: defaultId,
|
||||||
title: title,
|
title: title,
|
||||||
message: message,
|
message: message,
|
||||||
detail: detail
|
detail: detail
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = dialog.showMessageBox(options)
|
const response = dialog.showMessageBox(options)
|
||||||
console.log(response)
|
console.log(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setProgressBar(c: number) {
|
export async function setProgressBar(c: number) {
|
||||||
mainWindow.setProgressBar(c)
|
mainWindow.setProgressBar(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle('dialog:openDirectory', async () => {
|
ipcMain.handle('dialog:openDirectory', async () => {
|
||||||
const window = BrowserWindow.getFocusedWindow()
|
const window = BrowserWindow.getFocusedWindow()
|
||||||
|
|
||||||
if (!window) {
|
if (!window) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog(window, {
|
const { canceled, filePaths } = await dialog.showOpenDialog(window, {
|
||||||
properties: ['openDirectory']
|
properties: ['openDirectory']
|
||||||
})
|
})
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
return await settings.get('downloadPath')
|
return await settings.get('downloadPath')
|
||||||
} else {
|
} else {
|
||||||
await settings.set('downloadPath', filePaths[0])
|
await settings.set('downloadPath', filePaths[0])
|
||||||
return filePaths[0]
|
return filePaths[0]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('dialog:defaultDirectory', async () => {
|
ipcMain.handle('dialog:defaultDirectory', async () => {
|
||||||
const savedPath = await settings.get('downloadPath')
|
const savedPath = await settings.get('downloadPath')
|
||||||
|
|
||||||
if (!savedPath) {
|
if (!savedPath) {
|
||||||
const path = app.getPath('documents')
|
const path = app.getPath('documents')
|
||||||
|
|
||||||
await settings.set('downloadPath', path)
|
await settings.set('downloadPath', path)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
return savedPath
|
return savedPath
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const openWindows = new Map()
|
const openWindows = new Map()
|
||||||
|
|
||||||
// Open New Window
|
// Open New Window
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'window:openNewWindow',
|
'window:openNewWindow',
|
||||||
async (
|
async (
|
||||||
events,
|
events,
|
||||||
opt: {
|
opt: {
|
||||||
title: string
|
title: string
|
||||||
url: string
|
url: string
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
if (openWindows.has(opt.title)) {
|
||||||
|
const existingWindow = openWindows.get(opt.title)
|
||||||
|
existingWindow.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newWindow = new BrowserWindow({
|
||||||
|
title: opt.title,
|
||||||
|
icon: __dirname + '/icon/favicon.ico',
|
||||||
|
width: opt.width,
|
||||||
|
height: opt.height,
|
||||||
|
webPreferences: {
|
||||||
|
devTools: true,
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: true,
|
||||||
|
preload: path.join(__dirname, 'preload.js')
|
||||||
|
},
|
||||||
|
titleBarStyle: 'hidden',
|
||||||
|
titleBarOverlay: {
|
||||||
|
color: 'rgba(0,0,0,0)',
|
||||||
|
symbolColor: '#ffffff',
|
||||||
|
height: 40
|
||||||
|
},
|
||||||
|
resizable: false,
|
||||||
|
fullscreen: false,
|
||||||
|
maximizable: false,
|
||||||
|
vibrancy: 'fullscreen-ui',
|
||||||
|
backgroundMaterial: 'acrylic',
|
||||||
|
show: false
|
||||||
|
})
|
||||||
|
|
||||||
|
newWindow.once('ready-to-show', () => {
|
||||||
|
newWindow.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
newWindow.loadURL(opt.url)
|
||||||
|
|
||||||
|
openWindows.set(opt.title, newWindow)
|
||||||
|
|
||||||
|
newWindow.on('closed', () => {
|
||||||
|
openWindows.delete(opt.title)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
) => {
|
|
||||||
if (openWindows.has(opt.title)) {
|
|
||||||
const existingWindow = openWindows.get(opt.title)
|
|
||||||
existingWindow.focus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const newWindow = new BrowserWindow({
|
|
||||||
title: opt.title,
|
|
||||||
icon: __dirname + '/icon/favicon.ico',
|
|
||||||
width: opt.width,
|
|
||||||
height: opt.height,
|
|
||||||
webPreferences: {
|
|
||||||
devTools: true,
|
|
||||||
nodeIntegration: true,
|
|
||||||
contextIsolation: true,
|
|
||||||
preload: path.join(__dirname, 'preload.js')
|
|
||||||
},
|
|
||||||
titleBarStyle: 'hidden',
|
|
||||||
titleBarOverlay: {
|
|
||||||
color: 'rgba(0,0,0,0)',
|
|
||||||
symbolColor: '#ffffff',
|
|
||||||
height: 40
|
|
||||||
},
|
|
||||||
resizable: false,
|
|
||||||
fullscreen: false,
|
|
||||||
maximizable: false,
|
|
||||||
vibrancy: 'fullscreen-ui',
|
|
||||||
backgroundMaterial: 'acrylic',
|
|
||||||
show: false
|
|
||||||
})
|
|
||||||
|
|
||||||
newWindow.once('ready-to-show', () => {
|
|
||||||
newWindow.show()
|
|
||||||
})
|
|
||||||
|
|
||||||
newWindow.loadURL(opt.url)
|
|
||||||
|
|
||||||
openWindows.set(opt.title, newWindow)
|
|
||||||
|
|
||||||
newWindow.on('closed', () => {
|
|
||||||
openWindows.delete(opt.title)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
@ -12,12 +12,12 @@ const isProduction = process.env.NODE_ENV !== 'development'
|
|||||||
// Dynamic Renderer
|
// Dynamic Renderer
|
||||||
// ================
|
// ================
|
||||||
export default async function (mainWindow: BrowserWindow) {
|
export default async function (mainWindow: BrowserWindow) {
|
||||||
if (!isProduction) return mainWindow.loadURL('http://localhost:3000/')
|
if (!isProduction) return mainWindow.loadURL('http://localhost:3000/')
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use('/', serveStatic(path.join(__dirname, '../../public')))
|
app.use('/', serveStatic(path.join(__dirname, '../../public')))
|
||||||
const listener = app.listen(8079, 'localhost', () => {
|
const listener = app.listen(8079, 'localhost', () => {
|
||||||
const port = (listener.address() as any).port
|
const port = (listener.address() as any).port
|
||||||
console.log('Dynamic-Renderer Listening on', port)
|
console.log('Dynamic-Renderer Listening on', port)
|
||||||
mainWindow.loadURL(`http://localhost:${port}`)
|
mainWindow.loadURL(`http://localhost:${port}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,54 +7,54 @@ const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = []
|
|||||||
// Module
|
// Module
|
||||||
// ======
|
// ======
|
||||||
export default (mainWindow: BrowserWindow) => {
|
export default (mainWindow: BrowserWindow) => {
|
||||||
const isDevelopment = process.env.NODE_ENV === 'development'
|
const isDevelopment = process.env.NODE_ENV === 'development'
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
// OS X
|
// OS X
|
||||||
const name = 'electron-nuxt3'
|
const name = 'electron-nuxt3'
|
||||||
template.unshift({
|
template.unshift({
|
||||||
label: name,
|
label: name,
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'About ' + name,
|
label: 'About ' + name,
|
||||||
role: 'about'
|
role: 'about'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Quit',
|
label: 'Quit',
|
||||||
accelerator: 'Command+Q',
|
accelerator: 'Command+Q',
|
||||||
click() {
|
click() {
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
accelerator: 'Command+R',
|
accelerator: 'Command+R',
|
||||||
click() {
|
click() {
|
||||||
// Reload the current window
|
// Reload the current window
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.reload()
|
mainWindow.reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...(isDevelopment
|
...(isDevelopment
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Toggle Developer Tools',
|
label: 'Toggle Developer Tools',
|
||||||
accelerator: 'Alt+Command+I',
|
accelerator: 'Alt+Command+I',
|
||||||
click() {
|
click() {
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.webContents.toggleDevTools()
|
mainWindow.webContents.toggleDevTools()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
]
|
]
|
||||||
: [])
|
})
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate(template)
|
const menu = Menu.buildFromTemplate(template)
|
||||||
Menu.setApplicationMenu(menu)
|
Menu.setApplicationMenu(menu)
|
||||||
|
|
||||||
console.log('[-] MODULE::macMenu Initialized')
|
console.log('[-] MODULE::macMenu Initialized')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,48 +3,48 @@ import { ipcMain, BrowserWindow } from 'electron'
|
|||||||
// Helpers
|
// Helpers
|
||||||
// =======
|
// =======
|
||||||
const getWindowFromEvent = (event: Electron.IpcMainInvokeEvent) => {
|
const getWindowFromEvent = (event: Electron.IpcMainInvokeEvent) => {
|
||||||
const webContents = event.sender
|
const webContents = event.sender
|
||||||
const win = BrowserWindow.fromWebContents(webContents)
|
const win = BrowserWindow.fromWebContents(webContents)
|
||||||
return win
|
return win
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module
|
// Module
|
||||||
// ======
|
// ======
|
||||||
export default (mainWindow: BrowserWindow) => {
|
export default (mainWindow: BrowserWindow) => {
|
||||||
ipcMain.handle('isMaximized:app', (event) => {
|
ipcMain.handle('isMaximized:app', (event) => {
|
||||||
const win = getWindowFromEvent(event)
|
const win = getWindowFromEvent(event)
|
||||||
return win?.isMaximized()
|
return win?.isMaximized()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('titlebar:action', (event, action: 'toggleMaximize' | 'minimize') => {
|
ipcMain.handle('titlebar:action', (event, action: 'toggleMaximize' | 'minimize') => {
|
||||||
const win = getWindowFromEvent(event)
|
const win = getWindowFromEvent(event)
|
||||||
if (!win) return
|
if (!win) return
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'toggleMaximize':
|
case 'toggleMaximize':
|
||||||
win.isMaximized() ? win.unmaximize() : win.maximize()
|
win.isMaximized() ? win.unmaximize() : win.maximize()
|
||||||
break
|
break
|
||||||
case 'minimize':
|
case 'minimize':
|
||||||
win.minimize()
|
win.minimize()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('close:app', (event) => {
|
ipcMain.handle('close:app', (event) => {
|
||||||
const win = getWindowFromEvent(event)
|
const win = getWindowFromEvent(event)
|
||||||
if (!win) return
|
if (!win) return
|
||||||
win.close()
|
win.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('get:windowVisible', (_event) => {
|
ipcMain.handle('get:windowVisible', (_event) => {
|
||||||
return mainWindow.isVisible()
|
return mainWindow.isVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.on('maximize', () => mainWindow.webContents.send('window:maximizeChanged', true))
|
mainWindow.on('maximize', () => mainWindow.webContents.send('window:maximizeChanged', true))
|
||||||
mainWindow.on('unmaximize', () => mainWindow.webContents.send('window:maximizeChanged', false))
|
mainWindow.on('unmaximize', () => mainWindow.webContents.send('window:maximizeChanged', false))
|
||||||
mainWindow.on('enter-full-screen', () => mainWindow.webContents.send('window:fullscreenChanged', true))
|
mainWindow.on('enter-full-screen', () => mainWindow.webContents.send('window:fullscreenChanged', true))
|
||||||
mainWindow.on('leave-full-screen', () => mainWindow.webContents.send('window:fullscreenChanged', false))
|
mainWindow.on('leave-full-screen', () => mainWindow.webContents.send('window:fullscreenChanged', false))
|
||||||
|
|
||||||
console.log('[-] MODULE::titleBarActions Initialized')
|
console.log('[-] MODULE::titleBarActions Initialized')
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.electronjs.org/docs/latest/tutorial/ipc
|
// https://www.electronjs.org/docs/latest/tutorial/ipc
|
||||||
|
@ -2,7 +2,7 @@ import { BrowserWindow, ipcMain } from 'electron'
|
|||||||
import { autoUpdater } from 'electron-updater'
|
import { autoUpdater } from 'electron-updater'
|
||||||
import log from 'electron-log'
|
import log from 'electron-log'
|
||||||
|
|
||||||
var status: { status: string, info: any } = { status: "", info: null }
|
var status: { status: string; info: any } = { status: '', info: null }
|
||||||
|
|
||||||
autoUpdater.logger = log
|
autoUpdater.logger = log
|
||||||
;(autoUpdater.logger as typeof log).transports.file.level = 'info'
|
;(autoUpdater.logger as typeof log).transports.file.level = 'info'
|
||||||
@ -11,54 +11,57 @@ autoUpdater.autoDownload = false
|
|||||||
autoUpdater.autoInstallOnAppQuit = false
|
autoUpdater.autoInstallOnAppQuit = false
|
||||||
|
|
||||||
ipcMain.handle('updater:getUpdateStatus', async () => {
|
ipcMain.handle('updater:getUpdateStatus', async () => {
|
||||||
return status
|
return status
|
||||||
})
|
})
|
||||||
|
|
||||||
export default (mainWindow: BrowserWindow) => {
|
export default (mainWindow: BrowserWindow) => {
|
||||||
let readyToInstall = false
|
let readyToInstall = false
|
||||||
function updateStatus(statusA: string, info?: any) {
|
function updateStatus(statusA: string, info?: any) {
|
||||||
status = { status: statusA, info: info }
|
status = { status: statusA, info: info }
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdater.on('checking-for-update', () => {
|
autoUpdater.on('checking-for-update', () => {
|
||||||
updateStatus('check-for-update')
|
updateStatus('check-for-update')
|
||||||
})
|
})
|
||||||
autoUpdater.on('update-available', (_info) => {
|
autoUpdater.on('update-available', (_info) => {
|
||||||
updateStatus('update-available', _info)
|
updateStatus('update-available', _info)
|
||||||
})
|
})
|
||||||
autoUpdater.on('update-not-available', (_info) => {
|
autoUpdater.on('update-not-available', (_info) => {
|
||||||
updateStatus('update-not-available', _info)
|
updateStatus('update-not-available', _info)
|
||||||
})
|
})
|
||||||
autoUpdater.on('error', (_err) => {
|
autoUpdater.on('error', (_err) => {
|
||||||
updateStatus('update-error', _err)
|
updateStatus('update-error', _err)
|
||||||
})
|
})
|
||||||
autoUpdater.on('download-progress', (progress) => {
|
autoUpdater.on('download-progress', (progress) => {
|
||||||
updateStatus('downloading', progress)
|
updateStatus('downloading', progress)
|
||||||
})
|
})
|
||||||
autoUpdater.on('update-downloaded', (_info) => {
|
autoUpdater.on('update-downloaded', (_info) => {
|
||||||
updateStatus('update-downloaded', _info)
|
updateStatus('update-downloaded', _info)
|
||||||
mainWindow.webContents.send('updater:readyToInstall')
|
mainWindow.webContents.send('updater:readyToInstall')
|
||||||
readyToInstall = true
|
readyToInstall = true
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('updater:check', async (_event) => {
|
ipcMain.handle('updater:check', async (_event) => {
|
||||||
return await autoUpdater.checkForUpdates()
|
return await autoUpdater.checkForUpdates()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('updater:download', async (_event) => {
|
ipcMain.handle('updater:download', async (_event) => {
|
||||||
return await autoUpdater.downloadUpdate()
|
return await autoUpdater.downloadUpdate()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('updater:quitAndInstall', (_event) => {
|
ipcMain.handle('updater:quitAndInstall', (_event) => {
|
||||||
if (!readyToInstall) return
|
if (!readyToInstall) return
|
||||||
autoUpdater.quitAndInstall()
|
autoUpdater.quitAndInstall()
|
||||||
})
|
})
|
||||||
|
|
||||||
autoUpdater.checkForUpdates()
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
autoUpdater.checkForUpdates()
|
autoUpdater.checkForUpdates()
|
||||||
}, 1000 * 60 * 60 * 2)
|
|
||||||
|
|
||||||
console.log('[-] MODULE::updater Initialized')
|
setInterval(
|
||||||
|
() => {
|
||||||
|
autoUpdater.checkForUpdates()
|
||||||
|
},
|
||||||
|
1000 * 60 * 60 * 2
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('[-] MODULE::updater Initialized')
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
import {contextBridge, ipcRenderer} from 'electron'
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('myAPI', {
|
contextBridge.exposeInMainWorld('myAPI', {
|
||||||
selectFolder: () => ipcRenderer.invoke('dialog:openDirectory'),
|
selectFolder: () => ipcRenderer.invoke('dialog:openDirectory'),
|
||||||
getFolder: () => ipcRenderer.invoke('dialog:defaultDirectory'),
|
getFolder: () => ipcRenderer.invoke('dialog:defaultDirectory'),
|
||||||
openWindow: (opt: {
|
openWindow: (opt: { title: string; url: string; width: number; height: number; backgroundColor: string }) => ipcRenderer.invoke('window:openNewWindow', opt),
|
||||||
title: string,
|
getUpdateStatus: () => ipcRenderer.invoke('updater:getUpdateStatus'),
|
||||||
url: string,
|
startUpdateDownload: () => ipcRenderer.invoke('updater:download'),
|
||||||
width: number,
|
startUpdateInstall: () => ipcRenderer.invoke('updater:quitAndInstall')
|
||||||
height: number,
|
|
||||||
backgroundColor: string
|
|
||||||
}) => ipcRenderer.invoke('window:openNewWindow', opt),
|
|
||||||
getUpdateStatus: () => ipcRenderer.invoke('updater:getUpdateStatus'),
|
|
||||||
startUpdateDownload: () => ipcRenderer.invoke('updater:download'),
|
|
||||||
startUpdateInstall: () => ipcRenderer.invoke('updater:quitAndInstall'),
|
|
||||||
})
|
})
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { App, BrowserWindow } from 'electron'
|
import { App, BrowserWindow } from 'electron'
|
||||||
|
|
||||||
export default (app: App, win: BrowserWindow) => {
|
export default (app: App, win: BrowserWindow) => {
|
||||||
const gotTheLock = app.requestSingleInstanceLock()
|
const gotTheLock = app.requestSingleInstanceLock()
|
||||||
|
|
||||||
if (!gotTheLock) {
|
if (!gotTheLock) {
|
||||||
app.quit()
|
app.quit()
|
||||||
return true
|
return true
|
||||||
}
|
|
||||||
|
|
||||||
app.on('second-instance', (_, _argv) => {
|
|
||||||
if (win) {
|
|
||||||
win.show()
|
|
||||||
if (win.isMinimized()) win.restore()
|
|
||||||
win.focus()
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
app.on('open-url', function (event, url) {
|
app.on('second-instance', (_, _argv) => {
|
||||||
event.preventDefault()
|
if (win) {
|
||||||
win.webContents.send('deeplink', url)
|
win.show()
|
||||||
})
|
if (win.isMinimized()) win.restore()
|
||||||
|
win.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('open-url', function (event, url) {
|
||||||
|
event.preventDefault()
|
||||||
|
win.webContents.send('deeplink', url)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,103 +1,103 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
/* Projects */
|
/* Projects */
|
||||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
/* Modules */
|
/* Modules */
|
||||||
"module": "commonjs" /* Specify what module code is generated. */,
|
"module": "commonjs" /* Specify what module code is generated. */,
|
||||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
/* JavaScript Support */
|
/* JavaScript Support */
|
||||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
/* Emit */
|
/* Emit */
|
||||||
"declaration": false, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
"declaration": false /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
|
||||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
"outDir": "../.output/src" /* Specify an output folder for all emitted files. */,
|
"outDir": "../.output/src" /* Specify an output folder for all emitted files. */,
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
/* Interop Constraints */
|
/* Interop Constraints */
|
||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
|
||||||
/* Type Checking */
|
/* Type Checking */
|
||||||
"strict": true /* Enable all strict type-checking options. */,
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
/* Completeness */
|
/* Completeness */
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
{
|
{
|
||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
"extends": "./.nuxt/tsconfig.json",
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
"compilerOptions": {
|
|
||||||
"types": [
|
|
||||||
"@nuxtjs/i18n",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user