mirror of
https://github.com/markuryy/shark.git
synced 2025-12-12 11:41:02 +00:00
feat(dl): online track search and add-to-queue utility
This commit is contained in:
@@ -44,6 +44,9 @@
|
||||
{
|
||||
"url": "http://*.deezer.com/**"
|
||||
},
|
||||
{
|
||||
"url": "https://api.deezer.com/**"
|
||||
},
|
||||
{
|
||||
"url": "https://media.deezer.com/**"
|
||||
},
|
||||
|
||||
@@ -250,6 +250,35 @@ export class DeezerAPI {
|
||||
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
|
||||
async getPlaylist(playlistId: string): Promise<any> {
|
||||
return this.apiCall('deezer.pagePlaylist', {
|
||||
|
||||
51
src/lib/services/deezer/addToQueue.ts
Normal file
51
src/lib/services/deezer/addToQueue.ts
Normal 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
|
||||
});
|
||||
}
|
||||
@@ -1,71 +1,34 @@
|
||||
import type { SearchResult } from '$lib/types/search';
|
||||
import { deezerAPI } from '../deezer';
|
||||
|
||||
/**
|
||||
* Search Deezer for tracks matching the query
|
||||
* TODO: Implement actual Deezer search API
|
||||
* For now, returns mock data
|
||||
* Search Deezer for tracks using the public API
|
||||
*/
|
||||
export async function searchDeezerTracks(query: string): Promise<SearchResult[]> {
|
||||
if (!query.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
try {
|
||||
const response = await deezerAPI.searchTracks(query, 50);
|
||||
|
||||
// Mock data - replace with actual Deezer API implementation
|
||||
const mockResults: SearchResult[] = [
|
||||
{
|
||||
title: 'One More Time',
|
||||
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'
|
||||
// Map Deezer API response to SearchResult format
|
||||
if (!response.data || !Array.isArray(response.data)) {
|
||||
console.warn('[searchDeezerTracks] No data in response:', response);
|
||||
return [];
|
||||
}
|
||||
];
|
||||
|
||||
// Filter mock results by query
|
||||
return mockResults.filter(result =>
|
||||
result.title.toLowerCase().includes(query.toLowerCase()) ||
|
||||
result.artist.toLowerCase().includes(query.toLowerCase()) ||
|
||||
result.album.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
return response.data.map((track: any): SearchResult => ({
|
||||
title: track.title || 'Unknown Track',
|
||||
artist: track.artist?.name || 'Unknown Artist',
|
||||
album: track.album?.title || 'Unknown Album',
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import SearchResultsTable from '$lib/components/SearchResultsTable.svelte';
|
||||
import { searchLocalTracks } from '$lib/library/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 { deezerAuth } from '$lib/stores/deezer';
|
||||
import type { SearchResult, SearchType } from '$lib/types/search';
|
||||
@@ -69,7 +70,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$deezerAuth.user) {
|
||||
if (!$deezerAuth.arl || !$deezerAuth.user) {
|
||||
error = 'Please log in to Deezer first (Services → Deezer)';
|
||||
return;
|
||||
}
|
||||
@@ -80,11 +81,12 @@
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Downloading track:', result.trackId);
|
||||
await downloadTrack(result.trackId);
|
||||
// TODO: Show success notification
|
||||
deezerAPI.setArl($deezerAuth.arl);
|
||||
await addDeezerTrackToQueue(result.trackId);
|
||||
error = null; // Clear any previous error
|
||||
} 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>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { deezerAuth, loadDeezerAuth, saveArl, saveUser, clearDeezerAuth } from '$lib/stores/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';
|
||||
|
||||
let arlInput = $state('');
|
||||
@@ -129,35 +129,8 @@
|
||||
queueError = '';
|
||||
|
||||
try {
|
||||
// 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
|
||||
});
|
||||
// Use shared utility to add track to queue
|
||||
await addDeezerTrackToQueue(trackIdInput);
|
||||
|
||||
queueStatus = '✓ Added to download queue!';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user