diff --git a/components/Settings/Crunchyroll.vue b/components/Settings/Crunchyroll.vue index b9b4da4..c92db64 100644 --- a/components/Settings/Crunchyroll.vue +++ b/components/Settings/Crunchyroll.vue @@ -1,13 +1,54 @@ \ No newline at end of file +select { + background: url("data:image/svg+xml,") + no-repeat; + background-position: calc(100% - 0.75rem) center !important; + background-color: #5c5b5b; + -moz-appearance: none !important; + -webkit-appearance: none !important; + appearance: none !important; +} + diff --git a/components/Settings/Main.vue b/components/Settings/Main.vue index ef29e22..4497025 100644 --- a/components/Settings/Main.vue +++ b/components/Settings/Main.vue @@ -1,6 +1,6 @@ diff --git a/keys/.gitkeep b/keys/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/package.json b/package.json index 69db1ba..6fa14d2 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "!**/pages/*", "!**/ffmpeg/*", "!**/mp4decrypt/*", - "!**/keys/*", "!**/.git/*", "!**/.github/*", "!**/.nuxt/*", @@ -76,8 +75,7 @@ ], "extraResources": [ "./ffmpeg/**", - "./mp4decrypt/**", - "./keys/**" + "./mp4decrypt/**" ] } } diff --git a/src/api/routes/crunchyroll/crunchyroll.service.ts b/src/api/routes/crunchyroll/crunchyroll.service.ts index f47d796..5a21210 100644 --- a/src/api/routes/crunchyroll/crunchyroll.service.ts +++ b/src/api/routes/crunchyroll/crunchyroll.service.ts @@ -4,9 +4,10 @@ import { VideoPlaylist } from '../../types/crunchyroll' import { useFetch } from '../useFetch' import { parse as mpdParse } from 'mpd-parser' import { loggedInCheck } from '../service/service.service' +import settings from 'electron-settings' // Disable when Crunchyroll turns off switch endpoint -const enableDRMBypass = false +const enableDRMBypass = true // Crunchyroll Error message list const crErrors = [ @@ -110,6 +111,84 @@ async function crunchyLoginFetch(user: string, passw: string) { // Crunchyroll Playlist Fetch export async function crunchyGetPlaylist(q: string) { + + var endpoint = await settings.get('CREndpoint'); + const drmL3blob = await settings.get('l3blob'); + const drmL3key = await settings.get('l3key'); + + if (!drmL3blob || !drmL3key) { + await settings.set('CREndpoint', 1); + endpoint = 1 + } + + const endpoints: { id: number, name: string, url: string }[] = [ + { + id: 1, + name: 'Switch', + url: `/console/switch/play` + }, + { + id: 2, + name: 'PS4', + url: `/console/ps4/play` + }, + { + id: 3, + name: 'PS5', + url: `/console/ps5/play` + }, + { + id: 4, + name: 'XBOX One', + url: `/console/xbox_one/play` + }, + { + id: 5, + name: 'Firefox', + url: `/web/firefox/play` + }, + { + id: 6, + name: 'Edge', + url: `/web/edge/play` + }, + { + id: 7, + name: 'Safari', + url: `/web/safari/play` + }, + { + id: 8, + name: 'Chrome', + url: `/web/chrome/play` + }, + { + id: 9, + name: 'Web Fallback', + url: `/web/fallback/play` + }, + { + id: 10, + name: 'Iphone', + url: `/ios/iphone/play` + }, + { + id: 11, + name: 'Ipad', + url: `/ios/ipad/play` + }, + { + id: 12, + name: 'Android', + url: `/android/phone/play` + }, + { + id: 13, + name: 'Samsung TV', + url: `/tv/samsung/play` + }, + ] + const account = await loggedInCheck('CR') if (!account) return @@ -125,9 +204,7 @@ export async function crunchyGetPlaylist(q: string) { try { const response = await fetch( - enableDRMBypass - ? `https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/tv/samsung/play` - : `https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/console/switch/play`, + `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'}`, { method: 'GET', headers: headers @@ -143,7 +220,11 @@ export async function crunchyGetPlaylist(q: string) { return { data: data, account_id: login.account_id } } else { - throw new Error(await response.text()) + + const error = await response.text() + + messageBox('error', ['Cancel'], 2, 'Failed to get MPD Playlist', 'Failed to get MPD Playlist', error) + throw new Error(error) } } catch (e) { throw new Error(e as string) diff --git a/src/api/routes/service/service.service.ts b/src/api/routes/service/service.service.ts index 426e009..812820e 100644 --- a/src/api/routes/service/service.service.ts +++ b/src/api/routes/service/service.service.ts @@ -19,6 +19,7 @@ import { getMP4DecryptPath } from '../../services/mp4decrypt' const ffmpegP = getFFMPEGPath() const mp4e = getMP4DecryptPath() import util from 'util' +import settings from 'electron-settings' const exec = util.promisify(require('child_process').exec) // Get All Accounts @@ -366,6 +367,9 @@ export async function downloadCrunchyrollPlaylist( const seasonFolder = await createFolderName(`${name.replace(/[/\\?%*:|"<>]/g, '')} Season ${season}`, downloadPath) + const drmL3blob = await settings.get('l3blob') + const drmL3key = await settings.get('l3key') + const dubDownloadList: Array<{ audio_locale: string guid: string @@ -509,6 +513,22 @@ export async function downloadCrunchyrollPlaylist( keys = await getDRMKeys(pssh, assetId[1], list.account_id) } + if ( + (playlist.mediaGroups.AUDIO.audio.main.playlists[0].contentProtection && !drmL3blob && !drmL3key) || + (playlist.mediaGroups.AUDIO.audio.main.playlists[0].contentProtection && !drmL3blob) || + (playlist.mediaGroups.AUDIO.audio.main.playlists[0].contentProtection && !drmL3key) + ) { + await updatePlaylistByID(downloadID, 'failed') + messageBox( + 'error', + ['Cancel'], + 2, + 'Audio Widevine encrypted but no key provided', + 'Audio Widevine encrypted but no key provided', + 'To download Widevine encrypted videos add the L3 Widevine keys in Settings > Widewine > L3 Keys' + ) + return + } p.push({ filename: (playlist.mediaGroups.AUDIO.audio.main.playlists[0].segments[0].map.uri.match(/([^\/]+)\?/) as RegExpMatchArray)[1], url: playlist.mediaGroups.AUDIO.audio.main.playlists[0].segments[0].map.resolvedUri @@ -604,6 +624,23 @@ export async function downloadCrunchyrollPlaylist( keys = await getDRMKeys(pssh, assetId[1], play.account_id) } + if ( + (hq.contentProtection && !drmL3blob && !drmL3key) || + (hq.contentProtection && !drmL3blob) || + (hq.contentProtection && !drmL3key) + ) { + await updatePlaylistByID(downloadID, 'failed') + messageBox( + 'error', + ['Cancel'], + 2, + 'Audio Widevine encrypted but no key provided', + 'Audio Widevine encrypted but no key provided', + 'To download Widevine encrypted videos add the L3 Widevine keys in Settings > Widewine > L3 Keys' + ) + return + } + var p: { filename: string; url: string }[] = [] p.push({ diff --git a/src/api/services/decryption.ts b/src/api/services/decryption.ts index 540d24c..78933a0 100644 --- a/src/api/services/decryption.ts +++ b/src/api/services/decryption.ts @@ -1,12 +1,13 @@ import { Session } from '../modules/license' import { readFileSync } from 'fs' import { getWVKPath } from './widevine' -const keys = getWVKPath() export async function getDRMKeys(pssh: string, assetID: string, userID: string) { const auth = await getWVKey(assetID, userID) const depssh = Buffer.from(pssh, 'base64') + const keys = await getWVKPath() + if (!keys) return if (!keys.key) return diff --git a/src/api/services/widevine.ts b/src/api/services/widevine.ts index 762e1c4..489f913 100644 --- a/src/api/services/widevine.ts +++ b/src/api/services/widevine.ts @@ -1,23 +1,14 @@ import { app } from 'electron' +import settings from 'electron-settings' import path from 'path' -const isDev = process.env.NODE_ENV === 'development' -const appPath = app.getAppPath() -const resourcesPath = path.dirname(appPath) -const keyPath = path.join(resourcesPath, 'keys') -if (isDev) { - require('dotenv').config() -} -export function getWVKPath() { - if (isDev) { - const key = process.env.KEY_KEY - const client = process.env.KEY_CLIENT +export async function getWVKPath() { + const drmL3blob = await settings.get('l3blob') as string + const drmL3key = await settings.get('l3key') as string - return { key: key, client: client } - } else { - const key = path.join(keyPath, 'key') - const client = path.join(keyPath, 'client') - - return { key: key, client: client } + if (!drmL3blob || !drmL3key) { + return } + + return { key: drmL3key, client: drmL3blob } } diff --git a/src/electron/background.ts b/src/electron/background.ts index 7995eb9..00c4b41 100644 --- a/src/electron/background.ts +++ b/src/electron/background.ts @@ -167,6 +167,24 @@ ipcMain.handle('dialog:defaultDirectory', async () => { return savedPath }) +ipcMain.handle('dialog:selectEndpoint', async (events, nr: number) => { + await settings.set('CREndpoint', nr) + + return nr +}) + +ipcMain.handle('dialog:getEndpoint', async (events, nr: number) => { + const endpointNr = await settings.get('CREndpoint') + + if (!endpointNr) { + await settings.set('CREndpoint', 1) + + return 1 + } + + return endpointNr +}) + ipcMain.handle('dialog:defaultFile', async (events, type: string) => { if (!type) return diff --git a/src/electron/preload.ts b/src/electron/preload.ts index 3b40017..0d4907d 100644 --- a/src/electron/preload.ts +++ b/src/electron/preload.ts @@ -3,6 +3,8 @@ import { contextBridge, ipcRenderer } from 'electron' contextBridge.exposeInMainWorld('myAPI', { selectFolder: () => ipcRenderer.invoke('dialog:openDirectory'), selectFile: (type: string) => ipcRenderer.invoke('dialog:openFile', type), + selectEndpoint: (nr: number) => ipcRenderer.invoke('dialog:selectEndpoint', nr), + getEndpoint: () => ipcRenderer.invoke('dialog:getEndpoint'), getFolder: () => ipcRenderer.invoke('dialog:defaultDirectory'), getFile: (type: string) => ipcRenderer.invoke('dialog:defaultFile', type), openWindow: (opt: { title: string; url: string; width: number; height: number; backgroundColor: string }) => ipcRenderer.invoke('window:openNewWindow', opt),