mirror of
https://github.com/SrIzan10/lofi.git
synced 2026-06-06 00:56:53 +00:00
feat: api rework (#5)
This commit is contained in:
4
src/app.d.ts
vendored
4
src/app.d.ts
vendored
@@ -7,8 +7,8 @@ declare global {
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
interface Platform {
|
||||
caches: CacheStorage & { default: Cache };
|
||||
}
|
||||
caches: CacheStorage & { default: Cache }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
{#if !appState.isLoading}
|
||||
<audio
|
||||
bind:this={audioElement}
|
||||
src={appState.currentSong!.endpoint || `https://stream.chillhop.com/mp3/${appState.currentSong!.fileId}`}
|
||||
src={appState.currentSong!.endpoint}
|
||||
autoplay
|
||||
volume={appState.volume}
|
||||
ontimeupdate={checkTimeAndPrepareNextSong}
|
||||
|
||||
13
src/lib/fisheryates.ts
Normal file
13
src/lib/fisheryates.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// source: https://decipher.dev/30-seconds-of-typescript/docs/shuffle/
|
||||
// modified to use existing SIMDMersenneTwister constructor for random number generation
|
||||
|
||||
import type { SIMDMersenneTwister } from "./mersenne";
|
||||
|
||||
export const fisherYates = (arr: any[], sfml: SIMDMersenneTwister) => {
|
||||
let m = arr.length;
|
||||
while (m) {
|
||||
const i = Math.floor(sfml.random() * m--);
|
||||
[arr[m], arr[i]] = [arr[i], arr[m]];
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
297
src/lib/mersenne.ts
Normal file
297
src/lib/mersenne.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Original API from TypeScript port by Thilo Planz.
|
||||
* https://gist.github.com/thiloplanz/6abf04f957197e9e3912
|
||||
*
|
||||
* SFMT implementation by Claude Sonnet 4.
|
||||
*/
|
||||
|
||||
/*
|
||||
I've wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace
|
||||
so it's better encapsulated. Now you can have multiple random number generators
|
||||
and they won't stomp all over eachother's state.
|
||||
|
||||
If you want to use this as a substitute for Math.random(), use the random()
|
||||
method like so:
|
||||
|
||||
var m = new SIMDMersenneTwister();
|
||||
var randomNumber = m.random();
|
||||
|
||||
You can also call the other genrand_{foo}() methods on the instance.
|
||||
|
||||
If you want to use a specific seed in order to get a repeatable random
|
||||
sequence, pass an integer into the constructor:
|
||||
|
||||
var m = new SIMDMersenneTwister(123);
|
||||
|
||||
and that will always produce the same random sequence.
|
||||
|
||||
Sean McCullough (banksean@gmail.com)
|
||||
*/
|
||||
|
||||
/*
|
||||
* SIMD-oriented Fast Mersenne Twister (SFMT) TypeScript implementation
|
||||
* Based on the SFMT algorithm by Mutsuo Saito and Makoto Matsumoto
|
||||
*
|
||||
* This implementation provides better performance through SIMD-like operations
|
||||
* while maintaining the same API as the standard Mersenne Twister.
|
||||
*/
|
||||
|
||||
/*
|
||||
A C-program for MT19937, with initialization improved 2002/1/26.
|
||||
Coded by Takuji Nishimura and Makoto Matsumoto.
|
||||
|
||||
Before using, initialize the state by using init_genrand(seed)
|
||||
or init_by_array(init_key, key_length).
|
||||
|
||||
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Any feedback is very welcome.
|
||||
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
|
||||
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
|
||||
*/
|
||||
|
||||
export class SIMDMersenneTwister {
|
||||
/* SFMT period parameters for SFMT19937 */
|
||||
private readonly MEXP = 19937;
|
||||
private readonly N = Math.floor(this.MEXP / 128) + 1; // 156
|
||||
private readonly N32 = this.N * 4; // 624
|
||||
private readonly POS1 = 122;
|
||||
private readonly SL1 = 18;
|
||||
private readonly SL2 = 1;
|
||||
private readonly SR1 = 11;
|
||||
private readonly SR2 = 1;
|
||||
private readonly MSK1 = 0xdfffffef;
|
||||
private readonly MSK2 = 0xddfecb7f;
|
||||
private readonly MSK3 = 0xbffaffff;
|
||||
private readonly MSK4 = 0xbffffff6;
|
||||
private readonly PARITY1 = 0x00000001;
|
||||
private readonly PARITY2 = 0x00000000;
|
||||
private readonly PARITY3 = 0x00000000;
|
||||
private readonly PARITY4 = 0x13c9e684;
|
||||
|
||||
/* 128-bit state array represented as 32-bit chunks */
|
||||
private state = new Uint32Array(this.N32);
|
||||
private idx = this.N32; /* index counter */
|
||||
|
||||
constructor(seed?: number) {
|
||||
if (seed === undefined) {
|
||||
seed = new Date().getTime();
|
||||
}
|
||||
this.init_genrand(seed);
|
||||
}
|
||||
|
||||
/* Initialize generator state with seed */
|
||||
private init_genrand(seed: number): void {
|
||||
this.state[0] = seed >>> 0;
|
||||
for (let i = 1; i < this.N32; i++) {
|
||||
this.state[i] = (1812433253 * (this.state[i - 1] ^ (this.state[i - 1] >>> 30)) + i) >>> 0;
|
||||
}
|
||||
this.idx = this.N32;
|
||||
this.period_certification();
|
||||
}
|
||||
|
||||
/* Period certification */
|
||||
private period_certification(): void {
|
||||
const PARITY = [this.PARITY1, this.PARITY2, this.PARITY3, this.PARITY4];
|
||||
let inner = 0;
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
inner ^= this.state[i] & PARITY[i];
|
||||
}
|
||||
|
||||
for (let i = 16; i > 0; i >>= 1) {
|
||||
inner ^= inner >> i;
|
||||
}
|
||||
inner &= 1;
|
||||
|
||||
if (inner === 1) return;
|
||||
|
||||
/* Period certification failed, fix it */
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let work = 1;
|
||||
for (let j = 0; j < 32; j++) {
|
||||
if ((work & PARITY[i]) !== 0) {
|
||||
this.state[i] ^= work;
|
||||
return;
|
||||
}
|
||||
work <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize by an array with array-length */
|
||||
init_by_array(init_key: number[], key_length: number): void {
|
||||
let i = 1, j = 0;
|
||||
let count = this.N32 > key_length ? this.N32 : key_length;
|
||||
|
||||
this.init_genrand(19650218);
|
||||
|
||||
for (; count > 0; count--) {
|
||||
this.state[i] = ((this.state[i] ^ ((this.state[i - 1] ^ (this.state[i - 1] >>> 30)) * 1664525)) + init_key[j] + j) >>> 0;
|
||||
i++;
|
||||
j++;
|
||||
if (i >= this.N32) {
|
||||
this.state[0] = this.state[this.N32 - 1];
|
||||
i = 1;
|
||||
}
|
||||
if (j >= key_length) j = 0;
|
||||
}
|
||||
|
||||
for (count = this.N32 - 1; count > 0; count--) {
|
||||
this.state[i] = ((this.state[i] ^ ((this.state[i - 1] ^ (this.state[i - 1] >>> 30)) * 1566083941)) - i) >>> 0;
|
||||
i++;
|
||||
if (i >= this.N32) {
|
||||
this.state[0] = this.state[this.N32 - 1];
|
||||
i = 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.state[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
|
||||
this.idx = this.N32;
|
||||
this.period_certification();
|
||||
}
|
||||
|
||||
/* SIMD 128-bit recursion */
|
||||
private do_recursion(a: Uint32Array, b: Uint32Array, c: Uint32Array, d: Uint32Array): void {
|
||||
const lshift128 = (a: Uint32Array, out: Uint32Array, shift: number): void => {
|
||||
const th = shift >>> 5;
|
||||
const tl = shift & 31;
|
||||
const tr = 32 - tl;
|
||||
|
||||
if (th >= 4) {
|
||||
out[0] = out[1] = out[2] = out[3] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4 - th; i++) {
|
||||
out[i] = (a[i + th] << tl) | (i + th + 1 < 4 ? a[i + th + 1] >>> tr : 0);
|
||||
}
|
||||
for (let i = 4 - th; i < 4; i++) {
|
||||
out[i] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const rshift128 = (a: Uint32Array, out: Uint32Array, shift: number): void => {
|
||||
const th = shift >>> 5;
|
||||
const tl = shift & 31;
|
||||
const tr = 32 - tl;
|
||||
|
||||
if (th >= 4) {
|
||||
out[0] = out[1] = out[2] = out[3] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 3; i >= th; i--) {
|
||||
out[i] = (a[i - th] >>> tl) | (i - th - 1 >= 0 ? a[i - th - 1] << tr : 0);
|
||||
}
|
||||
for (let i = th - 1; i >= 0; i--) {
|
||||
out[i] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const x = new Uint32Array(4);
|
||||
const y = new Uint32Array(4);
|
||||
|
||||
lshift128(a, x, this.SL2);
|
||||
rshift128(c, y, this.SR2);
|
||||
|
||||
a[0] = a[0] ^ x[0] ^ ((b[0] >>> this.SR1) & this.MSK1) ^ y[0] ^ (d[0] << this.SL1);
|
||||
a[1] = a[1] ^ x[1] ^ ((b[1] >>> this.SR1) & this.MSK2) ^ y[1] ^ (d[1] << this.SL1);
|
||||
a[2] = a[2] ^ x[2] ^ ((b[2] >>> this.SR1) & this.MSK3) ^ y[2] ^ (d[2] << this.SL1);
|
||||
a[3] = a[3] ^ x[3] ^ ((b[3] >>> this.SR1) & this.MSK4) ^ y[3] ^ (d[3] << this.SL1);
|
||||
}
|
||||
|
||||
/* Generate the next N 128-bit blocks */
|
||||
private gen_rand_all(): void {
|
||||
let r1 = this.N - 2;
|
||||
let r2 = this.N - 1;
|
||||
|
||||
for (let i = 0; i < this.N - this.POS1; i++) {
|
||||
this.do_recursion(
|
||||
this.state.subarray(i * 4, (i + 1) * 4),
|
||||
this.state.subarray((i + this.POS1) * 4, (i + this.POS1 + 1) * 4),
|
||||
this.state.subarray(r1 * 4, (r1 + 1) * 4),
|
||||
this.state.subarray(r2 * 4, (r2 + 1) * 4)
|
||||
);
|
||||
r1 = r2;
|
||||
r2 = i;
|
||||
}
|
||||
|
||||
for (let i = this.N - this.POS1; i < this.N; i++) {
|
||||
this.do_recursion(
|
||||
this.state.subarray(i * 4, (i + 1) * 4),
|
||||
this.state.subarray((i + this.POS1 - this.N) * 4, (i + this.POS1 - this.N + 1) * 4),
|
||||
this.state.subarray(r1 * 4, (r1 + 1) * 4),
|
||||
this.state.subarray(r2 * 4, (r2 + 1) * 4)
|
||||
);
|
||||
r1 = r2;
|
||||
r2 = i;
|
||||
}
|
||||
|
||||
this.idx = 0;
|
||||
}
|
||||
|
||||
/* generates a random number on [0,0xffffffff]-interval */
|
||||
genrand_int32(): number {
|
||||
if (this.idx >= this.N32) {
|
||||
this.gen_rand_all();
|
||||
}
|
||||
return this.state[this.idx++];
|
||||
}
|
||||
|
||||
/* generates a random number on [0,0x7fffffff]-interval */
|
||||
genrand_int31(): number {
|
||||
return this.genrand_int32() >>> 1;
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1]-real-interval */
|
||||
genrand_real1(): number {
|
||||
return this.genrand_int32() * (1.0 / 4294967295.0);
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1)-real-interval */
|
||||
random(): number {
|
||||
return this.genrand_int32() * (1.0 / 4294967296.0);
|
||||
}
|
||||
|
||||
/* generates a random number on (0,1)-real-interval */
|
||||
genrand_real3(): number {
|
||||
return (this.genrand_int32() + 0.5) * (1.0 / 4294967296.0);
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1) with 53-bit resolution*/
|
||||
genrand_res53(): number {
|
||||
const a = this.genrand_int32() >>> 5;
|
||||
const b = this.genrand_int32() >>> 6;
|
||||
return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
|
||||
}
|
||||
}
|
||||
18
src/lib/stations/chillhop.ts
Normal file
18
src/lib/stations/chillhop.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { CHSong, Song } from "@/types";
|
||||
|
||||
export async function getChillhopStation(id: number): Promise<Song[]> {
|
||||
const res = await fetch(`https://stream.chillhop.com/live/${id}`);
|
||||
const data = await res.json() as CHSong[];
|
||||
|
||||
const finalData = data.map(song => ({
|
||||
artists: song.artists,
|
||||
title: song.title,
|
||||
endpoint: `https://stream.chillhop.com/mp3/${song.fileId}`,
|
||||
image: song.image,
|
||||
label: 'Chillhop Music',
|
||||
spotifyId: song.spotifyId,
|
||||
duration: song.duration,
|
||||
})) as Song[];
|
||||
|
||||
return finalData;
|
||||
}
|
||||
34
src/lib/stations/index.ts
Normal file
34
src/lib/stations/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Song, Station } from '@/types';
|
||||
import { getChillhopStation } from './chillhop';
|
||||
import { getSleepStationSongs } from './sleep';
|
||||
import { getChillhopData } from '@/utils';
|
||||
|
||||
const customStations: Record<number, () => Promise<Song[]>> = {
|
||||
50000: getSleepStationSongs,
|
||||
};
|
||||
|
||||
export const stations: Record<number, () => Promise<Song[]>> = new Proxy(customStations, {
|
||||
get(target, prop) {
|
||||
const stationId = Number(prop);
|
||||
|
||||
if (target[stationId]) {
|
||||
return target[stationId];
|
||||
}
|
||||
|
||||
if (stationId >= 10000 && stationId < 20000) {
|
||||
return () => getChillhopStation(stationId);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
has(target, prop) {
|
||||
const stationId = Number(prop);
|
||||
return target[stationId] !== undefined || (stationId >= 10000 && stationId < 20000);
|
||||
},
|
||||
});
|
||||
|
||||
export const stationMetadata = Promise.resolve([
|
||||
{ id: 50000, name: 'Lofi Sleep' },
|
||||
...(await getChillhopData()).stations,
|
||||
]) as Promise<Station[]>;
|
||||
26
src/lib/stations/sleep.ts
Normal file
26
src/lib/stations/sleep.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { Song } from "@/types";
|
||||
import { SIMDMersenneTwister } from "@/mersenne";
|
||||
import { fisherYates } from "@/fisheryates";
|
||||
|
||||
export async function getSleepStationSongs(): Promise<Song[]> {
|
||||
const m = new SIMDMersenneTwister();
|
||||
|
||||
const res = await fetch('https://lofi-cdn.srizan.dev/sleep/list.json');
|
||||
const files = await res.json() as string[];
|
||||
|
||||
const mapped = files.map(file => {
|
||||
const [artist, title] = file.replace('.opus', '').split(' - ');
|
||||
return {
|
||||
fileId: file.replace('.opus', ''),
|
||||
endpoint: `https://lofi-cdn.srizan.dev/sleep/${file}`,
|
||||
artists: artist,
|
||||
title: title,
|
||||
image: `https://lofi-cdn.srizan.dev/sleep/thumbs/${file.replace('.opus', '')}.webp`,
|
||||
label: 'Chilled Cat',
|
||||
duration: 0, // TODO: get duration for all songs and put them in list.json
|
||||
};
|
||||
}) as Song[];
|
||||
|
||||
const shuffled = fisherYates(mapped, m).slice(0, 5);
|
||||
return shuffled;
|
||||
}
|
||||
@@ -1,20 +1,30 @@
|
||||
export interface Song {
|
||||
artists: string;
|
||||
title: string;
|
||||
endpoint: string;
|
||||
image: string;
|
||||
duration: number; // not really used right now
|
||||
label?: string; // optional record label
|
||||
spotifyId?: string; // TODO: enforce in the future for all spotify scraped stations, unused atm.
|
||||
}
|
||||
|
||||
export interface CHSong {
|
||||
id: number;
|
||||
fileId: number | string;
|
||||
endpoint?: string;
|
||||
artists: string;
|
||||
title: string;
|
||||
image: string;
|
||||
likes: number;
|
||||
likes?: number;
|
||||
featured?: string;
|
||||
releaseDate: string;
|
||||
releaseDateText: string;
|
||||
duration: number;
|
||||
isrc: string;
|
||||
label: string;
|
||||
spotifyId: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
releaseDate?: string;
|
||||
releaseDateText?: string;
|
||||
duration?: number;
|
||||
isrc?: string;
|
||||
label?: string;
|
||||
spotifyId?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
export interface Preset {
|
||||
@@ -47,7 +57,7 @@ export interface DecodedStationMeta {
|
||||
export interface Station {
|
||||
name: string;
|
||||
id: number;
|
||||
meta: string; // Base64 encoded JSON string (DecodedStationMeta)
|
||||
meta?: string; // Base64 encoded JSON string (DecodedStationMeta)
|
||||
}
|
||||
|
||||
export interface Background {
|
||||
@@ -84,4 +94,4 @@ export interface BRStation {
|
||||
order: string;
|
||||
Name: string;
|
||||
Source: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,3 @@ export async function getStationSongs(stationId: number) {
|
||||
return data;
|
||||
}
|
||||
|
||||
export function setSongTime() {
|
||||
const currentTime = new Date().getTime() / 1000;
|
||||
const startTime = new Date(state.currentSong!.startTime).getTime() / 1000;
|
||||
const endTime = new Date(state.currentSong!.endTime).getTime() / 1000;
|
||||
const duration = endTime - startTime;
|
||||
const elapsed = currentTime - startTime;
|
||||
if (elapsed > 0 && elapsed < duration) {
|
||||
state.currentTime = elapsed;
|
||||
} else {
|
||||
state.currentTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { getChillhopData } from '@/utils';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { BASEROW_ENDPOINT, BASEROW_TOKEN } from '$env/static/private'
|
||||
import { dev } from '$app/environment';
|
||||
import type { BRStation } from '@/types';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
if (!dev) {
|
||||
return new Response('Not allowed', {
|
||||
status: 403
|
||||
});
|
||||
}
|
||||
const generalData = await getChillhopData();
|
||||
const existingRows = await fetch(`${BASEROW_ENDPOINT}/api/database/rows/table/685/?user_field_names=true`, {
|
||||
headers: {
|
||||
Authorization: `Token ${BASEROW_TOKEN}`
|
||||
}
|
||||
}).then(async (res) => await res.json()).then(d => d.results as BRStation[]);
|
||||
const chillhopStations = existingRows.filter(r => r.Source === 'chillhop');
|
||||
await fetch(`${BASEROW_ENDPOINT}/api/database/rows/table/685/batch-delete/ `, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Token ${BASEROW_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: chillhopStations.map(r => r.id)
|
||||
})
|
||||
})
|
||||
|
||||
const stations = generalData.stations.map((station) => ({
|
||||
ID: station.id,
|
||||
Name: station.name,
|
||||
Source: 'chillhop'
|
||||
}));
|
||||
await fetch(`${BASEROW_ENDPOINT}/api/database/rows/table/685/batch/?user_field_names=true`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Token ${BASEROW_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: stations
|
||||
})
|
||||
});
|
||||
|
||||
return new Response('done');
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { stations } from '@/stations';
|
||||
|
||||
export async function GET(event) {
|
||||
const { stationId } = event.params;
|
||||
const stationIdInt = parseInt(stationId);
|
||||
@@ -5,36 +7,15 @@ export async function GET(event) {
|
||||
return new Response('Invalid station ID', { status: 400 });
|
||||
}
|
||||
|
||||
if (stationId.startsWith('50')) {
|
||||
// custom stations
|
||||
if (stationIdInt === 50000) {
|
||||
const data = await fetch('https://lofi-cdn.srizan.dev/sleep/list.json').then(async (res) => await res.json() as string[]);
|
||||
const randomData = data.sort(() => 0.5 - Math.random()).slice(0, 5).map(song => {
|
||||
const noOpus = song.replace('.opus', '');
|
||||
const [artist, title] = noOpus.split(' - ');
|
||||
return {
|
||||
id: parseInt((Math.random() * 1000000).toFixed(0)),
|
||||
endpoint: `https://lofi-cdn.srizan.dev/sleep/${song}`,
|
||||
fileId: noOpus,
|
||||
image: `https://lofi-cdn.srizan.dev/sleep/thumbs/${noOpus}.webp`,
|
||||
artists: artist,
|
||||
title: title,
|
||||
}
|
||||
});
|
||||
return new Response(JSON.stringify(randomData), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// chillhop stations
|
||||
const res = await fetch(`https://stream.chillhop.com/live/${stationId}`);
|
||||
const data = await res.json();
|
||||
return new Response(JSON.stringify(data), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const stationFunction = stations[stationIdInt];
|
||||
if (!stationFunction) {
|
||||
return new Response('Station not found', { status: 404 });
|
||||
}
|
||||
|
||||
const songs = await stationFunction();
|
||||
return new Response(JSON.stringify(songs), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,27 +1,23 @@
|
||||
import { BASEROW_ENDPOINT, BASEROW_TOKEN } from "$env/static/private"
|
||||
import type { BRStation } from "@/types";
|
||||
import { getChillhopData } from "@/utils";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { stationMetadata } from '@/stations';
|
||||
import { getChillhopData } from '@/utils';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const responses = await Promise.all([
|
||||
fetch(`${BASEROW_ENDPOINT}/api/database/rows/table/685/?user_field_names=true`, {
|
||||
headers: {
|
||||
Authorization: `Token ${BASEROW_TOKEN}`
|
||||
}
|
||||
}).then(async (res) => await res.json()).then(d => d.results as BRStation[]).then(stations => stations.map(stations => {
|
||||
return {
|
||||
id: stations.ID,
|
||||
name: stations.Name,
|
||||
}
|
||||
})),
|
||||
stationMetadata,
|
||||
getChillhopData(),
|
||||
]);
|
||||
|
||||
return new Response(JSON.stringify({ stations: responses[0], backgrounds: responses[1].backgrounds, atmospheres: responses[1].atmospheres }), {
|
||||
const responseJson = JSON.stringify({
|
||||
stations: responses[0],
|
||||
backgrounds: responses[1].backgrounds,
|
||||
atmospheres: responses[1].atmospheres,
|
||||
});
|
||||
|
||||
return new Response(responseJson, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user