diff --git a/FEATURES.pdf b/FEATURES.pdf new file mode 100644 index 0000000..dc9200d Binary files /dev/null and b/FEATURES.pdf differ diff --git a/backend/app/services/bandcamp.py b/backend/app/services/bandcamp.py index 217fc3a..7695c37 100644 --- a/backend/app/services/bandcamp.py +++ b/backend/app/services/bandcamp.py @@ -32,21 +32,16 @@ async def search_bandcamp_verified(artist: str, title: str) -> dict | None: for r in results: artist_sim = _similarity(r.get("artist", ""), artist) title_sim = _similarity(r.get("title", ""), title) - # Require artist to be a strong match (>0.6) and title reasonable (>0.4) - if artist_sim >= 0.6 and title_sim >= 0.4: - return r - # Or if artist is very close, accept even if title differs (different track by same artist) - if artist_sim >= 0.8: + # Require artist to be a strong match (>0.75) AND title reasonable (>0.5) + if artist_sim >= 0.75 and title_sim >= 0.5: return r - # Try artist/band search as fallback + # Try artist/band search as fallback — very strict matching results = await search_bandcamp(artist, item_type="b") for r in results: - artist_sim = _similarity(r.get("title", ""), artist) # For band results, title IS the band name - if artist_sim >= 0.6: - return r - artist_sim = _similarity(r.get("artist", ""), artist) - if artist_sim >= 0.6: + # For band results, title IS the band name + name = r.get("title", "") or r.get("artist", "") + if _similarity(name, artist) >= 0.8: return r return None diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4533495..57712a4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,7 +11,6 @@ import PlaylistDetail from './pages/PlaylistDetail' import Discover from './pages/Discover' import Recommendations from './pages/Recommendations' import Billing from './pages/Billing' -import ListeningRoom from './pages/ListeningRoom' function RootRedirect() { const { user, loading } = useAuth() @@ -84,16 +83,6 @@ function AppRoutes() { } /> - - - - - - } - /> - {recommendation.bandcamp_url ? ( + {recommendation.bandcamp_url && ( - ) : ( - - - )} diff --git a/frontend/src/pages/ListeningRoom.tsx b/frontend/src/pages/ListeningRoom.tsx deleted file mode 100644 index ec8d964..0000000 --- a/frontend/src/pages/ListeningRoom.tsx +++ /dev/null @@ -1,346 +0,0 @@ -import { useState, useEffect } from 'react' -import { useSearchParams } from 'react-router-dom' -import { Search, Headphones, ExternalLink, Play, X, Music, Disc3 } from 'lucide-react' -import { searchBandcamp, getBandcampEmbed } from '../lib/api' -import type { BandcampResult, BandcampEmbed } from '../lib/api' - -type SearchType = 't' | 'a' | 'b' - -interface QueueItem { - result: BandcampResult - embed: BandcampEmbed | null - loading: boolean -} - -export default function ListeningRoom() { - const [searchParams] = useSearchParams() - const [query, setQuery] = useState(searchParams.get('q') || '') - const [searchType, setSearchType] = useState('t') - const [results, setResults] = useState([]) - const [searching, setSearching] = useState(false) - const [searchError, setSearchError] = useState('') - const [currentPlayer, setCurrentPlayer] = useState(null) - const [queue, setQueue] = useState([]) - - // Auto-search if query param is provided - useEffect(() => { - const q = searchParams.get('q') - if (q) { - setQuery(q) - handleSearch(q) - } - }, []) // eslint-disable-line react-hooks/exhaustive-deps - - async function handleSearch(searchQuery?: string) { - const q = (searchQuery || query).trim() - if (!q) return - - setSearching(true) - setSearchError('') - try { - const data = await searchBandcamp(q, searchType) - setResults(data) - if (data.length === 0) { - setSearchError('No results found. Try a different search term.') - } - } catch { - setSearchError('Search failed. Please try again.') - setResults([]) - } finally { - setSearching(false) - } - } - - async function handleListen(result: BandcampResult) { - const item: QueueItem = { result, embed: null, loading: true } - - // Set as current player immediately - setCurrentPlayer(item) - - try { - const embed = await getBandcampEmbed(result.bandcamp_url) - const loaded: QueueItem = { result, embed, loading: false } - setCurrentPlayer(loaded) - } catch { - setCurrentPlayer({ result, embed: null, loading: false }) - } - } - - function addToQueue(result: BandcampResult) { - // Don't add duplicates - if (queue.some((q) => q.result.bandcamp_url === result.bandcamp_url)) return - setQueue((prev) => [...prev, { result, embed: null, loading: false }]) - } - - async function playFromQueue(index: number) { - const item = queue[index] - setQueue((prev) => prev.filter((_, i) => i !== index)) - - setCurrentPlayer({ ...item, loading: true }) - try { - const embed = await getBandcampEmbed(item.result.bandcamp_url) - setCurrentPlayer({ result: item.result, embed, loading: false }) - } catch { - setCurrentPlayer({ result: item.result, embed: null, loading: false }) - } - } - - function removeFromQueue(index: number) { - setQueue((prev) => prev.filter((_, i) => i !== index)) - } - - const isAlbum = currentPlayer?.result.item_type === 'a' - const embedHeight = isAlbum ? 470 : 120 - - const typeLabels: Record = { t: 'Tracks', a: 'Albums', b: 'Artists' } - - return ( -
- {/* Header */} -
-
- -
-
-

Listening Room

-

- Discover and listen to music on Bandcamp -

-
-
- - {/* Search Section */} -
-
{ - e.preventDefault() - handleSearch() - }} - className="flex gap-3" - > -
- - setQuery(e.target.value)} - placeholder="Search Bandcamp..." - className="w-full pl-11 pr-4 py-3 rounded-xl border border-purple-100 focus:border-purple focus:ring-2 focus:ring-purple/20 outline-none text-charcoal placeholder:text-charcoal-muted/50" - /> -
- -
- - {/* Type Toggle */} -
- {(Object.entries(typeLabels) as [SearchType, string][]).map(([type, label]) => ( - - ))} -
-
- - {/* Search Results */} - {searchError && ( -
- -

{searchError}

-
- )} - - {results.length > 0 && ( -
-

- Results ({results.length}) -

-
- {results.map((result, i) => ( -
-
- {/* Art */} -
- {result.art_url ? ( - {result.title} - ) : ( - - )} -
- - {/* Info */} -
-

- {result.title} -

-

- {result.artist} -

- -
- - - - - -
-
-
-
- ))} -
-
- )} - - {/* Player Section */} - {(currentPlayer || queue.length > 0) && ( -
- {/* Now Playing */} - {currentPlayer && ( -
-
- -

Now Playing

-
- - {currentPlayer.loading ? ( -
-
-
- ) : currentPlayer.embed ? ( -
-
- {currentPlayer.embed.art_url && ( - {currentPlayer.embed.title} - )} -
-

- {currentPlayer.embed.title} -

-

- {currentPlayer.embed.artist} -

-
- - - Bandcamp - -
- -