initial state: muzick music player + recommendation engine

This commit is contained in:
kami
2026-07-14 01:35:52 +04:00
commit 737bf19fd1
196 changed files with 32431 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
// Deezer API client (read-only).
//
// Deezer provides a free, no-auth search API with high-quality cover art
// (up to 1000×1000 via the `cover_xl` field). Coverage is very good for
// European and mainstream releases.
//
// Rate limit: Deezer doesn't document a hard limit for the public search API,
// but ~50 req/min is a safe polite default, enforced via the http base.
//
// Best-effort: on any error, methods log a warning and return null rather than
// throwing, so enrichment never breaks the worker.
import { buildUserAgent, requestJson } from './http.js';
/** A normalised Deezer album search result (only the fields we read). */
export interface DeezerAlbum {
artistName: string;
title: string;
/** 1000×1000 cover URL. */
coverXl: string;
/** 500×500 cover URL (fallback). */
coverBig: string;
}
interface DeezerSearchResult {
artist?: { name?: string };
title?: string;
cover_xl?: string;
cover_big?: string;
}
interface DeezerSearchResponse {
data?: DeezerSearchResult[];
}
export class DeezerClient {
private readonly baseUrl = 'https://api.deezer.com';
private readonly userAgent: string;
private readonly minIntervalMs: number;
constructor(contact = '', minIntervalMs = 1200) {
this.userAgent = buildUserAgent(contact);
this.minIntervalMs = minIntervalMs;
}
/**
* Search the Deezer catalog for an album by artist + title and return the
* best match, or null if no match / error.
*
* Deezer's search `q` supports the fielded syntax `artist:"…" album:"…"`
* which gives precise matches.
*/
async searchAlbum(artist: string, album: string): Promise<DeezerAlbum | null> {
if (artist.trim() === '' || album.trim() === '') return null;
const q = `artist:"${artist}" album:"${album}"`;
const qs = new URLSearchParams({ q });
const url = `${this.baseUrl}/search/album?${qs.toString()}`;
try {
const data = await requestJson<DeezerSearchResponse>(url, {
userAgent: this.userAgent,
minIntervalMs: this.minIntervalMs,
});
const results = data.data ?? [];
if (results.length === 0) return null;
const best = results[0];
const coverXl = best.cover_xl ?? best.cover_big ?? null;
const coverBig = best.cover_big ?? best.cover_xl ?? null;
if (!coverXl) return null;
return {
artistName: best.artist?.name ?? artist,
title: best.title ?? album,
coverXl,
coverBig: coverBig ?? coverXl,
};
} catch (err) {
console.warn('[Deezer] searchAlbum failed:', (err as Error).message);
return null;
}
}
}