updated electron and changed video token invalidation process

This commit is contained in:
stratuma 2024-05-24 15:48:17 +02:00
parent 38d58a7dd7
commit 4cb0c59ccf
7 changed files with 479 additions and 405 deletions

View File

@ -29,7 +29,7 @@
"@types/node-cron": "^3.0.11", "@types/node-cron": "^3.0.11",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"electron": "^30.0.6", "electron": "^30.0.8",
"electron-builder": "^24.13.3", "electron-builder": "^24.13.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0", "eslint-config-prettier": "^8.10.0",

647
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -502,7 +502,7 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
throw new Error(e as string) throw new Error(e as string)
} }
if (isProxyActive) await deactivateVideoToken(q, playlist.token) if (isProxyActive) await deleteVideoToken(q, playlist.token)
decrementPlaylistCounter() decrementPlaylistCounter()
for (const p of proxies) { for (const p of proxies) {
@ -599,7 +599,7 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
} }
// Crunchyroll Delete Video Token Fetch // Crunchyroll Delete Video Token Fetch
export async function deleteVideoToken(content: string, token: string) { export async function removeVideoToken(content: string, token: string) {
const account = await loggedInCheck('CR') const account = await loggedInCheck('CR')
if (!account) return if (!account) return
@ -644,7 +644,7 @@ export async function deleteVideoToken(content: string, token: string) {
} }
// Crunchyroll Deactivate Video Token Fetch // Crunchyroll Deactivate Video Token Fetch
export async function deactivateVideoToken(content: string, token: string) { export async function deleteVideoToken(content: string, token: string) {
const account = await loggedInCheck('CR') const account = await loggedInCheck('CR')
if (!account) return if (!account) return
@ -748,6 +748,15 @@ export async function crunchyGetPlaylistMPD(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 regex = /\/manifest\/([A-Z0-9]+)\/.*\?playbackGuid=([^&]+)/
const match = q.match(regex)
if (!match) return
const contentID = match[1]
const playlistID = match[2]
try { try {
const response = await fetch(q, { const response = await fetch(q, {
method: 'GET', method: 'GET',
@ -755,6 +764,7 @@ export async function crunchyGetPlaylistMPD(q: string, geo: string | undefined)
}) })
if (response.ok) { if (response.ok) {
await deleteVideoToken(contentID, playlistID)
const raw = await response.text() const raw = await response.text()
const parsed = mpdParse(raw) const parsed = mpdParse(raw)
@ -762,6 +772,19 @@ export async function crunchyGetPlaylistMPD(q: string, geo: string | undefined)
return parsed return parsed
} else { } else {
const error = await response.text() const error = await response.text()
const errorJSON: {
error: string
} = await JSON.parse(error)
if (errorJSON.error === 'Invalid streaming token') {
decrementPlaylistCounter()
await incrementPlaylistCounter()
await activateVideoToken(contentID, playlistID)
return await crunchyGetPlaylistMPD(q, geo)
}
messageBox('error', ['Cancel'], 2, 'Failed to get Crunchyroll MPD', 'Failed to get Crunchyroll MPD', error) messageBox('error', ['Cancel'], 2, 'Failed to get Crunchyroll MPD', 'Failed to get Crunchyroll MPD', error)
server.logger.log({ server.logger.log({
level: 'error', level: 'error',
@ -770,7 +793,7 @@ export async function crunchyGetPlaylistMPD(q: string, geo: string | undefined)
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
section: 'mpdCrunchyrollFetch' section: 'mpdCrunchyrollFetch'
}) })
throw new Error(await response.text()) throw new Error(error)
} }
} catch (e) { } catch (e) {
throw new Error(e as string) 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 { checkAccountMaxStreams, crunchyGetPlaylist, crunchyGetPlaylistMPD, deleteVideoToken } from '../crunchyroll/crunchyroll.service' import { checkAccountMaxStreams, crunchyGetPlaylist, crunchyGetPlaylistMPD } 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'
@ -490,7 +490,6 @@ export async function downloadCrunchyrollPlaylist(
if (playlist.data.audioLocale !== subs[0]) { if (playlist.data.audioLocale !== subs[0]) {
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)
playlist = await crunchyGetPlaylist(found.guid, found.geo) playlist = await crunchyGetPlaylist(found.guid, found.geo)
} else { } else {
console.log('Exact Playlist not found, taking what crunchy gives.') console.log('Exact Playlist not found, taking what crunchy gives.')
@ -518,8 +517,6 @@ export async function downloadCrunchyrollPlaylist(
return return
} }
await deleteVideoToken(episodeID, playlist.data.token)
const subFolder = await createFolder() const subFolder = await createFolder()
const audioFolder = await createFolder() const audioFolder = await createFolder()
@ -609,8 +606,6 @@ export async function downloadCrunchyrollPlaylist(
section: 'crunchyrollDownloadProcessSubtitles' section: 'crunchyrollDownloadProcessSubtitles'
}) })
} }
await deleteVideoToken(episodeID, subPlaylist.data.token)
} }
await updatePlaylistByID(downloadID, 'waiting for dub playlist') await updatePlaylistByID(downloadID, 'waiting for dub playlist')
@ -624,8 +619,6 @@ export async function downloadCrunchyrollPlaylist(
if (found) { if (found) {
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)
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 })
@ -672,17 +665,18 @@ export async function downloadCrunchyrollPlaylist(
const sbs: Array<string> = [] const sbs: Array<string> = []
for (const sub of subDownloadList) { for (const sub of subDownloadList) {
const name = await downloadCRSub(sub, subFolder, quality) const name = await downloadCRSub(sub, subFolder, quality)
if (!name) return
sbs.push(name) sbs.push(name)
} }
return sbs return sbs
} }
const audioDownload = async () => { const audioDownload = async () => {
const audios: Array<string> = [] const audios: Array<string | undefined> = new Array(dubDownloadList.length).fill(undefined)
const concurrentDownloads = 2 const concurrentDownloads = 2
const tasks: Array<Promise<void>> = [] const tasks: Array<Promise<void>> = []
const downloadTask = async (v: any) => { const downloadTask = async (v: any, index: number) => {
const list = await crunchyGetPlaylist(v.guid, v.geo) const list = await crunchyGetPlaylist(v.guid, v.geo)
if (!list) return if (!list) return
@ -691,8 +685,6 @@ export async function downloadCrunchyrollPlaylist(
if (!playlist) return if (!playlist) return
await deleteVideoToken(episodeID, list.data.token)
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\/)?([^_,]+)/)
if (!assetId) { if (!assetId) {
@ -764,12 +756,12 @@ export async function downloadCrunchyrollPlaylist(
const path = await downloadMPDAudio(p, audioFolder, list.data.audioLocale, downloadID, keys ? keys : undefined) const path = await downloadMPDAudio(p, audioFolder, list.data.audioLocale, downloadID, keys ? keys : undefined)
if (path) { if (path) {
audios.push(path as string) audios[index] = path as string
} }
} }
for (const v of dubDownloadList) { for (const [index, v] of dubDownloadList.entries()) {
const task = downloadTask(v).finally(() => { const task = downloadTask(v, index).finally(() => {
tasks.splice(tasks.indexOf(task), 1) tasks.splice(tasks.indexOf(task), 1)
}) })
@ -782,7 +774,7 @@ export async function downloadCrunchyrollPlaylist(
await Promise.all(tasks) await Promise.all(tasks)
return audios return audios.filter((path) => path !== undefined) as string[]
} }
const downloadVideo = async () => { const downloadVideo = async () => {
@ -866,8 +858,6 @@ export async function downloadCrunchyrollPlaylist(
if (!mdp) return if (!mdp) return
await deleteVideoToken(episodeID, play.data.token)
var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.height === quality) var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.height === quality)
if (!hq) { if (!hq) {
@ -987,6 +977,8 @@ export async function downloadCrunchyrollPlaylist(
if (!audios) return if (!audios) return
if (!subss) return
await updatePlaylistByID(downloadID, 'merging video & audio') await updatePlaylistByID(downloadID, 'merging video & audio')
var episodeNaming = (await settings.get('EpisodeTemp')) as string var episodeNaming = (await settings.get('EpisodeTemp')) as string
@ -1308,8 +1300,25 @@ export async function checkProxies() {
if (response.ok) { if (response.ok) {
p.status = 'online' p.status = 'online'
server.logger.log({
level: 'info',
message: 'Proxy fetch successful, marking as online',
proxy: p.name,
timestamp: new Date().toISOString(),
section: 'checkProxyFetch'
})
} else { } else {
const data = await response.text()
p.status = 'offline' p.status = 'offline'
server.logger.log({
level: 'error',
message: 'Proxy fetch failed, marking as offline',
proxy: p.name,
error: data,
timestamp: new Date().toISOString(),
section: 'checkProxyFetch'
})
} }
} }

View File

@ -42,17 +42,23 @@ export async function downloadMPDAudio(
audio: name audio: name
}) })
const dn = downloading.find((i) => i.id === downloadID && i.audio === name)
for (const [index, part] of parts.entries()) { for (const [index, part] of parts.entries()) {
let success = false let success = false
while (!success) { while (!success) {
try { try {
var stream var stream
const response = await fetch(part.url)
if (!response.ok) {
throw Error(await response.text())
}
stream = fs.createWriteStream(`${path}/${part.filename}`) stream = fs.createWriteStream(`${path}/${part.filename}`)
const { body } = await fetch(part.url) const readableStream = Readable.from(response.body as any)
const readableStream = Readable.from(body as any)
await finished(readableStream.pipe(stream)) await finished(readableStream.pipe(stream))
@ -60,6 +66,9 @@ export async function downloadMPDAudio(
success = true success = true
} catch (error) { } catch (error) {
if (dn) {
dn.status = 'failed'
}
console.error(`Error occurred during download of fragment ${index + 1}:`, error) console.error(`Error occurred during download of fragment ${index + 1}:`, error)
server.logger.log({ server.logger.log({
level: 'error', level: 'error',
@ -138,6 +147,17 @@ async function mergePartsAudio(
.input(concatenatedFile) .input(concatenatedFile)
.outputOptions('-c copy') .outputOptions('-c copy')
.save(`${dir}/${name}.aac`) .save(`${dir}/${name}.aac`)
.on('error', (error) => {
console.log(error)
server.logger.log({
level: 'error',
message: `Error merging audio fragments of Download ${downloadID} Audio ${name}`,
error: error,
timestamp: new Date().toISOString(),
section: 'crunchyrollDownloadProcessAudioMergingFFMPEG'
})
reject(error)
})
.on('end', async () => { .on('end', async () => {
console.log('Merging finished') console.log('Merging finished')
await deleteFolder(tmp) await deleteFolder(tmp)

View File

@ -1,10 +1,18 @@
import fs from 'fs' import fs from 'fs'
import { server } from '../api'
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) => {
server.logger.log({
level: 'error',
message: `Error while concatenating`,
error: error,
timestamp: new Date().toISOString(),
section: 'crunchyrollDownloadProcessConcatenate'
})
reject(error) reject(error)
}) })
@ -22,6 +30,13 @@ export async function concatenateTSFiles(inputFiles: Array<string>, outputFile:
const readStream = fs.createReadStream(inputFiles[index]) const readStream = fs.createReadStream(inputFiles[index])
readStream.on('error', (error) => { readStream.on('error', (error) => {
server.logger.log({
level: 'error',
message: `Error while concatenating`,
error: error,
timestamp: new Date().toISOString(),
section: 'crunchyrollDownloadProcessConcatenate'
})
reject(error) reject(error)
}) })

View File

@ -3,6 +3,7 @@ import { parse, stringify } from 'ass-compiler'
import { Readable } from 'stream' import { Readable } from 'stream'
import { finished } from 'stream/promises' import { finished } from 'stream/promises'
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import { server } from '../api'
export async function downloadCRSub( export async function downloadCRSub(
sub: { sub: {
@ -14,6 +15,7 @@ export async function downloadCRSub(
dir: string, dir: string,
qual: 1080 | 720 | 480 | 360 | 240 qual: 1080 | 720 | 480 | 360 | 240
) { ) {
try {
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
@ -69,6 +71,16 @@ export async function downloadCRSub(
console.log(`Sub ${sub.language}.${sub.format} downloaded`) console.log(`Sub ${sub.language}.${sub.format} downloaded`)
return path return path
} catch (e) {
console.log('Failed to download and parse subs')
server.logger.log({
level: 'error',
message: 'Failed to download and parse subs',
error: e,
timestamp: new Date().toISOString(),
section: 'subDownloadProcess'
})
}
} }
function resamplePOSSubtitle(subtitle: string, ox: number, oy: number, nx: number, ny: number) { function resamplePOSSubtitle(subtitle: string, ox: number, oy: number, nx: number, ny: number) {