added better activestreams limiter and account limit checker
This commit is contained in:
parent
cecdd683c5
commit
b52d93a7f3
@ -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>
|
||||||
|
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",
|
"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",
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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.`)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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'),
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user