1.1.14 - importing and what not

This commit is contained in:
exttex 2021-01-25 21:00:06 +01:00
parent def6d5286d
commit b0ebe66d54
16 changed files with 500 additions and 51 deletions

View File

@ -27,8 +27,8 @@
permanent permanent
fixed fixed
app app
mini-variant :mini-variant='!this.$root.settings.sidebarOpen'
expand-on-hover :expand-on-hover='!this.$root.settings.sidebarOpen'
><v-list nav dense> ><v-list nav dense>
<!-- Profile --> <!-- Profile -->
@ -127,6 +127,16 @@
<v-list-item-title>{{$t('Downloads')}}</v-list-item-title> <v-list-item-title>{{$t('Downloads')}}</v-list-item-title>
</v-list-item> </v-list-item>
<!-- Importer -->
<v-list-item link to='/importer'>
<v-list-item-icon>
<v-icon v-if='!$root.importer.done && !$root.importer.active'>mdi-import</v-icon>
<v-icon v-if='$root.importer.done' color='primary'>mdi-check</v-icon>
<v-progress-circular indeterminate style='top: -8px' size='42' v-if='$root.importer.active'></v-progress-circular>
</v-list-item-icon>
<v-list-item-title>{{$t('Importer')}}</v-list-item-title>
</v-list-item>
<!-- About --> <!-- About -->
<v-list-item link to='/about'> <v-list-item link to='/about'>
<v-list-item-icon> <v-list-item-icon>
@ -243,8 +253,7 @@
<v-spacer></v-spacer> <v-spacer></v-spacer>
<!-- Volume --> <!-- Volume -->
<v-col cols='auto' class='d-none d-sm-flex px-2' @click.stop> <v-col cols='auto' class='d-none d-sm-flex px-2' @click.stop ref='volumeBar'>
<div style='width: 180px;' class='d-flex'> <div style='width: 180px;' class='d-flex'>
<v-slider <v-slider
dense dense
@ -445,6 +454,23 @@ export default {
} }
}, },
async mounted() { async mounted() {
//Scroll on volume
this.$refs.volumeBar.addEventListener('wheel', e => {
//Volup
if (e.deltaY < 0) {
if (this.volume + 0.05 > 1)
this.volume = 1;
else
this.volume += 0.05;
} else {
//Voldown
if (this.volume - 0.05 < 0)
this.volume = 0;
else
this.volume -= 0.05;
}
});
//onClick for footer //onClick for footer
this.$refs.footer.addEventListener('click', () => { this.$refs.footer.addEventListener('click', () => {
if (this.$root.track) this.showPlayer = true; if (this.$root.track) this.showPlayer = true;

View File

@ -1,11 +1,11 @@
<template> <template>
<div> <div>
<v-card max-width='175px' max-height='210px' @click='play' :loading='loading' elevation='0' color='transparent'> <v-card max-width='175px' max-height='220px' height='220px' @click='play' :loading='loading' elevation='0' color='transparent'>
<v-img :src='stl.cover.thumb'> <v-img :src='stl.cover.thumb'>
</v-img> </v-img>
<div class='pa-2 text-subtitle-2 text-center text-truncate'>{{stl.title}}</div> <div class='pa-2 text-subtitle-2'>{{stl.subtitle}}</div>
</v-card> </v-card>
</div> </div>

View File

@ -12,6 +12,7 @@ import Settings from '@/views/Settings.vue';
import DeezerPage from '@/views/DeezerPage.vue'; import DeezerPage from '@/views/DeezerPage.vue';
import DownloadsPage from '@/views/DownloadsPage.vue'; import DownloadsPage from '@/views/DownloadsPage.vue';
import About from '@/views/About.vue'; import About from '@/views/About.vue';
import Importer from '@/views/Importer.vue';
Vue.use(VueRouter); Vue.use(VueRouter);
@ -79,6 +80,10 @@ const routes = [
{ {
path: '/about', path: '/about',
component: About component: About
},
{
path: '/importer',
component: Importer
} }
]; ];

View File

@ -146,5 +146,12 @@
"Collaborative": "Collaborative", "Collaborative": "Collaborative",
"Edit playlist": "Edit playlist", "Edit playlist": "Edit playlist",
"Save": "Save", "Save": "Save",
"Edit": "Edit" "Edit": "Edit",
"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."
} }

View File

@ -106,6 +106,14 @@ new Vue({
} }
}, },
//Importer
importer: {
active: false,
done: false,
error: false,
tracks: []
},
//Used to prevent double listen logging //Used to prevent double listen logging
logListenId: null, logListenId: null,
@ -586,6 +594,27 @@ new Vue({
this.seek(data.position); this.seek(data.position);
}); });
//Importer
//Start
this.sockets.subscribe('importerInit', (data) => {
this.importer = data;
});
//New track imported
this.sockets.subscribe('importerTrack', (data) => {
this.importer.tracks.push(data);
});
//Mark as done
this.sockets.subscribe('importerDone', () => {
this.importer.active = false;
this.importer.done = true;
});
this.sockets.subscribe('importerError', () => {
this.importer.error = true;
this.importer.active = false;
this.importer.done = false;
});
r(); r();
}, },
@ -605,6 +634,9 @@ new Vue({
document.addEventListener('keyup', (e) => { document.addEventListener('keyup', (e) => {
//Don't handle keystrokes in text fields //Don't handle keystrokes in text fields
if (e.target.tagName == "INPUT") return; if (e.target.tagName == "INPUT") return;
//Don't handle if specials
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
//K toggle playback //K toggle playback
if (e.code == "KeyK" || e.code == "Space") this.$root.toggle(); if (e.code == "KeyK" || e.code == "Space") this.$root.toggle();
//L +10s (from YT) //L +10s (from YT)

View File

@ -100,23 +100,25 @@
</v-btn> </v-btn>
<!-- Volume --> <!-- Volume -->
<v-slider <div ref='volumeBar' style='width: 100%;'>
min='0.00' <v-slider
:prepend-icon='$root.muted ? "mdi-volume-off" : "mdi-volume-high"' min='0.00'
max='1.00' :prepend-icon='$root.muted ? "mdi-volume-off" : "mdi-volume-high"'
step='0.01' max='1.00'
v-model='$root.volume' step='0.01'
class='px-8' v-model='$root.volume'
style='padding-top: 2px;' class='px-8'
@change='updateVolume' style='padding-top: 2px;'
@click:prepend='$root.toggleMute()' @change='updateVolume'
> @click:prepend='$root.toggleMute()'
<template v-slot:append> >
<div style='position: absolute; padding-top: 4px;'> <template v-slot:append>
{{Math.round($root.volume * 100)}}% <div style='position: absolute; padding-top: 4px;'>
</div> {{Math.round($root.volume * 100)}}%
</template> </div>
</v-slider> </template>
</v-slider>
</div>
</div> </div>
@ -328,6 +330,22 @@ export default {
} }
}, },
mounted() { mounted() {
//Scroll on volume
this.$refs.volumeBar.addEventListener('wheel', e => {
//Volup
if (e.deltaY < 0) {
if (this.$root.volume + 0.05 > 1)
this.$root.volume = 1;
else
this.$root.volume += 0.05;
} else {
//Voldown
if (this.$root.volume - 0.05 < 0)
this.$root.volume = 0;
else
this.$root.volume -= 0.05;
}
});
}, },
computed: { computed: {
}, },

View File

@ -0,0 +1,86 @@
<template>
<div>
<h1>{{$t("Importer")}}</h1><br>
<span class='text-h6'>
<v-icon right color='warning' class='mr-2'>mdi-alert</v-icon>
{{$t("Currently only Spotify is supported and limited to 100 tracks.")}}
</span>
<br>
<!-- URL entry and buttons -->
<div class='d-flex mt-4' v-if='!$root.importer.done && !$root.importer.active && !$root.importer.error'>
<v-text-field
v-model=input
:label='$t("Enter URL")'
:rules='[valid]'
></v-text-field>
<v-btn class='mx-2 mt-4' color='primary' :disabled='!valid' @click='start("import")'>
<v-icon left>mdi-playlist-plus</v-icon>
{{$t("Import into playlist")}}
</v-btn>
<v-btn class='mx-2 mt-4' color='green' :disabled='!valid' @click='start("download")'>
<v-icon left>mdi-download</v-icon>
{{$t("Download")}}
</v-btn>
</div>
<!-- Tracks -->
<div class='mt-4' v-if='$root.importer.done || $root.importer.active'>
<h2 class='mb-2'>Tracks:</h2>
<v-list>
<v-list-item v-for='(track, i) in $root.importer.tracks' :key='i'>
<v-list-item-avatar>
<v-img :src='track.art'></v-img>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{track.title}}</v-list-item-title>
<v-list-item-subtitle>{{track.artist}}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-icon v-if='track.ok' color='green'>mdi-check</v-icon>
<v-icon v-if='!track.ok' color='red'></v-icon>
</v-list-item-action>
</v-list-item>
</v-list>
</div>
<!-- Error -->
<div v-if='$root.importer.error' class='text-center mt-4'>
<h2>{{$t("An error occured, URL might be invalid or unsupported.")}}</h2>
</div>
</div>
</template>
<script>
export default {
name: 'Importer',
data() {
return {
input: null,
}
},
methods: {
async start(type) {
await this.$axios.post('/import', {url: this.input, type});
}
},
computed: {
valid() {
let i = this.input || '';
return i.startsWith('https://open.spotify.com/playlist/') && !i.includes(' ');
}
},
mounted() {
},
destroyed() {
//If done
if (this.$root.importer.done || this.$root.importer.error) {
this.$root.importer.done = false;
this.$root.importer.active = false;
this.$root.importer.error = false;
this.$root.importer.tracks = [];
}
},
}
</script>

View File

@ -144,7 +144,6 @@
<v-list-item-title class='pl-2'>{{$t("Select primary color")}}</v-list-item-title> <v-list-item-title class='pl-2'>{{$t("Select primary color")}}</v-list-item-title>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
<!-- Autocomplete --> <!-- Autocomplete -->
<v-list-item> <v-list-item>
<v-list-item-action> <v-list-item-action>
@ -154,6 +153,17 @@
<v-list-item-title>{{$t("Show autocomplete in search")}}</v-list-item-title> <v-list-item-title>{{$t("Show autocomplete in search")}}</v-list-item-title>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
<!-- Keep sidebar open -->
<v-list-item>
<v-list-item-action>
<v-checkbox v-model='$root.settings.sidebarOpen' class='pl-2'></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{$t("Keep sidebar open")}}</v-list-item-title>
<v-list-item-subtitle>{{$t("WARNING: Might require reload to work properly!")}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- Accounts --> <!-- Accounts -->
<v-subheader>{{$t("Integrations")}}</v-subheader> <v-subheader>{{$t("Integrations")}}</v-subheader>

145
app/package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "freezer", "name": "freezer",
"version": "1.1.9", "version": "1.1.13",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -206,11 +206,11 @@
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
}, },
"axios": { "axios": {
"version": "0.19.2", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": { "requires": {
"follow-redirects": "1.5.10" "follow-redirects": "^1.10.0"
} }
}, },
"backo2": { "backo2": {
@ -298,6 +298,11 @@
} }
} }
}, },
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -379,6 +384,32 @@
} }
} }
}, },
"cheerio": {
"version": "1.0.0-rc.5",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.5.tgz",
"integrity": "sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==",
"requires": {
"cheerio-select-tmp": "^0.1.0",
"dom-serializer": "~1.2.0",
"domhandler": "^4.0.0",
"entities": "~2.1.0",
"htmlparser2": "^6.0.0",
"parse5": "^6.0.0",
"parse5-htmlparser2-tree-adapter": "^6.0.0"
}
},
"cheerio-select-tmp": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz",
"integrity": "sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==",
"requires": {
"css-select": "^3.1.2",
"css-what": "^4.0.0",
"domelementtype": "^2.1.0",
"domhandler": "^4.0.0",
"domutils": "^2.4.4"
}
},
"color": { "color": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
@ -502,6 +533,23 @@
"which": "^2.0.1" "which": "^2.0.1"
} }
}, },
"css-select": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz",
"integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==",
"requires": {
"boolbase": "^1.0.0",
"css-what": "^4.0.0",
"domhandler": "^4.0.0",
"domutils": "^2.4.3",
"nth-check": "^2.0.0"
}
},
"css-what": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz",
"integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A=="
},
"dashdash": { "dashdash": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@ -510,14 +558,6 @@
"assert-plus": "^1.0.0" "assert-plus": "^1.0.0"
} }
}, },
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"deep-is": { "deep-is": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@ -562,6 +602,39 @@
"esutils": "^2.0.2" "esutils": "^2.0.2"
} }
}, },
"dom-serializer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
"integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0",
"entities": "^2.0.0"
}
},
"domelementtype": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
"integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
},
"domhandler": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
"integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
"requires": {
"domelementtype": "^2.1.0"
}
},
"domutils": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
"integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==",
"requires": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0"
}
},
"ecc-jsbn": { "ecc-jsbn": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -692,6 +765,11 @@
"ansi-colors": "^4.1.1" "ansi-colors": "^4.1.1"
} }
}, },
"entities": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
},
"escape-html": { "escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -1002,12 +1080,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.5.10", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
"requires": {
"debug": "=3.1.0"
}
}, },
"forever-agent": { "forever-agent": {
"version": "0.6.1", "version": "0.6.1",
@ -1119,6 +1194,17 @@
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true "dev": true
}, },
"htmlparser2": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.0.tgz",
"integrity": "sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0",
"domutils": "^2.4.4",
"entities": "^2.0.0"
}
},
"http-errors": { "http-errors": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
@ -1511,6 +1597,14 @@
"node-addon-api": "^2.0.0" "node-addon-api": "^2.0.0"
} }
}, },
"nth-check": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
"integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
"requires": {
"boolbase": "^1.0.0"
}
},
"oauth-sign": { "oauth-sign": {
"version": "0.9.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
@ -1569,6 +1663,19 @@
"callsites": "^3.0.0" "callsites": "^3.0.0"
} }
}, },
"parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"parse5-htmlparser2-tree-adapter": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
"integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
"requires": {
"parse5": "^6.0.1"
}
},
"parseqs": { "parseqs": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",

View File

@ -1,7 +1,7 @@
{ {
"name": "freezer", "name": "freezer",
"private": true, "private": true,
"version": "1.1.13", "version": "1.1.14",
"description": "", "description": "",
"main": "background.js", "main": "background.js",
"scripts": { "scripts": {
@ -11,9 +11,10 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"arg": "^4.1.3", "arg": "^4.1.3",
"axios": "^0.19.2", "axios": "^0.21.1",
"browser-id3-writer": "^4.4.0", "browser-id3-writer": "^4.4.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"cheerio": "^1.0.0-rc.5",
"compare-versions": "^3.6.0", "compare-versions": "^3.6.0",
"discord-rpc": "^3.1.4", "discord-rpc": "^3.1.4",
"express": "^4.17.1", "express": "^4.17.1",

View File

@ -10,7 +10,6 @@ const sanitize = require('sanitize-filename');
const ID3Writer = require('browser-id3-writer'); const ID3Writer = require('browser-id3-writer');
const Metaflac = require('metaflac-js2'); const Metaflac = require('metaflac-js2');
const { Track, Lyrics, DeezerImage } = require('./definitions'); const { Track, Lyrics, DeezerImage } = require('./definitions');
const { throwDeprecation } = require('process');
let deezer; let deezer;
@ -377,7 +376,7 @@ class DownloadThread {
async tagTrack(path) { async tagTrack(path) {
let cover; let cover;
try { try {
cover = await this.downloadCover(this.track.albumArt.hash, 'cover', this.settings.coverResolution); cover = await this.downloadCover(DeezerImage.url(this.track.albumArt.hash, 'cover', this.settings.coverResolution), 'cover', this.settings.coverResolution);
} catch (e) {} } catch (e) {}
//Genre tag //Genre tag

94
app/src/importer.js Normal file
View File

@ -0,0 +1,94 @@
const {EventEmitter} = require('events');
const axios = require('axios');
const cheerio = require('cheerio');
const logger = require('./winston');
class Importer extends EventEmitter {
constructor(deezer) {
super();
this.deezer = deezer;
this.tracks = [];
}
//Conver spotify URL to URI
async getSpotifyURI(inputUrl) {
let url = new URL(inputUrl);
return 'spotify' + url.pathname.replace(new RegExp('/', 'g'), ':');
}
//Import spotify playlist
async importSpotifyPlaylist(url) {
//Clean
this.tracks = [];
try {
//Fetch
let uri = await this.getSpotifyURI(url);
let spotifyData = await Spotify.getEmbedData(uri);
if (!spotifyData.tracks.items) throw Error("No items!");
for (let track of spotifyData.tracks.items) {
//Output track
let out = new ImporterTrack(
track.track.name,
track.track.artists.map(a => a.name).join(', '),
(track.track.album.images.length > 0) ? track.track.album.images[0].url : null
);
//Match
try {
let deezerData = await this.deezer.callPublicApi('track', 'isrc:' + track.track.external_ids.isrc);
if (deezerData.id.toString()) {
//Found track
out.id = deezerData.id.toString();
out.ok = true;
}
} catch (e) {
logger.error(`Error importing: Spotify: ${track.track.id} ${e}`);
}
//Send back
this.emit('imported', out);
this.tracks.push(out);
}
//Emit done with playlist details
this.emit('done', {
title: spotifyData.name,
description: spotifyData.description,
tracks: this.tracks
});
} catch (e) {
//Emit error on error
logger.error(`Error importing: ${e}`);
this.emit('error', `${e}`);
}
}
}
//Track only with most important metadata for UI
class ImporterTrack {
constructor(title, artist, art) {
this.id = null;
this.title = title;
this.artist = artist;
this.art = art;
this.ok = false;
}
}
class Spotify {
constructor() {}
//Fetch JSON data from embeded spotify
static async getEmbedData(uri) {
//Fetch
let url = `https://embed.spotify.com/?uri=${uri}`;
let res = await axios.get(url);
const $ = cheerio.load(res.data);
//Get JSON
let data = JSON.parse(decodeURIComponent($('#resource').html()));
return data;
}
}
module.exports = {Importer};

View File

@ -6,7 +6,6 @@ const logger = require('./winston');
class Integrations extends EventEmitter { class Integrations extends EventEmitter {
//LastFM, Discord etc //LastFM, Discord etc
constructor(settings) { constructor(settings) {
super(); super();
@ -67,7 +66,7 @@ class Integrations extends EventEmitter {
//Connect to discord client //Connect to discord client
connectDiscord() { connectDiscord() {
//Don't steal, k ty //Don't steal, k ty
const CLIENTID = '759835951450292324'; const CLIENTID = '803292927227854878';
this.discordReady = false; this.discordReady = false;
DiscordRPC.register(CLIENTID); DiscordRPC.register(CLIENTID);

View File

@ -10,6 +10,7 @@ const {Settings} = require('./settings');
const {Track, Album, Artist, Playlist, DeezerProfile, SearchResults, DeezerLibrary, DeezerPage, Lyrics} = require('./definitions'); const {Track, Album, Artist, Playlist, DeezerProfile, SearchResults, DeezerLibrary, DeezerPage, Lyrics} = require('./definitions');
const {DownloadManager} = require('./downloads'); const {DownloadManager} = require('./downloads');
const {Integrations} = require('./integrations'); const {Integrations} = require('./integrations');
const {Importer} = require('./importer');
let settings; let settings;
let deezer; let deezer;
@ -464,6 +465,69 @@ app.post('/log', async (req, res) => {
res.status(200).end(); res.status(200).end();
}); });
//Importer
app.post('/import', async (req, res) => {
//Importer status
sockets.forEach(s => s.emit('importerInit', {
done: false,
active: true,
error: false,
tracks: []
}));
let type = req.body.type;
//Create importer
let importer = new Importer(deezer);
//Error
importer.on('error', t => {
sockets.forEach(s => s.emit('importerError'));
})
//New track imported
importer.on('imported', t => {
sockets.forEach(s => s.emit('importerTrack', t));
});
//Finished
importer.on('done', async (i) => {
//Create playlist
let playlistRaw = await deezer.callApi('playlist.create', {
description: i.description,
title: i.title,
status: 1,
songs: i.tracks.map(t => [parseInt(t.id, 10)])
});
//Download
if (type == 'download') {
//Fetch playlist
let data = await deezer.callApi('deezer.pagePlaylist', {
playlist_id: playlistRaw.results.toString(),
lang: settings.contentLanguage,
nb: 10000,
start: 0,
tags: true
});
let playlist = new Playlist(data.results.DATA, data.results.SONGS);
//Enqueue
await downloadManager.addBatch({
tracks: playlist.tracks,
quality: settings.downloadsQuality,
playlistName: i.title
});
//Delete
await deezer.callApi('playlist.delete', {playlist_id: parseInt(playlist.id.toString(),10)});
downloadManager.start();
}
//Send to UI
sockets.forEach(s => {
s.emit('importerDone');
});
})
importer.importSpotifyPlaylist(req.body.url);
res.status(200).end();
});
//Last.FM authorization callback //Last.FM authorization callback
app.get('/lastfm', async (req, res) => { app.get('/lastfm', async (req, res) => {
//Got token //Got token

View File

@ -43,6 +43,7 @@ class Settings {
this.forceWhiteTrayIcon = false; this.forceWhiteTrayIcon = false;
this.contentLanguage = 'en'; this.contentLanguage = 'en';
this.contentCountry = 'US'; this.contentCountry = 'US';
this.sidebarOpen = false;
} }
//Based on electorn app.getPath //Based on electorn app.getPath

View File

@ -1,7 +1,7 @@
{ {
"name": "freezer", "name": "freezer",
"private": true, "private": true,
"version": "1.1.13", "version": "1.1.14",
"description": "", "description": "",
"scripts": { "scripts": {
"pack": "electron-builder --dir", "pack": "electron-builder --dir",