From 53f6b760fe21821169fdb0ee8ebe0e347bdfa44a Mon Sep 17 00:00:00 2001 From: exttex Date: Sun, 25 Jul 2021 16:20:11 +0200 Subject: [PATCH] 1.1.24 - FLAC for HiFi users --- app/client/src/locales/pl.json | 22 ++++++------- app/client/src/main.js | 11 +++++-- app/package-lock.json | 5 +-- app/package.json | 2 +- app/src/deezer.js | 56 +++++++++++++++++++++++++++++++++- app/src/downloads.js | 18 ++++++++--- package.json | 2 +- 7 files changed, 93 insertions(+), 23 deletions(-) diff --git a/app/client/src/locales/pl.json b/app/client/src/locales/pl.json index 03e5fae..ccbe9fd 100644 --- a/app/client/src/locales/pl.json +++ b/app/client/src/locales/pl.json @@ -148,12 +148,12 @@ "Save": "Zapisz", "Edit": "Edytuj", "Importer": "Importer", - "Enter URL": "Enter URL", - "Currently only Spotify is supported and limited to 100 tracks.": "Currently only Spotify is supported and limited to 100 tracks.", - "Import into playlist": "Import into playlist", - "Keep sidebar open": "Keep sidebar open", - "WARNING: Might require reload to work properly!": "WARNING: Might require reload to work properly!", - "An error occured, URL might be invalid or unsupported.": "An error occured, URL might be invalid or unsupported.", + "Enter URL": "Wprowadź adres URL", + "Currently only Spotify is supported and limited to 100 tracks.": "Obecnie obsługuje tylko Spotify i maksymalnie 100 utworów.", + "Import into playlist": "Importuj na playlistę", + "Keep sidebar open": "Panel boczny zawsze otwarty", + "WARNING: Might require reload to work properly!": "UWAGA: Może wymagać ponownego uruchomienia, by działać poprawnie!", + "An error occured, URL might be invalid or unsupported.": "Wystąpił błąd, adres URL może być nieprawidłowy lub nieobsługiwany.", "Top tracks": "Najpopularniejsze utwory", "Show all top tracks": "Pokaż wszystkie najpopularniejsze utwory", "Singles": "Single", @@ -163,9 +163,9 @@ "No": "Nie", "Download Filename": "Nazwa pobieranego pliku", "Language": "Język", - "Background Image": "Background Image", - "Enter URL or absolute path. WARNING: Requires reload!": "Enter URL or absolute path. WARNING: Requires reload!", - "LGBT Mode": "LGBT Mode", - "Native top bar": "Native top bar", - "Requires restart of Freezer!": "Requires restart of Freezer!" + "Background Image": "Obraz w tle", + "Enter URL or absolute path. WARNING: Requires reload!": "Wprowadź adres URL lub całą ścieżkę pliku. UWAGA: Wymaga ponownego uruchomienia!", + "LGBT Mode": "Tryb LGBT", + "Native top bar": "Natywny górny pasek", + "Requires restart of Freezer!": "Wymaga ponownego uruchomiania Freezera!" } \ No newline at end of file diff --git a/app/client/src/main.js b/app/client/src/main.js index 319e04b..ea4d49b 100644 --- a/app/client/src/main.js +++ b/app/client/src/main.js @@ -236,7 +236,11 @@ new Vue({ this.playbackInfo = playbackInfo; //Stream URL - let url = `${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${this.playbackInfo.url}`; + let url; + if (this.playbackInfo.encrypted) + url = `${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${this.playbackInfo.url}`; + else + url = this.playbackInfo.direct; //Cancel loading this.loaders--; if (this.loaders > 0) { @@ -424,7 +428,10 @@ new Vue({ if (this.gapless.promise) resolve(); } this.gapless.info = info - this.gapless.audio = new Audio(`${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${info.url}`); + if (info.encrypted) + this.gapless.audio = new Audio(`${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${info.url}`); + else + this.gapless.audio = new Audio(info.direct); this.gapless.audio.volume = 0.00; this.gapless.audio.preload = 'auto'; this.gapless.crossfade = false; diff --git a/app/package-lock.json b/app/package-lock.json index 2ea9175..a3aaba9 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "freezer", - "version": "1.1.20", + "version": "1.1.23", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "freezer", - "version": "1.1.20", + "version": "1.1.23", "license": "ISC", "dependencies": { "arg": "^5.0.0", @@ -18,6 +18,7 @@ "cors": "^2.8.5", "discord-rpc": "^3.2.0", "express": "^4.17.1", + "form-data": "^2.3.3", "lastfmapi": "^0.1.1", "metaflac-js2": "^1.0.7", "nedb": "^1.8.0", diff --git a/app/package.json b/app/package.json index dc00ff4..c5af81c 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "freezer", "private": true, - "version": "1.1.23", + "version": "1.1.24", "description": "", "main": "background.js", "scripts": { diff --git a/app/src/deezer.js b/app/src/deezer.js index 5bdb80d..2f2e73c 100644 --- a/app/src/deezer.js +++ b/app/src/deezer.js @@ -140,11 +140,37 @@ class DeezerAPI { this.token = data.results.checkForm; this.userId = data.results.USER.USER_ID; this.favoritesPlaylist = data.results.USER.LOVEDTRACKS_ID.toString(); + try { + await this.authorizeAtv(); + } catch (e) { + logger.warn(`Couldnt authorize ATV: ${e}`); + } if (!this.userId || this.userId == 0 || !this.token) return false; return true; } + //Authorize Android TV + async authorizeAtv() { + let res = await axios.post( + "https://distribution-api.deezer.com/device/token", + {"brand_name":"Hisense","device_id":"7239e4071d8992c955ad","model_name":"HE50A6109FUWTS","country_code":"FRA"} + ); + let deviceToken = res.data.device_token; + // Get unauthorized token + res = await axios.get('https://connect.deezer.com/oauth/access_token.php?grant_type=client_credentials&client_id=447462&client_secret=a83bf7f38ad2f137e444727cfc3775cf&output=json'); + let accessToken = res.data.access_token; + // Get smart login code + res = await axios.post(`https://connect.deezer.com/2.0/smartlogin/device?access_token=${accessToken}&device_token=${deviceToken}`); + let smartLoginCode = res.data.data.smartLoginCode; + // Associate + await this.callApi('deezer.associateSmartLoginCodeWithUser', {'SMARTLOGIN_CODE': smartLoginCode}); + // Get authorized token + res = await axios.get(`https://connect.deezer.com/2.0/smartlogin/${smartLoginCode}?access_token=${accessToken}`); + accessToken = res.data.data.accessToken; + this.accessToken = accessToken; + } + //Wrapper because electron is piece of shit async callPublicApi(path, params) { if (this.electron) return await this._callPublicApiElectron(path, params); @@ -223,6 +249,30 @@ class DeezerAPI { return `https://e-cdns-proxy-${md5origin.substring(0, 1)}.dzcdn.net/mobile/1/${step3}`; } + //Generate url with android tv support + async generateUrl(trackId, md5origin, mediaVersion, quality = 3) { + if (quality == 9 && this.accessToken) { + try { + let res = await axios({ + method: 'GET', + url: `https://api.deezer.com/platform/gcast/track/${trackId}/streamUrls`, + headers: { + "Authorization": "Bearer " + this.accessToken + }, + responseType: 'json' + }); + if (res.data.data.attributes.url_flac) + return {encrypted: false, url: res.data.data.attributes.url_flac}; + } catch (e) { + console.warn(`Failed getting ATV URL! Using normal: ${e}`) + } + } + return { + encrypted: true, + url: DeezerAPI.getUrl(trackId, md5origin, mediaVersion, quality) + }; + } + async fallback(info, quality = 3) { let qualityInfo = Track.getUrlInfo(info); @@ -266,11 +316,15 @@ class DeezerAPI { //Fallback thru available qualities, -1 if none work async qualityFallback(info, quality = 3) { try { - let res = await axios.head(DeezerAPI.getUrl(info.trackId, info.md5origin, info.mediaVersion, quality)); + let urlGen = await this.generateUrl(info.trackId, info.md5origin, info.mediaVersion, quality); + let res = await axios.head(urlGen.url); if (res.status > 400) throw new Error(`Status code: ${res.status}`); //Make sure it's an int info.quality = parseInt(quality.toString(), 10); info.size = parseInt(res.headers['content-length'], 10); + info.encrypted = urlGen.encrypted; + if (!info.encrypted) + info.direct = urlGen.url; return info; } catch (e) { logger.warn('Quality fallback: ' + e); diff --git a/app/src/downloads.js b/app/src/downloads.js index b9971cc..90b3c3c 100644 --- a/app/src/downloads.js +++ b/app/src/downloads.js @@ -206,6 +206,7 @@ class DownloadThread { this.stopped = true; this.isUserUploaded = download.track.id.toString().startsWith('-'); this.coverPath = null; + this.encrypted = false; } //Callback wrapper @@ -262,9 +263,11 @@ class DownloadThread { this.download.downloaded = start; //Download - let url = DeezerAPI.getUrl(this.qualityInfo.trackId, this.qualityInfo.md5origin, this.qualityInfo.mediaVersion, this.qualityInfo.quality); + let urlGen = await deezer.generateUrl(this.qualityInfo.trackId, this.qualityInfo.md5origin, this.qualityInfo.mediaVersion, this.qualityInfo.quality); + this.encrypted = urlGen.encrypted; + if (this.stopped) return; - this._request = https.get(url, {headers: {'Range': `bytes=${start}-`}}, (r) => { + this._request = https.get(urlGen.url, {headers: {'Range': `bytes=${start}-`}}, (r) => { this._response = r; let outFile = fs.createWriteStream(tmp, {flags: 'a'}); @@ -321,13 +324,18 @@ class DownloadThread { this.download.state = 2; //Decrypt - decryptor.decryptFile(decryptor.getKey(this.qualityInfo.trackId), tmp, `${tmp}.DEC`); let outPath = this.generatePath(this.qualityInfo.quality); await fs.promises.mkdir(path.dirname(outPath), {recursive: true}); - await fs.promises.copyFile(`${tmp}.DEC`, outPath); - await fs.promises.unlink(`${tmp}.DEC`); + if (this.encrypted) { + decryptor.decryptFile(decryptor.getKey(this.qualityInfo.trackId), tmp, `${tmp}.DEC`); + await fs.promises.copyFile(`${tmp}.DEC`, outPath); + await fs.promises.unlink(`${tmp}.DEC`); + } else { + await fs.promises.copyFile(tmp, outPath); + } await fs.promises.unlink(tmp); + let cover = null; if (!this.isUserUploaded) { diff --git a/package.json b/package.json index 6e788ea..b303f9e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "freezer", "private": true, - "version": "1.1.23", + "version": "1.1.24", "description": "Freezer PC", "scripts": { "pack": "electron-builder --dir",