feat(dl): online track search and add-to-queue utility

This commit is contained in:
2025-10-02 09:54:05 -04:00
parent 81ef5bac90
commit d1edc8b7f7
6 changed files with 115 additions and 94 deletions

View File

@@ -44,6 +44,9 @@
{ {
"url": "http://*.deezer.com/**" "url": "http://*.deezer.com/**"
}, },
{
"url": "https://api.deezer.com/**"
},
{ {
"url": "https://media.deezer.com/**" "url": "https://media.deezer.com/**"
}, },

View File

@@ -250,6 +250,35 @@ export class DeezerAPI {
return this.apiCall('song.getData', { SNG_ID: trackId }); return this.apiCall('song.getData', { SNG_ID: trackId });
} }
// Search tracks using public API (no authentication required)
async searchTracks(query: string, limit: number = 25): Promise<any> {
const url = `https://api.deezer.com/search/track?q=${encodeURIComponent(query)}&limit=${limit}`;
console.log('[DEBUG] Searching Deezer API:', { query, limit, url });
try {
const response = await fetch(url, {
method: 'GET',
headers: {
...this.httpHeaders
},
connectTimeout: 30000
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('[DEBUG] Search results:', result);
return result;
} catch (error: any) {
console.error('[ERROR] Search failed:', error);
throw error;
}
}
// Get playlist data // Get playlist data
async getPlaylist(playlistId: string): Promise<any> { async getPlaylist(playlistId: string): Promise<any> {
return this.apiCall('deezer.pagePlaylist', { return this.apiCall('deezer.pagePlaylist', {

View File

@@ -0,0 +1,51 @@
/**
* Utility to add a Deezer track to the download queue
* Used by both search results and services/deezer pages
*/
import { deezerAPI } from '$lib/services/deezer';
import { addToQueue } from '$lib/stores/downloadQueue';
/**
* Fetch track metadata and add to download queue
* @param trackId - Deezer track ID
* @returns Promise that resolves when track is added to queue
*/
export async function addDeezerTrackToQueue(trackId: string): Promise<void> {
// Fetch full track data from GW API
const trackInfo = await deezerAPI.getTrack(trackId);
if (!trackInfo || !trackInfo.SNG_ID) {
throw new Error('Track not found or invalid track ID');
}
// Build track object
const track = {
id: trackInfo.SNG_ID,
title: trackInfo.SNG_TITLE,
artist: trackInfo.ART_NAME,
artistId: trackInfo.ART_ID,
artists: [trackInfo.ART_NAME],
album: trackInfo.ALB_TITLE,
albumId: trackInfo.ALB_ID,
albumArtist: trackInfo.ART_NAME,
albumArtistId: trackInfo.ART_ID,
trackNumber: trackInfo.TRACK_NUMBER || 1,
discNumber: trackInfo.DISK_NUMBER || 1,
duration: trackInfo.DURATION,
explicit: trackInfo.EXPLICIT_LYRICS === 1,
md5Origin: trackInfo.MD5_ORIGIN,
mediaVersion: trackInfo.MEDIA_VERSION,
trackToken: trackInfo.TRACK_TOKEN
};
// Add to queue
await addToQueue({
source: 'deezer',
type: 'track',
title: track.title,
artist: track.artist,
totalTracks: 1,
downloadObject: track
});
}

View File

@@ -1,71 +1,34 @@
import type { SearchResult } from '$lib/types/search'; import type { SearchResult } from '$lib/types/search';
import { deezerAPI } from '../deezer';
/** /**
* Search Deezer for tracks matching the query * Search Deezer for tracks using the public API
* TODO: Implement actual Deezer search API
* For now, returns mock data
*/ */
export async function searchDeezerTracks(query: string): Promise<SearchResult[]> { export async function searchDeezerTracks(query: string): Promise<SearchResult[]> {
if (!query.trim()) { if (!query.trim()) {
return []; return [];
} }
// Simulate API delay try {
await new Promise(resolve => setTimeout(resolve, 300)); const response = await deezerAPI.searchTracks(query, 50);
// Mock data - replace with actual Deezer API implementation // Map Deezer API response to SearchResult format
const mockResults: SearchResult[] = [ if (!response.data || !Array.isArray(response.data)) {
{ console.warn('[searchDeezerTracks] No data in response:', response);
title: 'One More Time', return [];
artist: 'Daft Punk',
album: 'Discovery',
duration: 320,
source: 'online',
trackId: '3135556',
coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/250x250-000000-80-0-0.jpg'
},
{
title: 'Get Lucky',
artist: 'Daft Punk',
album: 'Random Access Memories',
duration: 369,
source: 'online',
trackId: '67238732',
coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/b0b1e82769d1a1e452fd3f2f95d3bb0f/250x250-000000-80-0-0.jpg'
},
{
title: 'Around the World',
artist: 'Daft Punk',
album: 'Homework',
duration: 429,
source: 'online',
trackId: '3135553',
coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/250x250-000000-80-0-0.jpg'
},
{
title: 'Harder, Better, Faster, Stronger',
artist: 'Daft Punk',
album: 'Discovery',
duration: 224,
source: 'online',
trackId: '3135554',
coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/250x250-000000-80-0-0.jpg'
},
{
title: 'Digital Love',
artist: 'Daft Punk',
album: 'Discovery',
duration: 301,
source: 'online',
trackId: '3135555',
coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/250x250-000000-80-0-0.jpg'
} }
];
// Filter mock results by query return response.data.map((track: any): SearchResult => ({
return mockResults.filter(result => title: track.title || 'Unknown Track',
result.title.toLowerCase().includes(query.toLowerCase()) || artist: track.artist?.name || 'Unknown Artist',
result.artist.toLowerCase().includes(query.toLowerCase()) || album: track.album?.title || 'Unknown Album',
result.album.toLowerCase().includes(query.toLowerCase()) duration: track.duration,
); source: 'online',
trackId: String(track.id),
coverArtUrl: track.album?.cover_medium || track.album?.cover_small
}));
} catch (error) {
console.error('[searchDeezerTracks] Search failed:', error);
throw error;
}
} }

View File

@@ -4,7 +4,8 @@
import SearchResultsTable from '$lib/components/SearchResultsTable.svelte'; import SearchResultsTable from '$lib/components/SearchResultsTable.svelte';
import { searchLocalTracks } from '$lib/library/search'; import { searchLocalTracks } from '$lib/library/search';
import { searchDeezerTracks } from '$lib/services/deezer/search'; import { searchDeezerTracks } from '$lib/services/deezer/search';
import { downloadTrack } from '$lib/services/deezer/downloader'; import { deezerAPI } from '$lib/services/deezer';
import { addDeezerTrackToQueue } from '$lib/services/deezer/addToQueue';
import { settings, loadSettings } from '$lib/stores/settings'; import { settings, loadSettings } from '$lib/stores/settings';
import { deezerAuth } from '$lib/stores/deezer'; import { deezerAuth } from '$lib/stores/deezer';
import type { SearchResult, SearchType } from '$lib/types/search'; import type { SearchResult, SearchType } from '$lib/types/search';
@@ -69,7 +70,7 @@
return; return;
} }
if (!$deezerAuth.user) { if (!$deezerAuth.arl || !$deezerAuth.user) {
error = 'Please log in to Deezer first (Services → Deezer)'; error = 'Please log in to Deezer first (Services → Deezer)';
return; return;
} }
@@ -80,11 +81,12 @@
} }
try { try {
console.log('Downloading track:', result.trackId); deezerAPI.setArl($deezerAuth.arl);
await downloadTrack(result.trackId); await addDeezerTrackToQueue(result.trackId);
// TODO: Show success notification error = null; // Clear any previous error
} catch (e) { } catch (e) {
error = 'Error downloading track: ' + (e as Error).message; console.error('Queue error:', e);
error = 'Error adding to download queue: ' + (e as Error).message;
} }
} }
</script> </script>

View File

@@ -3,7 +3,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { deezerAuth, loadDeezerAuth, saveArl, saveUser, clearDeezerAuth } from '$lib/stores/deezer'; import { deezerAuth, loadDeezerAuth, saveArl, saveUser, clearDeezerAuth } from '$lib/stores/deezer';
import { deezerAPI } from '$lib/services/deezer'; import { deezerAPI } from '$lib/services/deezer';
import { addToQueue } from '$lib/stores/downloadQueue'; import { addDeezerTrackToQueue } from '$lib/services/deezer/addToQueue';
import { settings } from '$lib/stores/settings'; import { settings } from '$lib/stores/settings';
let arlInput = $state(''); let arlInput = $state('');
@@ -129,35 +129,8 @@
queueError = ''; queueError = '';
try { try {
// Build track object // Use shared utility to add track to queue
const track = { await addDeezerTrackToQueue(trackIdInput);
id: trackInfo.SNG_ID,
title: trackInfo.SNG_TITLE,
artist: trackInfo.ART_NAME,
artistId: trackInfo.ART_ID,
artists: [trackInfo.ART_NAME],
album: trackInfo.ALB_TITLE,
albumId: trackInfo.ALB_ID,
albumArtist: trackInfo.ART_NAME,
albumArtistId: trackInfo.ART_ID,
trackNumber: trackInfo.TRACK_NUMBER || 1,
discNumber: trackInfo.DISK_NUMBER || 1,
duration: trackInfo.DURATION,
explicit: trackInfo.EXPLICIT_LYRICS === 1,
md5Origin: trackInfo.MD5_ORIGIN,
mediaVersion: trackInfo.MEDIA_VERSION,
trackToken: trackInfo.TRACK_TOKEN
};
// Add to queue
await addToQueue({
source: 'deezer',
type: 'track',
title: track.title,
artist: track.artist,
totalTracks: 1,
downloadObject: track
});
queueStatus = '✓ Added to download queue!'; queueStatus = '✓ Added to download queue!';