Add files via upload
This commit is contained in:
parent
81ce3b2271
commit
9fb037a648
13 changed files with 832 additions and 518 deletions
|
|
@ -1,27 +1,25 @@
|
|||
import * as SecureStore from 'expo-secure-store';
|
||||
|
||||
const BASE = 'https://music.nodeshift.space';
|
||||
const CLOUD = 'https://cloud.nodeshift.space';
|
||||
const BASE = 'https://music.nodeshift.space';
|
||||
const CLOUD = 'https://cloud.nodeshift.space';
|
||||
const CLIENT_ID = 'nsc_A5zi4cZ103aYJGcNTG1bERXV1YCAQW_U';
|
||||
const REDIRECT = 'burmalda://auth/callback';
|
||||
const REDIRECT = 'burmalda://auth/callback';
|
||||
|
||||
// ── Token storage ──────────────────────────────────────────────────────────────
|
||||
// ── Token storage ─────────────────────────────────────────────────────────────
|
||||
|
||||
export async function saveTokens(access: string, refresh: string) {
|
||||
await SecureStore.setItemAsync('access_token', access);
|
||||
await SecureStore.setItemAsync('refresh_token', refresh);
|
||||
}
|
||||
|
||||
export async function getAccessToken(): Promise<string | null> {
|
||||
return SecureStore.getItemAsync('access_token');
|
||||
}
|
||||
|
||||
export async function clearTokens() {
|
||||
await SecureStore.deleteItemAsync('access_token');
|
||||
await SecureStore.deleteItemAsync('refresh_token');
|
||||
}
|
||||
|
||||
// ── Auth fetch helper ──────────────────────────────────────────────────────────
|
||||
// ── Fetch helper ──────────────────────────────────────────────────────────────
|
||||
|
||||
async function apiFetch(path: string, opts: RequestInit = {}): Promise<Response> {
|
||||
const token = await getAccessToken();
|
||||
|
|
@ -33,15 +31,10 @@ async function apiFetch(path: string, opts: RequestInit = {}): Promise<Response>
|
|||
return fetch(`${BASE}${path}`, { ...opts, headers });
|
||||
}
|
||||
|
||||
// ── OAuth ──────────────────────────────────────────────────────────────────────
|
||||
// ── OAuth ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export function buildOAuthUrl(state: string): string {
|
||||
const p = new URLSearchParams({
|
||||
client_id: CLIENT_ID,
|
||||
redirect_uri: REDIRECT,
|
||||
state,
|
||||
scope: 'profile email',
|
||||
});
|
||||
const p = new URLSearchParams({ client_id: CLIENT_ID, redirect_uri: REDIRECT, state, scope: 'profile email' });
|
||||
return `${CLOUD}/api/cloud/oauth/authorize?${p}`;
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +51,7 @@ export async function exchangeCode(code: string): Promise<{ access_token: string
|
|||
return res.json();
|
||||
}
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────────
|
||||
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Track {
|
||||
id: number;
|
||||
|
|
@ -88,7 +81,12 @@ export interface User {
|
|||
plan: string;
|
||||
}
|
||||
|
||||
// ── API calls ──────────────────────────────────────────────────────────────────
|
||||
export interface Lyrics {
|
||||
synced: string | null;
|
||||
plain: string | null;
|
||||
}
|
||||
|
||||
// ── API calls ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getTracks(): Promise<Track[]> {
|
||||
const r = await apiFetch('/api/music/tracks');
|
||||
|
|
@ -114,6 +112,12 @@ export async function searchTracks(q: string): Promise<Track[]> {
|
|||
return r.json();
|
||||
}
|
||||
|
||||
export async function fetchLyrics(id: number): Promise<Lyrics | null> {
|
||||
const r = await apiFetch(`/api/music/lyrics/${id}`);
|
||||
if (!r.ok) return null;
|
||||
return r.json();
|
||||
}
|
||||
|
||||
export function streamUrl(filename: string): string {
|
||||
return `${BASE}/api/music/stream/${filename}`;
|
||||
}
|
||||
|
|
@ -123,3 +127,21 @@ export function coverUrl(filename: string | null): string | null {
|
|||
if (filename.startsWith('http')) return filename;
|
||||
return `${BASE}/api/music/cover/${filename}`;
|
||||
}
|
||||
|
||||
// ── LRC parser ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface LrcLine {
|
||||
time: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export function parseLrc(lrc: string): LrcLine[] {
|
||||
const lines: LrcLine[] = [];
|
||||
for (const raw of lrc.split('\n')) {
|
||||
const m = raw.match(/^\[(\d+):(\d+)\.(\d+)\]\s*(.*)$/);
|
||||
if (!m) continue;
|
||||
const time = parseInt(m[1]) * 60 + parseFloat(`${m[2]}.${m[3]}`);
|
||||
if (m[4].trim()) lines.push({ time, text: m[4].trim() });
|
||||
}
|
||||
return lines.sort((a, b) => a.time - b.time);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue