added better activestreams limiter and account limit checker

This commit is contained in:
stratuma 2024-05-23 02:22:58 +02:00
parent cecdd683c5
commit b52d93a7f3
10 changed files with 410 additions and 32 deletions

View File

@ -5,7 +5,7 @@
Crunchyroll <br /> Crunchyroll <br />
Downloader Downloader
</div> </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 class="absolute right-0 bottom-0 text-xs text-gray-200"> Made by Stratum </div>
</div> </div>
</template> </template>

View 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>

View 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>

View File

@ -2,7 +2,7 @@
"name": "crunchyroll-downloader", "name": "crunchyroll-downloader",
"author": "Stratum", "author": "Stratum",
"description": "Crunchyroll Downloader", "description": "Crunchyroll Downloader",
"version": "1.1.4", "version": "1.1.5",
"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",

View File

@ -14,15 +14,17 @@
</button> </button>
</div> </div>
<SettingsMain v-if="activeIndex === 0" /> <SettingsMain v-if="activeIndex === 0" />
<SettingsCrunchyroll v-if="activeIndex === 1" /> <SettingsNaming v-if="activeIndex === 1" />
<SettingsWidevine v-if="activeIndex === 2" /> <SettingsCrunchyroll v-if="activeIndex === 2" />
<SettingsProxy v-if="activeIndex === 3" /> <SettingsWidevine v-if="activeIndex === 3" />
<SettingsAbout v-if="activeIndex === 4" /> <SettingsProxy v-if="activeIndex === 4" />
<SettingsSystem v-if="activeIndex === 5" />
<SettingsAbout v-if="activeIndex === 6" />
</div> </div>
</template> </template>
<script lang="ts" setup> <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) const activeIndex = ref(0)
</script> </script>

View File

@ -129,7 +129,7 @@ async function crunchyLoginFetchProxy(user: string, passw: string, geo: string)
headers = { headers = {
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=', Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=',
'Content-Type': 'application/json', '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 = { body = {
@ -139,7 +139,7 @@ async function crunchyLoginFetchProxy(user: string, passw: string, geo: string)
scope: 'offline_access', scope: 'offline_access',
device_name: 'RMX2170', device_name: 'RMX2170',
device_type: 'realme 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=' token: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4='
} }
} }
@ -218,7 +218,7 @@ async function crunchyLoginFetch(user: string, passw: string) {
headers = { headers = {
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=', Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=',
'Content-Type': 'application/x-www-form-urlencoded', '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 = { body = {
@ -377,7 +377,7 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
const headersLoc = { const headersLoc = {
Authorization: `Bearer ${login.access_token}`, Authorization: `Bearer ${login.access_token}`,
'X-Cr-Disable-Drm': 'true', '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' '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) 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) 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' '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}${ `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' 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) { if (responseProx.ok) {
const data: VideoPlaylistNoGEO = JSON.parse(await response.text()) 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)) { if (!playlist.versions.find((ver) => ver.guid === v.guid)) {
playlist.versions.push({ ...v, geo: p.code }) 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)) { if (!playlist.subtitles.find((ver) => ver.language === v.language)) {
playlist.subtitles.push({ ...v, geo: p.code }) 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)) { if (!playlist.hardSubs.find((ver) => ver.hlang === v.hlang)) {
playlist.hardSubs.push({ ...v, geo: p.code }) 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}` Authorization: `Bearer ${login.access_token}`
} }
const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${content}/${token}/inactive`, { const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/token/${content}/${token}`, {
method: 'PATCH', method: 'DELETE',
headers: headers headers: headers
}) })
@ -584,3 +594,109 @@ export async function crunchyGetPlaylistMPD(q: string, geo: string | undefined)
throw new Error(e as string) 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)
}
}

View File

@ -4,7 +4,7 @@ import { concatenateTSFiles } from '../../services/concatenate'
import { createFolder, createFolderName, deleteFolder, deleteTemporaryFolders } from '../../services/folder' import { createFolder, createFolderName, deleteFolder, deleteTemporaryFolders } from '../../services/folder'
import { downloadADNSub, downloadCRSub } from '../../services/subs' import { downloadADNSub, downloadCRSub } from '../../services/subs'
import { CrunchyEpisode } from '../../types/crunchyroll' 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' import fs from 'fs'
var cron = require('node-cron') var cron = require('node-cron')
import { Readable } from 'stream' import { Readable } from 'stream'
@ -159,7 +159,7 @@ async function deletePlaylistandTMP() {
} }
} }
setTimeout(deletePlaylistandTMP, 500) deletePlaylistandTMP();
// Update Playlist Item // Update Playlist Item
export async function updatePlaylistByID( export async function updatePlaylistByID(
@ -425,6 +425,27 @@ export async function downloadADNPlaylist(
await deleteFolder(videoFolder) 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 // Download Crunchyroll Playlist
export async function downloadCrunchyrollPlaylist( export async function downloadCrunchyrollPlaylist(
e: string, e: string,
@ -449,8 +470,15 @@ export async function downloadCrunchyrollPlaylist(
totalDownloaded: 0 totalDownloaded: 0
}) })
const accmaxstream = await checkAccountMaxStreams();
if (accmaxstream) {
maxLimit = accmaxstream
}
await updatePlaylistByID(downloadID, 'downloading') await updatePlaylistByID(downloadID, 'downloading')
await incrementPlaylistCounter();
var playlist = await crunchyGetPlaylist(e, geo) var playlist = await crunchyGetPlaylist(e, geo)
if (!playlist) { if (!playlist) {
@ -471,7 +499,9 @@ export async function downloadCrunchyrollPlaylist(
const found = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP') const found = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
if (found) { if (found) {
await deleteVideoToken(episodeID, playlist.data.token) await deleteVideoToken(episodeID, playlist.data.token)
decrementPlaylistCounter();
playlist = await crunchyGetPlaylist(found.guid, found.geo) playlist = await crunchyGetPlaylist(found.guid, found.geo)
await incrementPlaylistCounter();
} else { } else {
console.log('Exact Playlist not found, taking what crunchy gives.') console.log('Exact Playlist not found, taking what crunchy gives.')
messageBox( messageBox(
@ -499,6 +529,7 @@ export async function downloadCrunchyrollPlaylist(
} }
await deleteVideoToken(episodeID, playlist.data.token) await deleteVideoToken(episodeID, playlist.data.token)
decrementPlaylistCounter();
const subFolder = await createFolder() const subFolder = await createFolder()
@ -537,6 +568,7 @@ export async function downloadCrunchyrollPlaylist(
if (playlist.data.audioLocale !== 'ja-JP') { if (playlist.data.audioLocale !== 'ja-JP') {
const foundStream = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP') const foundStream = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
if (foundStream) { if (foundStream) {
await incrementPlaylistCounter();
subPlaylist = await crunchyGetPlaylist(foundStream.guid, foundStream.geo) subPlaylist = await crunchyGetPlaylist(foundStream.guid, foundStream.geo)
} }
} else { } 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) { for (const d of dubs) {
@ -586,16 +619,18 @@ export async function downloadCrunchyrollPlaylist(
} }
if (found) { if (found) {
await incrementPlaylistCounter();
const list = await crunchyGetPlaylist(found.guid, found.geo) const list = await crunchyGetPlaylist(found.guid, found.geo)
if (list) { if (list) {
await deleteVideoToken(episodeID, list.data.token)
decrementPlaylistCounter();
const foundSub = list.data.subtitles.find((sub) => sub.language === d) const foundSub = list.data.subtitles.find((sub) => sub.language === d)
if (foundSub) { if (foundSub) {
subDownloadList.push({ ...foundSub, isDub: true }) subDownloadList.push({ ...foundSub, isDub: true })
} else { } else {
console.log(`No Dub Sub Found for ${d}`) console.log(`No Dub Sub Found for ${d}`)
} }
await deleteVideoToken(episodeID, playlist.data.token)
} }
dubDownloadList.push(found) dubDownloadList.push(found)
console.log(`Audio ${d}.aac found, adding to download`) console.log(`Audio ${d}.aac found, adding to download`)
@ -642,6 +677,7 @@ export async function downloadCrunchyrollPlaylist(
const audioDownload = async () => { const audioDownload = async () => {
const audios: Array<string> = [] const audios: Array<string> = []
for (const v of dubDownloadList) { for (const v of dubDownloadList) {
await incrementPlaylistCounter();
const list = await crunchyGetPlaylist(v.guid, v.geo) const list = await crunchyGetPlaylist(v.guid, v.geo)
if (!list) return if (!list) return
@ -651,6 +687,7 @@ export async function downloadCrunchyrollPlaylist(
if (!playlist) return if (!playlist) return
await deleteVideoToken(episodeID, list.data.token) await deleteVideoToken(episodeID, list.data.token)
decrementPlaylistCounter();
const assetId = playlist.mediaGroups.AUDIO.audio.main.playlists[0].segments[0].resolvedUri.match(/\/assets\/(?:p\/)?([^_,]+)/) const assetId = playlist.mediaGroups.AUDIO.audio.main.playlists[0].segments[0].resolvedUri.match(/\/assets\/(?:p\/)?([^_,]+)/)
@ -755,6 +792,7 @@ export async function downloadCrunchyrollPlaylist(
return return
} }
await incrementPlaylistCounter();
const play = await crunchyGetPlaylist(code, geo) const play = await crunchyGetPlaylist(code, geo)
if (!play) { if (!play) {
@ -799,6 +837,7 @@ export async function downloadCrunchyrollPlaylist(
if (!mdp) return if (!mdp) return
await deleteVideoToken(episodeID, play.data.token) await deleteVideoToken(episodeID, play.data.token)
decrementPlaylistCounter();
var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.height === quality) var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.height === quality)

View File

@ -1,9 +1,17 @@
import path from 'path' import path from 'path'
import { app } from 'electron' import { app } from 'electron'
import fs from 'fs' import fs from 'fs'
import settings from 'electron-settings'
export async function createFolder() { 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 { try {
await fs.promises.mkdir(tempFolderPath, { recursive: true }) await fs.promises.mkdir(tempFolderPath, { recursive: true })
return tempFolderPath return tempFolderPath
@ -25,6 +33,13 @@ export async function checkDirectoryExistence(dir: string) {
} }
export async function createFolderName(name: string, 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 var folderPath
const dirExists = await checkDirectoryExistence(dir) const dirExists = await checkDirectoryExistence(dir)
@ -32,7 +47,7 @@ export async function createFolderName(name: string, dir: string) {
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(tempPath, name)
} }
try { try {
@ -54,15 +69,21 @@ export async function deleteFolder(folderPath: string) {
} }
export async function deleteTemporaryFolders() { 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-' const folderPrefix = 'crd-tmp-'
try { try {
const files = await fs.promises.readdir(documentsPath) const files = await fs.promises.readdir(tempPath)
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(tempPath, folder)
await deleteFolder(folderPath) await deleteFolder(folderPath)
console.log(`Temporary folder ${folder} deleted.`) console.log(`Temporary folder ${folder} deleted.`)
} }

View File

@ -171,6 +171,38 @@ ipcMain.handle('dialog:defaultDirectory', async () => {
return savedPath 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) => { ipcMain.handle('dialog:selectEndpoint', async (events, nr: number) => {
await settings.set('CREndpoint', nr) 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() const openWindows = new Map()
// Open New Window // Open New Window

View File

@ -16,5 +16,11 @@ contextBridge.exposeInMainWorld('myAPI', {
openWindow: (opt: { title: string; url: string; width: number; height: number; backgroundColor: string }) => ipcRenderer.invoke('window:openNewWindow', opt), openWindow: (opt: { title: string; url: string; width: number; height: number; backgroundColor: string }) => ipcRenderer.invoke('window:openNewWindow', opt),
getUpdateStatus: () => ipcRenderer.invoke('updater:getUpdateStatus'), getUpdateStatus: () => ipcRenderer.invoke('updater:getUpdateStatus'),
startUpdateDownload: () => ipcRenderer.invoke('updater:download'), 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'),
}) })