Merge pull request 'Update branch' (#1) from Hyperpipe/Hyperpipe:main into main

Reviewed-on: https://codeberg.org/HexagonCDN/Hyperpipe/pulls/1
This commit is contained in:
HexagonCDN 2023-03-07 10:33:33 +00:00
commit 098af5026d
49 changed files with 8355 additions and 813 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
log.txt
node_modules
.DS_Store

View file

@ -5,8 +5,6 @@ pipeline:
- npm install
- npm run build
when:
path:
exclude: ['*.md', '.gitea/*']
event: ['push', 'pull_request']
surge:
@ -18,8 +16,6 @@ pipeline:
secrets: [surge_login, surge_token]
when:
branch: main
path:
exclude: ['*.md', '.gitea/*']
event: ['push']
docker:
@ -29,11 +25,10 @@ pipeline:
repo: codeberg.org/hyperpipe/hyperpipe
registry: codeberg.org
tags: latest
username: snematoda
username:
from_secret: cb_user
password:
from_secret: cb_token
when:
branch: main
path:
exclude: ['*.md', '.gitea/*']
event: ['push']

View file

@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM node:lts AS build
FROM --platform=$BUILDPLATFORM node:lts-alpine AS build
WORKDIR /app/
@ -9,7 +9,7 @@ RUN --mount=type=cache,target=/root/.cache/node \
npm install --prefer-offline && \
npm run build
FROM --platform=$BUILDPLATFORM nginx:stable
FROM --platform=$BUILDPLATFORM nginx:stable-alpine
COPY --from=build /app/dist/ /usr/share/nginx/html/
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf

View file

@ -1,5 +1,7 @@
# Hyperpipe
<div align="center">
![](https://codeberg.org/Hyperpipe/Hyperpipe/raw/branch/main/public/android-chrome-192x192.png)
A Privacy Respecting Frontend for YouTube Music inspired and built with the help of [Piped][piped] and YouTube's InnerTube API.
@ -7,6 +9,8 @@ A Privacy Respecting Frontend for YouTube Music inspired and built with the help
![](https://img.shields.io/badge/youtube-music-red?style=for-the-badge&logo=youtube-music)
![Offical instance](https://img.shields.io/website?down_color=red&down_message=offline&label=status&style=for-the-badge&up_color=cornflowerblue&up_message=online&url=https%3A%2F%2Fhyperpipe.surge.sh)
</div>
## Disclaimer
**_HYPERPIPE IS VERY EXPIRIMENTAL, EXPECT_**
@ -103,6 +107,7 @@ You can reach out to me personally on:
- ViteJS -> [MIT][vite]
- shaka-player -> [Apache-2.0][shaka]
- PeerJS -> [MIT][peer]
- DOMPurify -> [Apache-2.0][purify]
- Bootstrap Icons -> [MIT][bi]
- VueJS theme -> [MIT][vuetheme]
- Dracula theme -> [MIT][dracula]
@ -119,16 +124,17 @@ You can reach out to me personally on:
---
*Hyperpipe does not host any content. All content on Hyperpipe is from YouTube Music. YouTube and YouTube Music are trademarks of Google LLC. Hyperpipe is not affiliated with YouTube or YouTube Music.*
*All content on Hyperpipe is from YouTube Music. YouTube and YouTube Music are trademarks of Google LLC. Hyperpipe is not affiliated with YouTube or YouTube Music.*
[hypipe]: https://hyperpipe.surge.sh
[piped]: https://piped.kavin.rocks
[piped]: https://github.com/TeamPiped/Piped
[license]: https://codeberg.org/Hyperpipe/Hyperpipe/src/branch/main/LICENSE.md
[vue]: https://github.com/vuejs/core/blob/main/LICENSE
[vite]: https://github.com/vitejs/vite/blob/main/LICENSE
[bi]: https://github.com/twbs/icons/blob/main/LICENSE.md
[peer]: https://github.com/peers/peerjs/blob/master/LICENSE
[shaka]: https://github.com/shaka-project/shaka-player/blob/main/LICENSE
[purify]: https://github.com/cure53/DOMPurify/blob/main/LICENSE
[nord]: https://github.com/arcticicestudio/nord/blob/develop/LICENSE.md
[vuetheme]: https://github.com/vuejs/theme/blob/main/LICENSE
[dracula]: https://github.com/dracula/dracula-theme/blob/master/LICENSE

View file

@ -14,7 +14,6 @@
<link rel="dns-prefetch" href="https://hyperpipe-proxy.onrender.com" />
<link rel="dns-prefetch" href="https://piped-instances.kavin.rocks" />
<link rel="manifest" href="/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="no-referrer" />

8152
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,20 +10,18 @@
"check": "prettier --check ."
},
"dependencies": {
"bootstrap-icons": "^1.10.2",
"buffer": "^6.0.3",
"dompurify": "^2.4.1",
"mux.js": "^6.2.0",
"bootstrap-icons": "^1.10.3",
"dompurify": "^2.4.4",
"mux.js": "^6.3.0",
"peerjs": "^1.4.7",
"pinia": "^2.0.28",
"shaka-player": "^4.3.1",
"stream-browserify": "^3.0.0",
"vue": "^3.2.38",
"xml-js": "^1.6.11"
"pinia": "^2.0.32",
"shaka-player": "^4.3.4",
"vue": "^3.2.38"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"prettier": "^2.8.1",
"vite": "^4.0.0"
"prettier": "^2.8.4",
"vite": "^4.1.4",
"vite-plugin-pwa": "^0.14.4"
}
}

View file

@ -1,26 +0,0 @@
{
"name": "Hyperpipe",
"short_name": "Hyperpipe",
"start_url": "/",
"display": "standalone",
"background_color": "#000",
"description": "Privacy respecting YouTube Music Frontend.",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/android-maskable.png",
"sizes": "1024x1024",
"type": "image/png",
"purpose": "maskable"
}
]
}

View file

@ -109,9 +109,8 @@ function playList(a) {
/* Lifestyle hooks */
onBeforeMount(() => {
/* Set the default theme if set */
if (store.theme) {
document.body.setAttribute('data-theme', store.theme);
}
if (store.theme) document.body.setAttribute('data-theme', store.theme);
if (store.compact == 'true') document.body.setAttribute('data-compact', '');
/* Set the default locale if set */
if (store.locale) setupLocale(store.locale);

View file

@ -94,10 +94,12 @@ body[data-theme*='blur'] .pl-modal,
body[data-theme*='blur'] .modal-box,
body[data-theme*='blur'] .popup input[type='text'] {
background: var(--color-blur);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
body[data-theme*='blur'] .modal {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(5px);
}
@ -327,15 +329,26 @@ img {
display: grid;
grid-template-columns: 1fr;
}
@media (max-width: 530px) {
[data-compact] .grid-3 {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 530px) {
.grid-3 {
grid-template-columns: 1fr 1fr;
}
[data-compact] .grid-3 {
grid-template-columns: 1fr 1fr 1fr;
}
}
@media (min-width: 780px) {
.grid-3 {
grid-template-columns: 1fr 1fr 1fr;
}
[data-compact] .grid-3 {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
}
@media (min-width: 930px) {
.grid {
@ -347,6 +360,9 @@ img {
.grid-3 {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
[data-compact] .grid-3 {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
}
/* Animations */

View file

@ -1,3 +1,3 @@
{
"date": "2022-12-31"
"date": "2023-03-05"
}

View file

@ -45,5 +45,18 @@ defineEmits(['open-album']);
}
.card-text {
margin-top: 0.5rem;
cursor: default;
}
[data-compact] .card {
min-height: 12rem;
width: 10rem;
}
[data-compact] .card-bg {
width: 7rem;
height: 7rem;
}
[data-compact] .card-text {
margin-top: 0.25rem;
}
</style>

View file

@ -14,19 +14,17 @@ const subs = JSON.parse(store.subs ? store.subs : '[]'),
isSub = ref(subs.includes(hash));
function Sub() {
if (artist.state.title) {
if (isSub.value) {
subs.splice(subs.indexOf(hash), 1);
store.setItem('subs', JSON.stringify(subs));
isSub.value = false;
} else {
subs.push(hash);
store.setItem('subs', JSON.stringify(subs));
isSub.value = true;
}
if (!artist.state.title) return;
alert(JSON.stringify(subs));
if (isSub.value) {
subs.splice(subs.indexOf(hash), 1);
isSub.value = false;
} else {
subs.push(hash);
isSub.value = true;
}
store.setItem('subs', JSON.stringify(subs));
}
</script>

View file

@ -30,9 +30,7 @@ async function getCharts() {
console.log(json);
if (!id.value)
id.value = json.options.all.filter(
i => i.title == json.options.default,
)[0].id;
id.value = json.options.all.find(i => i.title == json.options.default).id;
data.options = json.options.all;
data.songs = json.trending;

View file

@ -116,7 +116,9 @@ onMounted(get);
}
.btn-grid {
display: grid;
grid-template-columns: calc(100% / 3) calc(100% / 3) calc(100% / 3);
align-self: center;
max-width: 100%;
grid-template-columns: repeat(auto-fill, 8rem);
grid-auto-rows: 1fr;
gap: 0.125rem;
padding: 1rem 0;
@ -143,20 +145,4 @@ onMounted(get);
text-align: center;
text-transform: capitalize;
}
@media (min-width: 760px) {
.btn-grid {
grid-template-columns: calc(100% / 4) calc(100% / 4) calc(100% / 4) calc(
100% / 4
);
}
}
@media (min-width: 1024px) {
.btn-grid {
grid-template-columns:
calc(100% / 5) calc(100% / 5) calc(100% / 5) calc(100% / 5)
calc(100% / 5);
}
}
</style>

View file

@ -36,8 +36,11 @@ const list = ref([]),
show = reactive({
new: false,
sync: false,
import: false,
}),
text = ref(''),
isImport = ref(false),
importFile = ref(''),
sync = reactive({
type: 'send',
id: 'Please Wait...',
@ -53,16 +56,27 @@ const list = ref([]),
const pathname = url => new URL(url).pathname;
const Open = key => {
const Open = async key => {
console.log(key);
const { imageProxyUrl } = await getJsonPiped('/config');
useGetPlaylist(key, res => {
console.log(res);
if (res.urls.length > 0) {
results.resetItems();
results.setItem('songs', {
title: 'Local • ' + key,
items: res.urls.map(i => ({ ...i, ...{ playlistId: key } })),
items: res.urls.map(i => ({
...i,
...{
playlistId: key,
thumbnail: `${imageProxyUrl}/vi_webp/${i.url.replace(
'/watch?v=',
'',
)}/maxresdefault.webp?host=i.ytimg.com`,
},
})),
});
nav.state.page = 'home';
@ -82,6 +96,80 @@ const Open = key => {
});
}
},
Import = async (data = importFile.value) => {
if (data?.type == 'application/json')
data = await data.text().then(json => {
json = JSON.parse(json);
if (json?.subscriptions?.length > 0) {
const subs = JSON.parse(store.subs || '[]');
for (const sub of json.subscriptions) {
const id = sub.url.slice(-24);
if (subs.indexOf(id) < 0) subs.push(id);
}
store.subs = JSON.stringify(subs);
}
return json.local;
});
if (!data) {
alert('No data to import');
return;
}
List();
for (let i of data) {
const pl = list.value.find(p => p.name == i.name);
if (pl) {
for (let u of i.urls) {
if (pl.urls.findIndex(r => r.url === u.url) < 0) {
useUpdatePlaylist(i.name, u, () => {
console.log('Added: ' + u.name);
});
}
}
} else {
useCreatePlaylist(i.name, i.urls);
}
List();
}
show.import = false;
},
Export = () => {
List();
const base = JSON.stringify(
{
format: 'Hyperpipe',
version: 0,
app_version: 0,
subscriptions: JSON.parse(store.subs).map(id => ({
url: 'https://www.youtube.com/channel/' + id,
service_id: 0,
})),
local: list.value,
playlists: [], // TODO?
},
null,
2,
),
file = new Blob([base], { type: 'application/json' }),
ele = document.createElement('a');
ele.href = URL.createObjectURL(file);
ele.download = 'hyperpipe.json';
ele.click();
ele.remove();
},
Send = () => {
const conn = sync.peer.connect(sync.to);
@ -146,12 +234,13 @@ const Login = async () => {
};
const getFeeds = async () => {
const subs = JSON.parse(store.subs ? store.subs : '[]');
const subs = store.subs;
if (subs.length > 0) {
const json = await getJsonPiped(
'/feed/unauthenticated?channels=' + subs.join(','),
);
if (subs) {
const json = await getJsonPiped('/feed/unauthenticated', {
method: 'POST',
body: subs,
});
results.resetItems();
results.setItem('songs', {
@ -183,29 +272,9 @@ watch(
if (sync.type == 'rec') {
console.log(data);
List();
Import(data);
for (let i of data) {
const pl = list.value.filter(p => p.name == i.name)[0];
if (pl) {
for (let u of i.urls) {
if (!pl.urls.filter(r => r.url === u.url)[0]) {
useUpdatePlaylist(i.name, u, () => {
console.log('Added: ' + u.name);
});
}
}
} else {
useCreatePlaylist(i.name, i.urls);
}
List();
if (data.indexOf(i) == data.length - 1) {
show.sync = false;
}
}
show.sync = false;
}
});
});
@ -300,6 +369,43 @@ onMounted(async () => {
</template>
</Modal>
<Modal
n="2"
:display="show.import"
:title="t('action.import')"
@show="
e => {
show.import = e;
}
">
<template #content>
<div class="tabs">
<button :data-active="isImport" @click="isImport = true">
{{ t('action.import') }}
</button>
<button :data-active="!isImport" @click="isImport = false">
{{ t('action.export') }}
</button>
</div>
<div v-if="isImport">
<input
type="file"
class="textbox"
name="import"
accept="application/json"
@change="importFile = $event.target.files[0]" />
</div>
</template>
<template #buttons>
<button @click="show.import = false">{{ t('action.cancel') }}</button>
<button @click="isImport ? Import() : Export()">
{{ isImport ? t('action.import') : t('action.export') }}
</button>
</template>
</Modal>
<div class="grid">
<div class="npl-box bi bi-plus-lg pop" @click="show.new = true"></div>
@ -308,6 +414,10 @@ onMounted(async () => {
@click="show.sync = true"></div>
<div class="npl-box bi bi-tag pop" @click="getFeeds"></div>
<div
class="npl-box bi bi-box-arrow-in-down pop"
@click="show.import = true"></div>
</div>
<h2 v-if="list.length > 0">{{ t('playlist.local') }}</h2>
@ -329,6 +439,7 @@ onMounted(async () => {
v-for="i in user.playlists"
:key="i.id"
:name="i.name.replace('Playlist - ', '')"
:author="t('title.songs') + ' • ' + i.videos"
:art="pathname(i.thumbnail) != '/' ? i.thumbnail : undefined"
@open-album="$emit('open-playlist', '/playlists?list=' + i.id)" />
</div>
@ -419,6 +530,20 @@ button.logout {
display: block;
background: linear-gradient(135deg, indianred, #bf616a);
}
input[type='file'] {
display: block;
}
input[type='file']::file-selector-button {
font-weight: bold;
appearance: inherit;
outline: inherit;
border: inherit;
border-radius: 0.25rem;
padding: 0.5rem;
margin: 0.1rem 0.5rem 0.1rem 0.1rem;
color: var(--color-background);
background: linear-gradient(135deg, cornflowerblue, #88c0d0);
}
.tabs button:first-child {
border-radius: 0.25rem 0 0 0.25rem;
}

View file

@ -118,6 +118,11 @@ async function Stream() {
}
}
function destroy() {
window.audioPlayer.destroy();
window.audioPlayer = undefined;
}
watch(
() => player.state.play,
() => {
@ -150,8 +155,9 @@ onMounted(() => {
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('play', () => {
player.state.status = 'pause';
audio.value.play().catch(err => {
alert(err);
console.log(err);
player.state.status = 'play';
});
});
@ -163,7 +169,7 @@ onMounted(() => {
navigator.mediaSession.setActionHandler('previoustrack', () => {
if (data.state.urls.length > 2) {
const i = data.state.urls.map(s => s.url).indexOf(data.state.url);
const i = data.state.urls.findIndex(s => s.url == data.state.url);
data.getSong(data.state.urls[i - 1].url);
}
@ -171,7 +177,7 @@ onMounted(() => {
navigator.mediaSession.setActionHandler('nexttrack', () => {
if (data.state.urls.length > 2) {
const i = data.state.urls.map(s => s.url).indexOf(data.state.url);
const i = data.state.urls.findIndex(s => s.url == data.state.url);
data.getSong(data.state.urls[i + 1].url);
}
@ -188,16 +194,10 @@ onMounted(() => {
});
onBeforeUnmount(() => {
if (window.audioPlayer) {
window.audioPlayer.destroy();
window.audioPlayer = undefined;
}
if (window.audioPlayer) destroy();
});
onUnmounted(() => {
if (window.audioPlayer) {
window.audioPlayer.destroy();
window.audioPlayer = undefined;
}
if (window.audioPlayer) destroy();
});
</script>

View file

@ -19,7 +19,8 @@ import('@/assets/version.json').then(v => {
const { t, setupLocale } = useI18n(),
instances = ref([]),
hypInstances = ref([]),
next = ref(false);
next = ref(false),
compact = ref(false);
getJson('https://piped-instances.kavin.rocks').then(i => {
instances.value = i;
@ -60,8 +61,14 @@ function setLang(locale) {
setStore('locale', locale);
}
function setCodec(codec) {
setStore('codec', codec);
if (window.audioPlayer)
window.audioPlayer.configure('preferredAudioCodecs', codec.split(':'));
}
function getStoreBool(key, ele) {
ele.value = getStore(key) || true;
ele.value = getStore(key) || ele.value;
}
const verifyApi = computed(() =>
@ -82,6 +89,7 @@ const verifyApi = computed(() =>
onMounted(() => {
getStoreBool('next', next);
getStoreBool('compact', compact);
});
</script>
@ -100,6 +108,17 @@ onMounted(() => {
<option value="nord">{{ t('pref.nord') }}</option>
</select>
<div class="left">
<input
type="checkbox"
name="pref-chk-compact"
id="pref-chk-compact"
class="input"
@change="setStore('compact', $event.target.checked)"
v-model="compact" />
<label for="pref-chk-compact">{{ t('pref.compact') }}</label>
</div>
<h2>Language</h2>
<select
@ -119,10 +138,10 @@ onMounted(() => {
class="input"
:value="getStore('page') || 'home'"
@change="setStore('page', $event.target.value)">
<option value="home">Home</option>
<option value="explore">Explore</option>
<option value="charts">Charts</option>
<option value="library">Library</option>
<option value="home">{{ t('pref.home') }}</option>
<option value="explore">{{ t('pref.explore') }}</option>
<option value="charts">{{ t('pref.charts') }}</option>
<option value="library">{{ t('pref.library') }}</option>
</select>
<h2>{{ t('pref.player') }}</h2>
@ -145,7 +164,7 @@ onMounted(() => {
name="pref-codec"
class="input"
:value="getStore('codec') || 'opus:mp4a'"
@change="setStore('codec', $event.target.value)">
@change="setCodec($event.target.value)">
<option value="opus:mp4a">opus, mp4a</option>
<option value="mp4a:opus">mp4a, opus</option>
<option value="opus">opus</option>

View file

@ -6,7 +6,7 @@ import SongItem from './SongItem.vue';
import AlbumItem from './AlbumItem.vue';
import { getJsonPiped, getPipedQuery } from '@/scripts/fetch.js';
import { useRoute, useWrap } from '@/scripts/util.js';
import { useRoute, useWrap, useShare } from '@/scripts/util.js';
import { useCreatePlaylist, useRemovePlaylist } from '@/scripts/db.js';
import { useResults, useArtist } from '@/stores/results.js';
@ -77,9 +77,18 @@ const shuffleAdd = () => {
removeSong = i => {
console.log(i);
try {
results.items.songs.items.splice(i, 1);
} catch {}
results.items.songs.items.splice(i, 1);
},
shareAlbum = () => {
const data = {
title: `View ${results.items?.songs?.title} on Hyperpipe`,
url:
location.origin +
(results.album.startsWith('/') ? '' : '/') +
results.album,
};
useShare(data);
},
saveAlbum = () => {
const urls = results.items?.songs?.items?.map(item => ({
@ -149,6 +158,7 @@ const shuffleAdd = () => {
items = json.items;
} else {
console.log(results.next);
const json = await getJsonPiped(`/nextpage/playlists/${results.next}`);
key = 'songs';
@ -233,6 +243,8 @@ onDeactivated(() => {
class="bi bi-bookmark-plus clickable"
@click="saveAlbum"></button>
<button class="bi bi-share clickable" @click="shareAlbum"></button>
<button
class="bi bi-plus-lg clickable"
@click="

View file

@ -3,7 +3,7 @@ import { ref, onMounted } from 'vue';
import { getJsonAuth } from '@/scripts/fetch.js';
import { useRand } from '@/scripts/colors.js';
import { useStore } from '@/scripts/util.js';
import { useStore, useShare } from '@/scripts/util.js';
import { useUpdatePlaylist } from '@/scripts/db.js';
import { useResults, useArtist } from '@/stores/results.js';
@ -40,9 +40,7 @@ const openSong = el => {
thumbnails: [{ url: props.art }],
});
const index = data.state.urls.map(s => s.url).indexOf(data.state.url);
console.log(data.state.urls);
const index = data.state.urls.findIndex(s => s.url == data.state.url);
if (
(index == data.state.urls.length - 1 && player.state.time > 98) ||
@ -50,6 +48,15 @@ const openSong = el => {
)
emit('open-song', props.play);
},
appendSong = () => {
const index = data.state.urls.findIndex(s => s.url == data.state.url);
data.state.urls.splice(index + 1, 0, {
url: props.play,
title: props.title,
thumbnails: [{ url: props.art }],
});
},
Remove = () => {
const auth = useStore().getItem('auth'),
isRemote = results.items?.songs?.title?.startsWith('Playlist - ');
@ -78,29 +85,13 @@ const openSong = el => {
emit('remove', props.index),
);
},
Share = async () => {
if ('share' in navigator) {
const data = {
title: `Listen to ${props.title} by ${props.author} on Hyperpipe`,
url: location.origin + props.play,
};
Share = () => {
const data = {
title: `Listen to ${props.title} by ${props.author} on Hyperpipe`,
url: location.origin + props.play,
};
try {
await navigator.share(data);
console.log('Done Sharing!');
} catch (err) {
console.log(err);
}
} else {
navigator.clipboard.writeText(location.host + props.play).then(
() => {
alert('Copied to Clipboard');
},
err => {
console.log(err);
},
);
}
useShare(data);
};
onMounted(() => {
@ -131,7 +122,12 @@ onMounted(() => {
v-if="playlistId"
class="bi bi-dash-lg clickable ign"
@click="Remove"></span>
<span class="bi bi-broadcast ign" @click="$emit('nxt-song')"></span>
<span
class="bi bi-chevron-bar-right clickable ign"
@click="appendSong"></span>
<span
class="bi bi-broadcast clickable ign"
@click="$emit('nxt-song')"></span>
<span class="bi bi-plus-lg clickable ign" @click="addSong"></span>
<span class="bi bi-share clickable ign" @click="Share"></span>
</div>
@ -173,4 +169,12 @@ span.bi-three-dots-vertical {
.bi-dash-lg {
color: indianred;
}
[data-compact] .card {
margin: 0;
}
[data-compact] .song-bg {
width: 70px;
height: 70px;
}
</style>

View file

@ -85,7 +85,7 @@ async function Like() {
remote.value = await getAuthPlaylists();
let fav = remote.value.filter(i => i.name == 'Playlist - Favorites')[0];
let fav = remote.value.find(i => i.name == 'Playlist - Favorites');
if (!fav) {
const { playlistId } = await useAuthCreatePlaylist('Favorites');
@ -134,7 +134,8 @@ async function Like() {
plRemote = true;
"
:data-active="pl == i.id && plRemote == true">
<span>{{ i.name }}</span>
<span>{{ i.name }}</span
><span class="ml-auto">{{ i.videos }}</span>
</div>
</template>
<template #buttons>
@ -424,11 +425,6 @@ input[type='range']::-webkit-slider-thumb {
background-color: var(--color-foreground);
-webkit-appearance: none;
appearance: none;
opacity: 0;
transition: opacity 0.4s ease;
}
input[type='range']:hover::-webkit-slider-thumb,
#vol input[type='range']::-webkit-slider-thumb {
opacity: 1;
height: 1rem;
width: 1rem;
@ -447,13 +443,8 @@ input[type='range']::-webkit-slider-runnable-track {
input[type='range']::-moz-range-thumb {
-moz-appearance: none;
appearance: none;
opacity: 0;
border: none;
outline: none;
transition: opacity 0.4s ease;
}
input[type='range']:hover::-moz-range-thumb,
#vol input[type='range']::-moz-range-thumb {
background-color: var(--color-foreground);
opacity: 1;
height: 1rem;

View file

@ -23,7 +23,9 @@
"create": "Yarat",
"receive": "Qəbul et",
"cancel": "Ləğv et",
"send": "Göndər"
"send": "Göndər",
"import": "İdxal Et",
"export": "İxrac Et"
},
"playlist": {
"local": "Yerli Pleylistlər",
@ -50,7 +52,12 @@
"light": "İşıqlı",
"blur_light": "Bulanıq (İşıqlı)",
"dracula": "Drakula",
"nord": "Nord"
"nord": "Nord",
"compact": "Kompakt Görünüş",
"charts": "Qrafiklər",
"library": "Pleylistlər",
"home": "Ana Menu",
"explore": "Kəşf Et"
},
"info": {
"see_all": "Hamısını gör",

18
src/locales/bn.json Normal file
View file

@ -0,0 +1,18 @@
{
"title": {
"artists": "শিল্পীরা",
"albums": "অ্যালবামগুলো",
"singles": "সিঙ্গেলস",
"similar_artists": "একইধরণের শিল্পীরা",
"playlists": "প্লেলিস্টস",
"moods": "মেজাজ",
"genres": "জনরাগুলো",
"songs": "গানগুলো",
"local": "স্থানীয়",
"search": "খুঁজুন",
"login": "লগিন"
},
"pref": {
"blur": "ঘোলা"
}
}

View file

@ -5,7 +5,9 @@
"send": "Pošalji",
"receive": "Primi",
"add": "Dodaj",
"create": "Napravi"
"create": "Napravi",
"import": "Uvezite",
"export": "Izvezite"
},
"title": {
"genres": "Žanrovi",
@ -60,7 +62,12 @@
"nord": "Nord",
"blur": "Zamagljeno",
"blur_light": "Zamućenje (Svjetlo)",
"light": "Svjetlo"
"light": "Svjetlo",
"home": "Početna",
"explore": "Istražite",
"charts": "Grafikoni",
"library": "Biblioteka",
"compact": "Kompaktan prikaz"
},
"info": {
"search": "Započni pretraživanje",

View file

@ -33,7 +33,12 @@
"blur": "Rozmazání",
"blur_light": "Rozmazaný (světlý)",
"dracula": "Drákula",
"nord": "Nord"
"nord": "Nord",
"explore": "Procházet",
"charts": "Žebříčky",
"home": "Domů",
"library": "Knihovna",
"compact": "Kompaktní zobrazení"
},
"instances": {
"name": "Název",
@ -55,7 +60,9 @@
"add": "Přidat",
"cancel": "Zrušit",
"send": "Poslat",
"receive": "Obdržet"
"receive": "Obdržet",
"import": "Importovat",
"export": "Exportovat"
},
"playlist": {
"local": "Místní playlisty",

View file

@ -3,13 +3,13 @@
"moods": "Stimmungen",
"songs": "Lieder",
"albums": "Alben",
"genres": "Genres",
"singles": "Singles",
"genres": "Kategorien",
"singles": "Einzeltitel",
"artists": "Künstler/innen",
"similar_artists": "Ähnliche Künstler/innen",
"featured": "Ausgewählt",
"community": "Gemeinschaft",
"login": "Anmeldung",
"login": "Anmelden",
"local": "Lokal",
"search": "Suche",
"spotlight": "Scheinwerfer",
@ -23,7 +23,9 @@
"create": "Erstellen",
"add": "Hinzufügen",
"cancel": "Abbrechen",
"receive": "Empfangen"
"receive": "Empfangen",
"import": "Importieren",
"export": "Exportieren"
},
"playlist": {
"local": "Lokale Wiedergabelisten",
@ -49,8 +51,13 @@
"light": "Hell",
"blur": "Unschärfe",
"blur_light": "Unschärfe (hell)",
"dracula": "Dracula",
"nord": "Nord"
"dracula": "Drakula",
"nord": "Nord",
"library": "Bibliothek",
"charts": "Diagramme",
"explore": "Erkunden",
"home": "Startseite",
"compact": "Kompakte Ansicht"
},
"info": {
"no_info": "Keine Informationen verfügbar",

View file

@ -23,7 +23,9 @@
"create": "Create",
"cancel": "Cancel",
"send": "Send",
"receive": "Receive"
"receive": "Receive",
"import": "Import",
"export": "Export"
},
"playlist": {
"local": "Local Playlists",
@ -42,6 +44,7 @@
"blur_light": "Blur (Light)",
"dracula": "Dracula",
"nord": "Nord",
"compact": "Compact View",
"tab": "Default Tab",
"player": "Audio Player",
"auto_queue": "Automatically Queue Songs",
@ -50,7 +53,11 @@
"auto": "auto",
"best": "best",
"worst": "worst",
"volume": "Default Volume"
"volume": "Default Volume",
"home": "Home",
"explore": "Explore",
"charts": "Charts",
"library": "Library"
},
"info": {
"see_all": "See All",

View file

@ -23,7 +23,9 @@
"create": "Crear",
"send": "Enviar",
"receive": "Recibir",
"cancel": "Cancelar"
"cancel": "Cancelar",
"import": "Importar",
"export": "Exportar"
},
"playlist": {
"local": "Listas de reproducción locales",
@ -50,7 +52,12 @@
"light": "Claro",
"blur_light": "Desenfoque (Luz)",
"dracula": "Drácula",
"nord": "Nord"
"nord": "Nord",
"home": "Inicio",
"library": "Biblioteca",
"charts": "Gráfica",
"explore": "Explorar",
"compact": "Vista compacta"
},
"info": {
"see_all": "Ver todo",

View file

@ -42,7 +42,12 @@
"blur": "Flou",
"blur_light": "Flou (clair)",
"dracula": "Dracula",
"nord": "Nord"
"nord": "Nord",
"charts": "Graphiques",
"explore": "Explorer",
"home": "Accueil",
"library": "Bibliothèque",
"compact": "Vue compacte"
},
"info": {
"see_all": "Voir tout",
@ -55,7 +60,9 @@
"add": "Ajouter",
"create": "Créer",
"cancel": "Annuler",
"receive": "Recevoir"
"receive": "Recevoir",
"import": "Importer",
"export": "Exporter"
},
"instances": {
"up_to_date": "À jour",

View file

@ -28,7 +28,9 @@
"receive": "Primi",
"create": "Stvori",
"back": "Natrag",
"add": "Dodaj"
"add": "Dodaj",
"import": "Uvezite",
"export": "Izvezite"
},
"instances": {
"loc": "Lokacije",
@ -56,7 +58,12 @@
"blur_light": "Zamagljeno (Svjetlo)",
"dracula": "Drakula",
"nord": "Nord",
"dark": "Mračno (Zadano)"
"dark": "Mračno (Zadano)",
"library": "Knjižnica",
"home": "Početna",
"charts": "Grafikoni",
"explore": "Istražite",
"compact": "Sažeti prikaz"
},
"lyrics": {
"load": "Dohvaćanje stihova",

View file

@ -2,9 +2,9 @@
"title": {
"artists": "Artis",
"similar_artists": "Artis Serupa",
"login": "Login",
"login": "Masuk",
"albums": "Album",
"remote": "Remote",
"remote": "Jarak Jauh",
"songs": "Lagu",
"singles": "Singel",
"genres": "Genre",
@ -14,14 +14,15 @@
"spotlight": "Sorotan",
"moods": "Suasana",
"local": "Lokal",
"playlists": "Daftar Putar"
"playlists": "Daftar Putar",
"feeds": "Umpan"
},
"playlist": {
"select": "Pilih Daftar Putar yang Ingin Ditambahkan",
"add": "Tambah Lagu ke Daftar Putar",
"sync": "Sinkronisasi Daftar Putar",
"local": "Daftar Putar Lokal",
"remote": "Daftar Putar Remote",
"remote": "Daftar Putar Jarak Jauh",
"name": "Judul Daftar Putar",
"create": "Buat Daftar Putar Baru"
},
@ -34,7 +35,19 @@
"codec": "Codec",
"worst": "paling rendah",
"auto": "otomatis",
"volume": "Volume Default"
"volume": "Volume Default",
"light": "Terang",
"nord": "Nord",
"blur_light": "Buram (Terang)",
"dracula": "Drakula",
"compact": "Tampilan Kompak",
"dark": "Gelap (Bawaan)",
"blur": "Buram",
"tab": "Tab Bawaan",
"home": "Beranda",
"explore": "Jelajahi",
"charts": "Bagan",
"library": "Pustaka"
},
"info": {
"no_info": "Tidak Ada Informasi Tersedia",
@ -47,7 +60,9 @@
"cancel": "Batal",
"send": "Kirim",
"receive": "Terima",
"back": "Kembali"
"back": "Kembali",
"import": "Impor",
"export": "Ekspor"
},
"instances": {
"piped": "Instance Piped",

View file

@ -15,7 +15,12 @@
"blur": "Sfocatura",
"blur_light": "Sfocatura (chiaro)",
"dracula": "Dracula",
"nord": "Nord"
"nord": "Nord",
"library": "Raccolta",
"home": "Pagina principale",
"charts": "Grafici",
"explore": "Esplora",
"compact": "Vista compatta"
},
"title": {
"albums": "Album",
@ -40,7 +45,9 @@
"back": "Indietro",
"create": "Crea",
"send": "Invia",
"cancel": "Annulla"
"cancel": "Annulla",
"import": "Importa",
"export": "Esporta"
},
"info": {
"see_all": "Vedi tutto",

View file

@ -5,7 +5,9 @@
"cancel": "Annuleren",
"receive": "Ontvangen",
"back": "Terug",
"create": "Creëren"
"create": "Creëren",
"import": "Importeren",
"export": "Exporteren"
},
"title": {
"community": "Gemeenschap",
@ -41,7 +43,12 @@
"blur_light": "Waas (Licht)",
"dracula": "Dracula",
"nord": "Nord",
"dark": "Donker (standaard)"
"dark": "Donker (standaard)",
"explore": "Ontdekken",
"charts": "Grafieken",
"home": "Start",
"library": "Bibliotheek",
"compact": "Compacte weergave"
},
"lyrics": {
"void": "Geen songtekst",

79
src/locales/pt.json Normal file
View file

@ -0,0 +1,79 @@
{
"title": {
"playlists": "Listas de reprodução",
"songs": "Músicas",
"albums": "Álbuns",
"artists": "Artistas",
"similar_artists": "Artistas Similares",
"moods": "Humores",
"genres": "Gêneros",
"featured": "Destaques",
"spotlight": "Foco",
"community": "Comunidade",
"login": "Entrar",
"local": "Local",
"remote": "Remoto",
"search": "Pesquisar",
"feeds": "Feeds"
},
"action": {
"import": "Importar",
"back": "Voltar",
"add": "Adicionar",
"create": "Criar",
"cancel": "Cancelar",
"send": "Enviar",
"receive": "Receber",
"export": "Exportar"
},
"pref": {
"dracula": "Drácula",
"explore": "Explorar",
"charts": "Gráficos",
"library": "Biblioteca",
"theme": "Tema",
"dark": "Escuro (padrão)",
"light": "Claro",
"blur": "Desfocado",
"blur_light": "Desfocado (Claro)",
"nord": "Nord",
"compact": "Visualização compacta",
"tab": "Guia Padrão",
"player": "Reprodutor de Áudio",
"auto_queue": "Enfileirar músicas automaticamente",
"codec": "Codec",
"quality": "Qualidade",
"auto": "automática",
"best": "melhor",
"worst": "pior",
"volume": "Volume Padrão"
},
"instances": {
"up_to_date": "Atualizado",
"version": "Versão",
"hyp": "Instância do Hyperpipe",
"piped": "Instância do Piped",
"auth": "Instância de Autenticação",
"name": "Nome",
"loc": "Localizações",
"cdn": "CDN"
},
"lyrics": {
"load": "Buscando Letras",
"void": "Sem Letras"
},
"playlist": {
"local": "Playlists Locais",
"remote": "Playlists Remotas",
"name": "Nome da Playlist",
"add": "Adicionar Músicas à Playlist",
"create": "Criar uma nova Playlist",
"sync": "Sincronizar playlists",
"select": "Selecione a Playlist para Adicionar"
},
"info": {
"see_all": "Ver Tudo",
"search": "Comece a pesquisar",
"no_info": "Nenhuma Informação Disponível"
}
}

View file

@ -42,6 +42,7 @@
"blur_light": "Desfocado (Claro)",
"dracula": "Dracula",
"nord": "Nord",
"compact": "Visualização compacta",
"tab": "Aba Padrão",
"player": "Reprodutor de Áudio",
"auto_queue": "Enfileirar músicas automaticamente",

View file

@ -23,7 +23,9 @@
"create": "Creați",
"send": "Trimite",
"receive": "Primiți",
"cancel": "Anulează"
"cancel": "Anulează",
"import": "Importă",
"export": "Exportă"
},
"playlist": {
"local": "Playlisturi locale",
@ -50,7 +52,12 @@
"light": "Luminat",
"blur_light": "Blur (Lumină)",
"dracula": "Dracula",
"nord": "Nord"
"nord": "Nord",
"compact": "Vizualizare compactă",
"home": "Acasă",
"explore": "Explorează",
"charts": "Topuri",
"library": "Bibliotecă"
},
"instances": {
"cdn": "CDN",

View file

@ -23,7 +23,9 @@
"cancel": "Отмена",
"receive": "Получить",
"send": "Отправить",
"create": "Создать"
"create": "Создать",
"import": "Импорт",
"export": "Экспорт"
},
"playlist": {
"local": "Локальные плейлисты",
@ -50,7 +52,12 @@
"blur": "Размытый",
"blur_light": "Размытый (светлый)",
"dracula": "Дракула",
"nord": "Норд"
"nord": "Норд",
"compact": "Компактный вид",
"home": "Главная",
"explore": "Обзор",
"charts": "Чарты",
"library": "Библиотека"
},
"instances": {
"piped": "Инстанс Piped",

View file

@ -5,7 +5,9 @@
"create": "Креирај",
"send": "Пошаљи",
"cancel": "Откажи",
"receive": "Прими"
"receive": "Прими",
"import": "Увезите",
"export": "Извезите"
},
"title": {
"featured": "Истакнуто",
@ -50,7 +52,12 @@
"dracula": "Дракула",
"nord": "Норд",
"dark": "Мрачно (Подразумевано)",
"blur_light": "Замућење (Светло)"
"blur_light": "Замућење (Светло)",
"home": "Почетна",
"charts": "Графикони",
"library": "Библиотека",
"explore": "Истражите",
"compact": "Компактни преглед"
},
"instances": {
"loc": "Локације",

View file

@ -23,7 +23,9 @@
"create": "Oluştur",
"cancel": "İptal",
"receive": "Al",
"send": "Gönder"
"send": "Gönder",
"export": "Dışa aktar",
"import": "İçe aktar"
},
"playlist": {
"local": "Yerel Oynatma Listeleri",
@ -50,7 +52,12 @@
"blur": "Bulanıklaştır",
"blur_light": "Bulanıklaştır (Açık)",
"dracula": "Dracula",
"nord": "Nord"
"nord": "Nord",
"charts": "Grafikler",
"library": "Kütüphane",
"explore": "Keşfet",
"home": "Ana sayfa",
"compact": "Sıkı Görünüm"
},
"info": {
"see_all": "Hepsini Gör",

View file

@ -4,7 +4,7 @@
"albums": "Các album",
"artists": "Các nhạc sĩ",
"similar_artists": "Các nhạc sĩ liên quan",
"genres": "Các thể loại",
"genres": "Thể loại",
"community": "Cộng đồng",
"search": "Tìm kiếm",
"singles": "Các đĩa đơn",
@ -12,7 +12,8 @@
"moods": "Chủ đề",
"playlists": "Các danh sách phát",
"featured": "Nổi bật",
"feeds": "Nguồn cấp dữ liệu"
"feeds": "Nguồn cấp dữ liệu",
"spotlight": "Tiêu điểm"
},
"pref": {
"auto": "tự động",
@ -30,7 +31,12 @@
"nord": "Nord",
"dracula": "Dracula",
"auto_queue": "Tự động xếp hàng các bài hát",
"tab": "Thẻ mặc định"
"tab": "Thẻ mặc định",
"library": "Thư viện",
"explore": "Khám phá",
"home": "Trang chủ",
"charts": "Xếp hạng",
"compact": "Chế độ xem nhỏ gọn"
},
"action": {
"cancel": "Hủy",
@ -38,7 +44,9 @@
"add": "Thêm",
"back": "Quay lại",
"receive": "Nhận",
"send": "Gửi"
"send": "Gửi",
"import": "Nhập",
"export": "Xuất"
},
"instances": {
"version": "Phiên bản",

View file

@ -32,7 +32,9 @@
"back": "返回",
"cancel": "取消",
"receive": "接收",
"send": "发送"
"send": "发送",
"import": "导入",
"export": "导出"
},
"pref": {
"player": "音频播放器",
@ -50,7 +52,12 @@
"light": "亮色",
"blur_light": "模糊(亮色)",
"dracula": "Dracula",
"nord": "Nord"
"nord": "Nord",
"home": "主页",
"explore": "探索",
"charts": "排行榜",
"library": "收藏库",
"compact": "紧凑视图"
},
"info": {
"see_all": "显示全部",

View file

@ -1,6 +1,4 @@
import { Buffer } from 'buffer/';
window.Buffer = Buffer;
import { json2xml } from 'xml-js';
import { useXML } from './xml.js';
export function useDash(streams, len) {
const sets = [],
@ -119,6 +117,5 @@ export function useDash(streams, len) {
],
};
console.log(json2xml(gen));
return json2xml(gen);
return useXML(gen);
}

View file

@ -6,15 +6,13 @@ export const HYPERPIPE_INSTANCE = 'hyperpipeapi.onrender.com';
export function getPipedQuery() {
const papi = new URLSearchParams(location.search).get('pipedapi');
if (!papi) {
return '';
}
if (!papi) return '';
return '?pipedapi=' + useSanitize(papi);
}
export async function getJson(url) {
const res = await fetch(url)
export async function getJson(url, opts) {
const res = await fetch(url, opts)
.then(res => res.json())
.catch(err => {
console.error(err);
@ -36,13 +34,13 @@ export async function getJson(url) {
}
}
export async function getJsonPiped(path) {
export async function getJsonPiped(path, opts) {
const root =
new URLSearchParams(location.search).get('pipedapi') ||
useStore().getItem('pipedapi') ||
PIPED_INSTANCE;
return await getJson('https://' + root + path);
return await getJson('https://' + root + path, opts);
}
export async function getJsonHyp(path) {
@ -51,7 +49,7 @@ export async function getJsonHyp(path) {
return await getJson('https://' + root + path);
}
export async function getJsonAuth(path, opts) {
export async function getJsonAuth(path, opts = {}) {
const root = useStore().getItem('authapi') || PIPED_INSTANCE;
return await fetch('https://' + root + path, opts)

View file

@ -35,9 +35,26 @@ export function useStore() {
}
}
export function useShare(data) {
if ('share' in navigator) {
navigator.share(data).catch(err => {
console.err(err);
});
} else {
navigator.clipboard.writeText(data.url).then(
() => {
alert('Copied to Clipboard');
},
err => {
alert(err);
},
);
}
}
export function useMetadata(url, urls, data) {
if ('mediaSession' in navigator) {
const now = urls.filter(u => u.url === url)[0];
const now = urls.find(u => u.url === url);
let artwork = [],
album = undefined;
@ -51,7 +68,7 @@ export function useMetadata(url, urls, data) {
src: t.url,
type: 'image/webp',
}));
} else artwork = [{ src: data.art, type: 'image/webp' }];
} else if (data.art) artwork = [{ src: data.art, type: 'image/webp' }];
console.log(album, artwork);
}

62
src/scripts/xml.js Normal file
View file

@ -0,0 +1,62 @@
function useAttr(json) {
let attrs = '';
for (const attr in json) {
if (json[attr] != null) {
attrs += ' ';
attrs += attr;
attrs += '="';
attrs += ('' + json[attr]).replace(/"/g, '&quote;');
attrs += '"';
}
}
return attrs;
}
function useElems(json) {
let elems = '';
json.forEach(elem => {
switch (elem.type) {
case 'element':
elems += '<';
elems += elem.name;
elems += useAttr(elem.attributes);
if (elem?.elements?.length > 0) {
elems += '>';
elems += useElems(elem.elements);
elems += '</';
elems += elem.name;
elems += '>';
} else elems += '/>';
break;
case 'text':
elems += ('' + elem.text)
.replace(/&amp;/g, '&')
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
break;
}
});
return elems;
}
export function useXML(json) {
json = JSON.parse(JSON.stringify(json));
let base = '';
base += '<?xml';
base += useAttr(json.declaration.attributes);
base += '?>';
base += useElems(json.elements);
console.log(base);
return base;
}

View file

@ -12,6 +12,10 @@ export const SUPPORTED_LOCALES = [
code: 'az',
name: 'Azərbaycan',
},
{
code: 'bn',
name: 'বাংলা',
},
{
code: 'bs',
name: 'Bosanski',
@ -68,13 +72,21 @@ export const SUPPORTED_LOCALES = [
code: 'nl',
name: 'Nederlands',
},
{
code: 'pt',
name: 'Português',
},
{
code: 'pt_br',
name: 'Português Brasileiro',
},
{
code: 'ro',
name: 'Română',
},
{
code: 'ru',
name: 'Pусский ',
name: 'Pyccкий',
},
{
code: 'sr',
@ -92,10 +104,6 @@ export const SUPPORTED_LOCALES = [
code: 'zh_Hans',
name: '中文 (简体)',
},
{
code: 'pt_br',
name: 'Português Brasileiro',
},
];
export const useNav = defineStore('nav', () => {

View file

@ -8,18 +8,17 @@ const store = useStore();
export const useData = defineStore('data', () => {
const state = reactive({
title: '',
description: '',
artist: '',
art: '',
url: '',
artistUrl: '',
lyrics: '',
src: [],
urls: [],
});
const player = usePlayer();
title: '',
description: '',
artist: '',
art: '',
url: '',
artistUrl: '',
lyrics: '',
src: [],
urls: [],
}),
player = usePlayer();
async function getSong(e) {
console.log(e);
@ -48,7 +47,7 @@ export const useData = defineStore('data', () => {
if (
store.getItem('next') !== 'false' &&
(!state.urls ||
!state.urls.filter(s => s.url == state.url)[0] ||
state.urls.findIndex(s => s.url == state.url) < 0 ||
state.urls.length == 1)
) {
const json = await getJsonHyp('/next/' + hash);
@ -98,8 +97,7 @@ export const useData = defineStore('data', () => {
}
function playNext(u) {
const now = state.urls.filter(s => s.url === state.url)[0],
i = state.urls.indexOf(now);
const i = state.urls.findIndex(s => s.url === state.url);
if (player.state.loop == 2) getSong(state.url);
else if (
@ -117,8 +115,7 @@ export const useData = defineStore('data', () => {
}
function prevTrack() {
const now = state.urls.filter(s => s.url === state.url)[0],
i = state.urls.indexOf(now);
const i = state.urls.findIndex(s => s.url === state.url);
if (state.urls[i - 1]) getSong(state.urls[i - 1].url);
else if (player.state.loop == 1) {
@ -130,8 +127,7 @@ export const useData = defineStore('data', () => {
}
function nextTrack() {
const now = state.urls.filter(s => s.url === state.url)[0],
i = state.urls.indexOf(now);
const i = state.urls.findIndex(s => s.url === state.url);
if (state.urls[i + 1]) getSong(state.urls[i + 1].url);
else if (player.state.loop == 1) {

View file

@ -3,13 +3,14 @@ import { defineStore } from 'pinia';
import { useNav } from '@/stores/misc.js';
import { getJsonPiped, getJsonHyp } from '@/scripts/fetch.js';
import { getJsonPiped, getJsonHyp, getJsonAuth } from '@/scripts/fetch.js';
import { useRoute } from '@/scripts/util.js';
export const useResults = defineStore('results', () => {
const items = ref({}),
search = ref(''),
chartsId = ref(''),
album = ref(''),
next = ref('');
function setItem(key, val) {
@ -18,6 +19,9 @@ export const useResults = defineStore('results', () => {
}
function resetItems() {
next.value = undefined;
album.value = undefined;
useArtist().reset();
for (let i in items.value) {
items.value[i] = undefined;
@ -42,22 +46,24 @@ export const useResults = defineStore('results', () => {
console.log('Album: ', e);
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('list'),
json = await getJsonPiped('/playlists/' + hash);
isAuth =
/[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}/.test(
hash,
),
path = '/playlists/' + hash,
json = isAuth ? await getJsonAuth(path) : await getJsonPiped(path);
console.log(json, json.relatedStreams);
resetItems();
if (
/[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}/.test(
hash,
)
) {
album.value = e;
if (isAuth)
json.relatedStreams = json.relatedStreams.map(i => {
i.playlistId = hash;
return i;
});
}
setItem('songs', {
items: json.relatedStreams,
@ -78,6 +84,7 @@ export const useResults = defineStore('results', () => {
search,
chartsId,
next,
album,
setItem,
resetItems,
getExplore,

View file

@ -2,10 +2,49 @@ import { fileURLToPath, URL } from 'url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
plugins: [
vue(),
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: [
'**/*.{js,css,html,png,svg,woff2}',
'manifest.webmanifest',
],
},
manifest: {
name: 'Hyperpipe',
short_name: 'Hyperpipe',
start_url: '/',
display: 'standalone',
background_color: '#000',
theme_color: '#000',
description: 'Privacy respecting YouTube Music Frontend.',
icons: [
{
src: '/android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: '/android-maskable.png',
sizes: '1024x1024',
type: 'image/png',
purpose: 'maskable',
},
],
},
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),