mirror of
https://github.com/markuryy/shark.git
synced 2025-12-12 19:51:01 +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": "http://*.deezer.com/**"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.deezer.com/**"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://media.deezer.com/**"
|
"url": "https://media.deezer.com/**"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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', {
|
||||||
|
|||||||
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 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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!';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user