Files
Musify/web-player/src/lib/server/youtube.ts
T
Admin dc1ba89a6d Add likes, follows and playlist visibility; redesign web-player UI
Backend:
- New Likes feature (toggle/get liked tracks) and endpoints
- User follow/unfollow, listening stats, public user profile
- Playlist visibility (public/private) support
- Various auth and DTO/response adjustments to support the above

Frontend (web-player):
- New design system pass: theme tokens/mode (light/dark), glass menu,
  tabs bar, player dock/extras/queue components, mix tiles, artist link
- New routes: Liked, public user profile (/u/[id]), likes API proxy
- Search refactor: shared SearchBox component used by TopBar and
  MobileHeader (search now reachable on mobile), live results as you
  type instead of requiring Enter, dropped the "Buscando…" quick-result
  preview
- Removed now-unused components (MobileNav, CoverBadge, MixBentoTile,
  PlayPauseOverlay, SuggestionsTable, userDirectory)
- Design handoff reference docs added under web-player/design_handoff_musify_player
  and web-player/docs/design-system-refactor-plan.md
2026-09-14 23:42:45 +02:00

93 lines
2.6 KiB
TypeScript

import type { createApiClient } from '$lib/server/api';
import type { YouTubeAlbumResult, YouTubeSong } from '$lib/types';
export const YOUTUBE_FILLER_QUERIES = [
'pop',
'rock',
'reggaeton',
'lo-fi',
'indie',
'salsa',
'k-pop',
'electrónica',
'jazz',
'baladas',
'trap',
'cumbia',
'hip hop',
'música clásica'
];
export interface YoutubeFiller {
query: string;
items: YouTubeSong[];
continuationToken: string;
}
export interface YoutubeAlbumFiller {
query: string;
items: YouTubeAlbumResult[];
}
function pickRandomFillerQuery(): string {
return YOUTUBE_FILLER_QUERIES[Math.floor(Math.random() * YOUTUBE_FILLER_QUERIES.length)];
}
function hash(text: string): number {
let h = 0;
for (let i = 0; i < text.length; i++) {
h = (h * 31 + text.charCodeAt(i)) | 0;
}
return Math.abs(h);
}
function pickFillerQueryForUser(userId: string, date: Date, variant = 0): string {
const dayKey = date.toISOString().slice(0, 10);
const index = (hash(`${userId}:${dayKey}`) + variant) % YOUTUBE_FILLER_QUERIES.length;
return YOUTUBE_FILLER_QUERIES[index];
}
export async function fetchYoutubeFiller(
api: ReturnType<typeof createApiClient>,
accessToken: string | null | undefined,
limit: number,
seedQuery?: string,
userId?: string,
variant = 0
): Promise<YoutubeFiller> {
if (!accessToken) return { query: '', items: [], continuationToken: '' };
const query =
seedQuery?.trim() ||
(userId ? pickFillerQueryForUser(userId, new Date(), variant) : pickRandomFillerQuery());
const { data } = await api.GET('/tracks', { params: { query: { name: query, pageSize: 1 } } });
const items = (data?.items ?? [])
.map((item) => item.youTubeSong)
.filter((song): song is NonNullable<typeof song> => song !== null && song !== undefined);
return {
query,
items: (items as YouTubeSong[]).slice(0, limit),
continuationToken: data?.nextYoutubeContinuationToken ?? ''
};
}
export async function fetchYoutubeAlbumFiller(
api: ReturnType<typeof createApiClient>,
accessToken: string | null | undefined,
limit: number,
seedQuery?: string,
userId?: string,
variant = 0
): Promise<YoutubeAlbumFiller> {
if (!accessToken) return { query: '', items: [] };
const query =
seedQuery?.trim() ||
(userId ? pickFillerQueryForUser(userId, new Date(), variant) : pickRandomFillerQuery());
const { data } = await api.GET('/albums', {
params: { query: { title: query, pageNumber: 1, pageSize: limit + 5 } }
});
const items = (data?.items ?? [])
.map((item) => item.youTubeAlbum)
.filter((album): album is NonNullable<typeof album> => album !== null && album !== undefined);
return { query, items: (items as YouTubeAlbumResult[]).slice(0, limit) };
}