added better activestreams limiter and account limit checker
This commit is contained in:
parent
cecdd683c5
commit
b52d93a7f3
@ -5,7 +5,7 @@
|
||||
Crunchyroll <br />
|
||||
Downloader
|
||||
</div>
|
||||
<div class="text-sm mt-1 text-gray-200"> v1.1.3 </div>
|
||||
<div class="text-sm mt-1 text-gray-200"> v1.1.5 </div>
|
||||
<div class="absolute right-0 bottom-0 text-xs text-gray-200"> Made by Stratum </div>
|
||||
</div>
|
||||
</template>
|
||||
|
90
components/Settings/Naming.vue
Normal file
90
components/Settings/Naming.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 mt-3 font-dm" style="-webkit-app-region: no-drag">
|
||||
<div class="flex flex-col items-center p-3 bg-[#11111189] rounded-xl select-none">
|
||||
<div class="text-sm mb-2">Episode File Naming</div>
|
||||
<input
|
||||
v-model="episodeNamingTemplate"
|
||||
type="text"
|
||||
name="text"
|
||||
placeholder="Episode Naming"
|
||||
class="bg-[#5c5b5b] w-full focus:outline-none px-3 py-2 rounded-xl text-sm text-center"
|
||||
/>
|
||||
<div class="text-sm mt-2">
|
||||
Example:
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
{{ `${episodeNaming}` }}
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2">
|
||||
Variables:
|
||||
</div>
|
||||
<div class="text-sm text-center">
|
||||
{seriesName}, {seasonNumber}, {seasonNumberDD}, {episodeNumber}, {episodeNumberDD}, {quality}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center p-3 bg-[#11111189] rounded-xl select-none">
|
||||
<div class="text-sm mb-2">Season Folder Naming</div>
|
||||
<input
|
||||
v-model="seasonNamingTemplate"
|
||||
type="text"
|
||||
name="text"
|
||||
placeholder="Episode Naming"
|
||||
class="bg-[#5c5b5b] w-full focus:outline-none px-3 py-2 rounded-xl text-sm text-center"
|
||||
/>
|
||||
<div class="text-sm mt-2">
|
||||
Example:
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
{{ `${seasonNaming}` }}
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2">
|
||||
Variables:
|
||||
</div>
|
||||
<div class="text-sm text-center">
|
||||
{seriesName}, {seasonNumber}, {seasonNumberDD}, {quality}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const episodeNumber = ref<number>(1);
|
||||
const seasonNumber = ref<number>(1);
|
||||
const quality = ref<number>(1080);
|
||||
const seriesName = ref<string>('Frieren');
|
||||
const episodeNamingTemplate = ref<string>();
|
||||
const seasonNamingTemplate = ref<string>();
|
||||
|
||||
const episodeNaming = computed(() => {
|
||||
if (!episodeNamingTemplate.value) return
|
||||
return episodeNamingTemplate.value
|
||||
.replace('{seriesName}', seriesName.value)
|
||||
.replace('{seasonNumber}', seasonNumber.value.toString())
|
||||
.replace('{seasonNumberDD}', seasonNumber.value.toString().padStart(2, '0'))
|
||||
.replace('{episodeNumber}', episodeNumber.value.toString())
|
||||
.replace('{episodeNumberDD}', episodeNumber.value.toString().padStart(2, '0'))
|
||||
.replace('{quality}', quality.value.toString() +'p');
|
||||
});
|
||||
|
||||
const seasonNaming = computed(() => {
|
||||
if (!seasonNamingTemplate.value) return
|
||||
return seasonNamingTemplate.value
|
||||
.replace('{seriesName}', seriesName.value)
|
||||
.replace('{seasonNumber}', seasonNumber.value.toString())
|
||||
.replace('{seasonNumberDD}', seasonNumber.value.toString().padStart(2, '0'))
|
||||
.replace('{quality}', quality.value.toString() +'p');
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
;(window as any).myAPI.getSeasonTemplate().then((result: string) => {
|
||||
seasonNamingTemplate.value = result
|
||||
})
|
||||
;(window as any).myAPI.getEpisodeTemplate().then((result: string) => {
|
||||
episodeNamingTemplate.value = result
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
36
components/Settings/System.vue
Normal file
36
components/Settings/System.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 mt-3 font-dm" style="-webkit-app-region: no-drag">
|
||||
<div class="flex flex-col items-center p-3 bg-[#11111189] rounded-xl select-none">
|
||||
<div class="text-sm mb-2">Default TEMP Path</div>
|
||||
<input
|
||||
@click="getTEMPFolder()"
|
||||
v-model="tempPath"
|
||||
type="text"
|
||||
name="text"
|
||||
placeholder="Select a TEMP Folder"
|
||||
class="bg-[#5c5b5b] w-full focus:outline-none px-3 py-2 rounded-xl text-sm text-center cursor-pointer"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const tempPath = ref<string>()
|
||||
|
||||
const getTEMPFolder = () => {
|
||||
if (process.client) {
|
||||
;(window as any).myAPI.selectTEMPFolder().then((result: string) => {
|
||||
tempPath.value = result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
;(window as any).myAPI.getTEMPFolder().then((result: any) => {
|
||||
tempPath.value = result
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -2,7 +2,7 @@
|
||||
"name": "crunchyroll-downloader",
|
||||
"author": "Stratum",
|
||||
"description": "Crunchyroll Downloader",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"private": true,
|
||||
"main": ".output/src/electron/background.js",
|
||||
"repository": "https://github.com/stratuma/Crunchyroll-Downloader-v4.0",
|
||||
|
@ -14,15 +14,17 @@
|
||||
</button>
|
||||
</div>
|
||||
<SettingsMain v-if="activeIndex === 0" />
|
||||
<SettingsCrunchyroll v-if="activeIndex === 1" />
|
||||
<SettingsWidevine v-if="activeIndex === 2" />
|
||||
<SettingsProxy v-if="activeIndex === 3" />
|
||||
<SettingsAbout v-if="activeIndex === 4" />
|
||||
<SettingsNaming v-if="activeIndex === 1" />
|
||||
<SettingsCrunchyroll v-if="activeIndex === 2" />
|
||||
<SettingsWidevine v-if="activeIndex === 3" />
|
||||
<SettingsProxy v-if="activeIndex === 4" />
|
||||
<SettingsSystem v-if="activeIndex === 5" />
|
||||
<SettingsAbout v-if="activeIndex === 6" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const options = ref<Array<string>>(['Main', 'Crunchyroll', 'Widevine', 'Proxy', 'About'])
|
||||
const options = ref<Array<string>>(['Main', 'Naming', 'Crunchy', 'Widevine', 'Proxy', 'System', 'About'])
|
||||
const activeIndex = ref(0)
|
||||
</script>
|
||||
|
||||
|
@ -129,7 +129,7 @@ async function crunchyLoginFetchProxy(user: string, passw: string, geo: string)
|
||||
headers = {
|
||||
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=',
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27'
|
||||
'User-Agent': 'Crunchyroll/3.46.2 Android/13 okhttp/4.12.0'
|
||||
}
|
||||
|
||||
body = {
|
||||
@ -139,7 +139,7 @@ async function crunchyLoginFetchProxy(user: string, passw: string, geo: string)
|
||||
scope: 'offline_access',
|
||||
device_name: 'RMX2170',
|
||||
device_type: 'realme RMX2170',
|
||||
ursa: 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27',
|
||||
ursa: 'Crunchyroll/3.46.2 Android/13 okhttp/4.12.0',
|
||||
token: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4='
|
||||
}
|
||||
}
|
||||
@ -218,7 +218,7 @@ async function crunchyLoginFetch(user: string, passw: string) {
|
||||
headers = {
|
||||
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'User-Agent': 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27'
|
||||
'User-Agent': 'Crunchyroll/3.46.2 Android/13 okhttp/4.12.0'
|
||||
}
|
||||
|
||||
body = {
|
||||
@ -377,7 +377,7 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
|
||||
const headersLoc = {
|
||||
Authorization: `Bearer ${login.access_token}`,
|
||||
'X-Cr-Disable-Drm': 'true',
|
||||
'x-cr-stream-limits': 'true',
|
||||
'x-cr-stream-limits': 'false',
|
||||
'User-Agent': 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27'
|
||||
}
|
||||
|
||||
@ -428,6 +428,14 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
|
||||
await deleteVideoToken(e.contentId, e.token)
|
||||
}
|
||||
|
||||
server.logger.log({
|
||||
level: 'error',
|
||||
message: 'Refetching Crunchyroll Video Playlist & Deleting all Video Token because too many streams',
|
||||
error: errorJSON,
|
||||
timestamp: new Date().toISOString(),
|
||||
section: 'playlistCrunchyrollFetch'
|
||||
})
|
||||
|
||||
return await crunchyGetPlaylist(q, geo)
|
||||
}
|
||||
|
||||
@ -458,7 +466,7 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
|
||||
'User-Agent': 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27'
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
const responseProx = await fetch(
|
||||
`https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}${
|
||||
endpoints.find((e) => e.id === endpoint) ? endpoints.find((e) => e.id === endpoint)?.url : '/console/switch/play'
|
||||
}`,
|
||||
@ -468,30 +476,32 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
|
||||
}
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
const data: VideoPlaylistNoGEO = JSON.parse(await response.text())
|
||||
if (responseProx.ok) {
|
||||
const dataProx: VideoPlaylistNoGEO = JSON.parse(await responseProx.text())
|
||||
|
||||
data.hardSubs = Object.values((data as any).hardSubs)
|
||||
dataProx.hardSubs = Object.values((dataProx as any).hardSubs)
|
||||
|
||||
data.subtitles = Object.values((data as any).subtitles)
|
||||
dataProx.subtitles = Object.values((dataProx as any).subtitles)
|
||||
|
||||
for (const v of data.versions) {
|
||||
for (const v of dataProx.versions) {
|
||||
if (!playlist.versions.find((ver) => ver.guid === v.guid)) {
|
||||
playlist.versions.push({ ...v, geo: p.code })
|
||||
}
|
||||
}
|
||||
|
||||
for (const v of data.subtitles) {
|
||||
for (const v of dataProx.subtitles) {
|
||||
if (!playlist.subtitles.find((ver) => ver.language === v.language)) {
|
||||
playlist.subtitles.push({ ...v, geo: p.code })
|
||||
}
|
||||
}
|
||||
|
||||
for (const v of data.hardSubs) {
|
||||
for (const v of dataProx.hardSubs) {
|
||||
if (!playlist.hardSubs.find((ver) => ver.hlang === v.hlang)) {
|
||||
playlist.hardSubs.push({ ...v, geo: p.code })
|
||||
}
|
||||
}
|
||||
|
||||
await deleteVideoToken(q, dataProx.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -513,8 +523,8 @@ export async function deleteVideoToken(content: string, token: string) {
|
||||
Authorization: `Bearer ${login.access_token}`
|
||||
}
|
||||
|
||||
const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${content}/${token}/inactive`, {
|
||||
method: 'PATCH',
|
||||
const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${content}/${token}`, {
|
||||
method: 'DELETE',
|
||||
headers: headers
|
||||
})
|
||||
|
||||
@ -584,3 +594,109 @@ export async function crunchyGetPlaylistMPD(q: string, geo: string | undefined)
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAccountInfo() {
|
||||
const account = await loggedInCheck('CR')
|
||||
|
||||
if (!account) return
|
||||
|
||||
const login = await crunchyLogin(account.username, account.password, 'LOCAL')
|
||||
|
||||
if (!login) return
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${login.access_token}`,
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://beta-api.crunchyroll.com/accounts/v1/me', {
|
||||
method: 'GET',
|
||||
headers: headers
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
account_id: string,
|
||||
external_id: string,
|
||||
} = await JSON.parse(await response.text())
|
||||
|
||||
return data
|
||||
} else {
|
||||
const error = await response.text()
|
||||
messageBox('error', ['Cancel'], 2, 'Failed to get Crunchyroll Account Info', 'Failed to get Crunchyroll Account Info', error)
|
||||
server.logger.log({
|
||||
level: 'error',
|
||||
message: 'Failed to get Crunchyroll Account Info',
|
||||
error: error,
|
||||
timestamp: new Date().toISOString(),
|
||||
section: 'settingsCrunchyrollFetch'
|
||||
})
|
||||
throw new Error(await response.text())
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
// Check Max account streams because of crunchyroll activestream limit
|
||||
export async function checkAccountMaxStreams() {
|
||||
|
||||
const accountinfo = await getAccountInfo()
|
||||
|
||||
if (!accountinfo) return 1
|
||||
|
||||
const account = await loggedInCheck('CR')
|
||||
|
||||
if (!account) return 1
|
||||
|
||||
const login = await crunchyLogin(account.username, account.password, 'LOCAL')
|
||||
|
||||
if (!login) return 1
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${login.access_token}`,
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://beta-api.crunchyroll.com/subs/v1/subscriptions/${accountinfo.external_id}/benefits`, {
|
||||
method: 'GET',
|
||||
headers: headers
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
items: {
|
||||
__class__: string,
|
||||
__href__: string,
|
||||
__links__: string,
|
||||
__actions__: string,
|
||||
benefit: string,
|
||||
source: string
|
||||
}[]
|
||||
} = await JSON.parse(await response.text())
|
||||
|
||||
if (!data.items || data.items.length === 0) return 1
|
||||
|
||||
if (data.items.find(i => i.benefit === 'concurrent_streams.4')) return 2
|
||||
|
||||
if (data.items.find(i => i.benefit === 'concurrent_streams.1')) return 1
|
||||
|
||||
if (data.items.find(i => i.benefit === 'concurrent_streams.6')) return 3
|
||||
|
||||
return 1
|
||||
} else {
|
||||
const error = await response.text()
|
||||
messageBox('error', ['Cancel'], 2, 'Failed to get Crunchyroll Account Subscription', 'Failed to get Crunchyroll Account Subscription', error)
|
||||
server.logger.log({
|
||||
level: 'error',
|
||||
message: 'Failed to get Crunchyroll Account Subscription',
|
||||
error: error,
|
||||
timestamp: new Date().toISOString(),
|
||||
section: 'subCrunchyrollFetch'
|
||||
})
|
||||
throw new Error(await response.text())
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { concatenateTSFiles } from '../../services/concatenate'
|
||||
import { createFolder, createFolderName, deleteFolder, deleteTemporaryFolders } from '../../services/folder'
|
||||
import { downloadADNSub, downloadCRSub } from '../../services/subs'
|
||||
import { CrunchyEpisode } from '../../types/crunchyroll'
|
||||
import { crunchyGetPlaylist, crunchyGetPlaylistMPD, deleteVideoToken } from '../crunchyroll/crunchyroll.service'
|
||||
import { checkAccountMaxStreams, crunchyGetPlaylist, crunchyGetPlaylistMPD, deleteVideoToken } from '../crunchyroll/crunchyroll.service'
|
||||
import fs from 'fs'
|
||||
var cron = require('node-cron')
|
||||
import { Readable } from 'stream'
|
||||
@ -159,7 +159,7 @@ async function deletePlaylistandTMP() {
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(deletePlaylistandTMP, 500)
|
||||
deletePlaylistandTMP();
|
||||
|
||||
// Update Playlist Item
|
||||
export async function updatePlaylistByID(
|
||||
@ -425,6 +425,27 @@ export async function downloadADNPlaylist(
|
||||
await deleteFolder(videoFolder)
|
||||
}
|
||||
|
||||
var counter = 0;
|
||||
var maxLimit = 1;
|
||||
|
||||
async function incrementPlaylistCounter() {
|
||||
return new Promise<void>((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (counter < maxLimit) {
|
||||
counter++;
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function decrementPlaylistCounter() {
|
||||
if (counter > 0) {
|
||||
counter--;
|
||||
}
|
||||
}
|
||||
|
||||
// Download Crunchyroll Playlist
|
||||
export async function downloadCrunchyrollPlaylist(
|
||||
e: string,
|
||||
@ -449,8 +470,15 @@ export async function downloadCrunchyrollPlaylist(
|
||||
totalDownloaded: 0
|
||||
})
|
||||
|
||||
const accmaxstream = await checkAccountMaxStreams();
|
||||
|
||||
if (accmaxstream) {
|
||||
maxLimit = accmaxstream
|
||||
}
|
||||
|
||||
await updatePlaylistByID(downloadID, 'downloading')
|
||||
|
||||
await incrementPlaylistCounter();
|
||||
var playlist = await crunchyGetPlaylist(e, geo)
|
||||
|
||||
if (!playlist) {
|
||||
@ -471,7 +499,9 @@ export async function downloadCrunchyrollPlaylist(
|
||||
const found = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
|
||||
if (found) {
|
||||
await deleteVideoToken(episodeID, playlist.data.token)
|
||||
decrementPlaylistCounter();
|
||||
playlist = await crunchyGetPlaylist(found.guid, found.geo)
|
||||
await incrementPlaylistCounter();
|
||||
} else {
|
||||
console.log('Exact Playlist not found, taking what crunchy gives.')
|
||||
messageBox(
|
||||
@ -499,6 +529,7 @@ export async function downloadCrunchyrollPlaylist(
|
||||
}
|
||||
|
||||
await deleteVideoToken(episodeID, playlist.data.token)
|
||||
decrementPlaylistCounter();
|
||||
|
||||
const subFolder = await createFolder()
|
||||
|
||||
@ -537,6 +568,7 @@ export async function downloadCrunchyrollPlaylist(
|
||||
if (playlist.data.audioLocale !== 'ja-JP') {
|
||||
const foundStream = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
|
||||
if (foundStream) {
|
||||
await incrementPlaylistCounter();
|
||||
subPlaylist = await crunchyGetPlaylist(foundStream.guid, foundStream.geo)
|
||||
}
|
||||
} else {
|
||||
@ -576,7 +608,8 @@ export async function downloadCrunchyrollPlaylist(
|
||||
})
|
||||
}
|
||||
|
||||
await deleteVideoToken(episodeID, playlist.data.token)
|
||||
await deleteVideoToken(episodeID, subPlaylist.data.token)
|
||||
decrementPlaylistCounter()
|
||||
}
|
||||
|
||||
for (const d of dubs) {
|
||||
@ -586,16 +619,18 @@ export async function downloadCrunchyrollPlaylist(
|
||||
}
|
||||
|
||||
if (found) {
|
||||
await incrementPlaylistCounter();
|
||||
const list = await crunchyGetPlaylist(found.guid, found.geo)
|
||||
if (list) {
|
||||
await deleteVideoToken(episodeID, list.data.token)
|
||||
decrementPlaylistCounter();
|
||||
|
||||
const foundSub = list.data.subtitles.find((sub) => sub.language === d)
|
||||
if (foundSub) {
|
||||
subDownloadList.push({ ...foundSub, isDub: true })
|
||||
} else {
|
||||
console.log(`No Dub Sub Found for ${d}`)
|
||||
}
|
||||
|
||||
await deleteVideoToken(episodeID, playlist.data.token)
|
||||
}
|
||||
dubDownloadList.push(found)
|
||||
console.log(`Audio ${d}.aac found, adding to download`)
|
||||
@ -642,6 +677,7 @@ export async function downloadCrunchyrollPlaylist(
|
||||
const audioDownload = async () => {
|
||||
const audios: Array<string> = []
|
||||
for (const v of dubDownloadList) {
|
||||
await incrementPlaylistCounter();
|
||||
const list = await crunchyGetPlaylist(v.guid, v.geo)
|
||||
|
||||
if (!list) return
|
||||
@ -651,6 +687,7 @@ export async function downloadCrunchyrollPlaylist(
|
||||
if (!playlist) return
|
||||
|
||||
await deleteVideoToken(episodeID, list.data.token)
|
||||
decrementPlaylistCounter();
|
||||
|
||||
const assetId = playlist.mediaGroups.AUDIO.audio.main.playlists[0].segments[0].resolvedUri.match(/\/assets\/(?:p\/)?([^_,]+)/)
|
||||
|
||||
@ -755,6 +792,7 @@ export async function downloadCrunchyrollPlaylist(
|
||||
return
|
||||
}
|
||||
|
||||
await incrementPlaylistCounter();
|
||||
const play = await crunchyGetPlaylist(code, geo)
|
||||
|
||||
if (!play) {
|
||||
@ -799,6 +837,7 @@ export async function downloadCrunchyrollPlaylist(
|
||||
if (!mdp) return
|
||||
|
||||
await deleteVideoToken(episodeID, play.data.token)
|
||||
decrementPlaylistCounter();
|
||||
|
||||
var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.height === quality)
|
||||
|
||||
|
@ -1,9 +1,17 @@
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
import settings from 'electron-settings'
|
||||
|
||||
export async function createFolder() {
|
||||
const tempFolderPath = path.join(app.getPath('documents'), `crd-tmp-${(Math.random() + 1).toString(36).substring(2)}`)
|
||||
|
||||
var tempPath = await settings.get('tempPath') as string
|
||||
|
||||
if (!tempPath) {
|
||||
tempPath = app.getPath('temp')
|
||||
}
|
||||
|
||||
const tempFolderPath = path.join(tempPath, `crd-tmp-${(Math.random() + 1).toString(36).substring(2)}`)
|
||||
try {
|
||||
await fs.promises.mkdir(tempFolderPath, { recursive: true })
|
||||
return tempFolderPath
|
||||
@ -25,6 +33,13 @@ export async function checkDirectoryExistence(dir: string) {
|
||||
}
|
||||
|
||||
export async function createFolderName(name: string, dir: string) {
|
||||
|
||||
var tempPath = await settings.get('tempPath') as string
|
||||
|
||||
if (!tempPath) {
|
||||
tempPath = app.getPath('temp')
|
||||
}
|
||||
|
||||
var folderPath
|
||||
|
||||
const dirExists = await checkDirectoryExistence(dir)
|
||||
@ -32,7 +47,7 @@ export async function createFolderName(name: string, dir: string) {
|
||||
if (dirExists) {
|
||||
folderPath = path.join(dir, name)
|
||||
} else {
|
||||
folderPath = path.join(app.getPath('documents'), name)
|
||||
folderPath = path.join(tempPath, name)
|
||||
}
|
||||
|
||||
try {
|
||||
@ -54,15 +69,21 @@ export async function deleteFolder(folderPath: string) {
|
||||
}
|
||||
|
||||
export async function deleteTemporaryFolders() {
|
||||
const documentsPath = app.getPath('documents')
|
||||
|
||||
var tempPath = await settings.get('tempPath') as string
|
||||
|
||||
if (!tempPath) {
|
||||
tempPath = app.getPath('temp')
|
||||
}
|
||||
|
||||
const folderPrefix = 'crd-tmp-'
|
||||
|
||||
try {
|
||||
const files = await fs.promises.readdir(documentsPath)
|
||||
const files = await fs.promises.readdir(tempPath)
|
||||
const tempFolders = files.filter((file) => file.startsWith(folderPrefix))
|
||||
|
||||
for (const folder of tempFolders) {
|
||||
const folderPath = path.join(documentsPath, folder)
|
||||
const folderPath = path.join(tempPath, folder)
|
||||
await deleteFolder(folderPath)
|
||||
console.log(`Temporary folder ${folder} deleted.`)
|
||||
}
|
||||
|
@ -171,6 +171,38 @@ ipcMain.handle('dialog:defaultDirectory', async () => {
|
||||
return savedPath
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:getDirectoryTEMP', async () => {
|
||||
const savedPath = await settings.get('tempPath')
|
||||
|
||||
if (!savedPath) {
|
||||
const path = app.getPath('temp')
|
||||
|
||||
await settings.set('tempPath', path)
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
return savedPath
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:openDirectoryTEMP', async () => {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog(window, {
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (canceled) {
|
||||
return await settings.get('tempPath')
|
||||
} else {
|
||||
await settings.set('tempPath', filePaths[0])
|
||||
return filePaths[0]
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:selectEndpoint', async (events, nr: number) => {
|
||||
await settings.set('CREndpoint', nr)
|
||||
|
||||
@ -248,6 +280,42 @@ app.on('window-all-closed', () => {
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:setEpisodeTemplate', async (events, name: string) => {
|
||||
await settings.set('EpisodeTemp', name)
|
||||
|
||||
return name
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:getEpisodeTemplate', async (events) => {
|
||||
const epTP = await settings.get('EpisodeTemp')
|
||||
|
||||
if (!epTP) {
|
||||
await settings.set('EpisodeTemp', '{seriesName} Season {seasonNumber} Episode {episodeNumber}')
|
||||
|
||||
return '{seriesName} Season {seasonNumber} Episode {episodeNumber}'
|
||||
}
|
||||
|
||||
return epTP
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:setSeasonTemplate', async (events, name: string) => {
|
||||
await settings.set('SeasonTemp', name)
|
||||
|
||||
return name
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:getSeasonTemplate', async (events) => {
|
||||
const seTP = await settings.get('SeasonTemp')
|
||||
|
||||
if (!seTP) {
|
||||
await settings.set('SeasonTemp', '{seriesName} Season {seasonNumber}')
|
||||
|
||||
return '{seriesName} Season {seasonNumber}'
|
||||
}
|
||||
|
||||
return seTP
|
||||
})
|
||||
|
||||
const openWindows = new Map()
|
||||
|
||||
// Open New Window
|
||||
|
@ -16,5 +16,11 @@ contextBridge.exposeInMainWorld('myAPI', {
|
||||
openWindow: (opt: { title: string; url: string; width: number; height: number; backgroundColor: string }) => ipcRenderer.invoke('window:openNewWindow', opt),
|
||||
getUpdateStatus: () => ipcRenderer.invoke('updater:getUpdateStatus'),
|
||||
startUpdateDownload: () => ipcRenderer.invoke('updater:download'),
|
||||
startUpdateInstall: () => ipcRenderer.invoke('updater:quitAndInstall')
|
||||
startUpdateInstall: () => ipcRenderer.invoke('updater:quitAndInstall'),
|
||||
selectTEMPFolder: () => ipcRenderer.invoke('dialog:openDirectoryTEMP'),
|
||||
getTEMPFolder: () => ipcRenderer.invoke('dialog:getDirectoryTEMP'),
|
||||
setEpisodeTemplate: (name: string) => ipcRenderer.invoke('dialog:setEpisodeTemplate', name),
|
||||
getEpisodeTemplate: () => ipcRenderer.invoke('dialog:getEpisodeTemplate'),
|
||||
setSeasonTemplate: (name: string) => ipcRenderer.invoke('dialog:setSeasonTemplate', name),
|
||||
getSeasonTemplate: () => ipcRenderer.invoke('dialog:getSeasonTemplate'),
|
||||
})
|
||||
|
Reference in New Issue
Block a user