diff --git a/src/routes/subsonic.js b/src/routes/subsonic.js
index 1881266..09d4250 100644
--- a/src/routes/subsonic.js
+++ b/src/routes/subsonic.js
@@ -726,27 +726,211 @@ router.get('/getSong.view', authenticateSubsonic, async (req, res) => {
// Search
router.get('/search3.view', authenticateSubsonic, async (req, res) => {
try {
- const { query } = req.query;
+ const { query, artistCount, albumCount, songCount } = req.query;
const searchTerm = `%${query}%`;
+ const maxArtists = parseInt(artistCount) || 20;
+ const maxAlbums = parseInt(albumCount) || 20;
+ const maxSongs = parseInt(songCount) || 20;
+ // Search artists
+ const artists = await all(`
+ SELECT DISTINCT artist,
+ (SELECT artwork_path FROM tracks t2 WHERE t2.artist = tracks.artist AND artwork_path IS NOT NULL LIMIT 1) as artwork_path
+ FROM tracks
+ WHERE artist LIKE ?
+ LIMIT ?
+ `, [searchTerm, maxArtists]);
+
+ // Search albums
+ const albums = await all(`
+ SELECT DISTINCT album, album_artist, year,
+ (SELECT artwork_path FROM tracks t2 WHERE t2.album = tracks.album LIMIT 1) as artwork_path
+ FROM tracks
+ WHERE album LIKE ? OR album_artist LIKE ?
+ LIMIT ?
+ `, [searchTerm, searchTerm, maxAlbums]);
+
+ // Search songs
const tracks = await all(`
SELECT * FROM tracks
WHERE title LIKE ? OR artist LIKE ? OR album LIKE ?
- LIMIT 20
- `, [searchTerm, searchTerm, searchTerm]);
+ LIMIT ?
+ `, [searchTerm, searchTerm, searchTerm, maxSongs]);
let searchXml = '\n';
+
+ // Add artists
+ artists.forEach(artist => {
+ const artistId = crypto.createHash('md5').update(`artist-${artist.artist}`).digest('hex');
+ const coverArt = artist.artwork_path ? artistId : '';
+ searchXml += ` \n`;
+ });
+
+ // Add albums
+ albums.forEach(album => {
+ const albumId = crypto.createHash('md5').update(`album-${album.album}-${album.album_artist}`).digest('hex');
+ const artistId = crypto.createHash('md5').update(`artist-${album.album_artist}`).digest('hex');
+ const coverArt = album.artwork_path ? albumId : '';
+ searchXml += ` \n`;
+ });
+
+ // Add songs
tracks.forEach(track => {
searchXml += buildSongXml(track, req);
});
+
searchXml += '';
sendSubsonicResponse(res, searchXml);
} catch (error) {
+ console.error('Error in search3:', error);
sendSubsonicError(res, 0, error.message);
}
});
+// Scrobble (mark as played)
+router.get('/scrobble.view', authenticateSubsonic, async (req, res) => {
+ // Just acknowledge - we're not tracking play counts yet
+ sendSubsonicResponse(res, '');
+});
+
+// Star/unstar items
+router.get('/star.view', authenticateSubsonic, async (req, res) => {
+ // Acknowledge - favorites not implemented yet
+ sendSubsonicResponse(res, '');
+});
+
+router.get('/unstar.view', authenticateSubsonic, async (req, res) => {
+ // Acknowledge - favorites not implemented yet
+ sendSubsonicResponse(res, '');
+});
+
+// Get starred items
+router.get('/getStarred.view', authenticateSubsonic, async (req, res) => {
+ sendSubsonicResponse(res, '');
+});
+
+router.get('/getStarred2.view', authenticateSubsonic, async (req, res) => {
+ sendSubsonicResponse(res, '');
+});
+
+// Get user
+router.get('/getUser.view', authenticateSubsonic, async (req, res) => {
+ const { username } = req.query;
+ sendSubsonicResponse(res, `
+
+ `);
+});
+
+// Get now playing
+router.get('/getNowPlaying.view', authenticateSubsonic, async (req, res) => {
+ sendSubsonicResponse(res, '');
+});
+
+// Get similar songs
+router.get('/getSimilarSongs.view', authenticateSubsonic, async (req, res) => {
+ try {
+ const { id, count } = req.query;
+ const limit = parseInt(count) || 50;
+
+ // Get the track to find similar songs
+ const track = await get('SELECT * FROM tracks WHERE id = ?', [id]);
+
+ if (!track) {
+ return sendSubsonicResponse(res, '');
+ }
+
+ // Find similar songs by same artist or genre
+ const similar = await all(`
+ SELECT * FROM tracks
+ WHERE (artist = ? OR genre = ?) AND id != ?
+ ORDER BY RANDOM()
+ LIMIT ?
+ `, [track.artist, track.genre, id, limit]);
+
+ let similarXml = '\n';
+ similar.forEach(t => {
+ similarXml += buildSongXml(t, req);
+ });
+ similarXml += '';
+
+ sendSubsonicResponse(res, similarXml);
+ } catch (error) {
+ console.error('Error in getSimilarSongs:', error);
+ sendSubsonicResponse(res, '');
+ }
+});
+
+router.get('/getSimilarSongs2.view', authenticateSubsonic, async (req, res) => {
+ try {
+ const { id, count } = req.query;
+ const limit = parseInt(count) || 50;
+
+ // Get the track to find similar songs
+ const track = await get('SELECT * FROM tracks WHERE id = ?', [id]);
+
+ if (!track) {
+ return sendSubsonicResponse(res, '');
+ }
+
+ // Find similar songs by same artist or genre
+ const similar = await all(`
+ SELECT * FROM tracks
+ WHERE (artist = ? OR genre = ?) AND id != ?
+ ORDER BY RANDOM()
+ LIMIT ?
+ `, [track.artist, track.genre, id, limit]);
+
+ let similarXml = '\n';
+ similar.forEach(t => {
+ similarXml += buildSongXml(t, req);
+ });
+ similarXml += '';
+
+ sendSubsonicResponse(res, similarXml);
+ } catch (error) {
+ console.error('Error in getSimilarSongs2:', error);
+ sendSubsonicResponse(res, '');
+ }
+});
+
+// Get top songs
+router.get('/getTopSongs.view', authenticateSubsonic, async (req, res) => {
+ try {
+ const { artist, count } = req.query;
+ const limit = parseInt(count) || 50;
+
+ const tracks = await all(`
+ SELECT * FROM tracks
+ WHERE artist = ?
+ ORDER BY RANDOM()
+ LIMIT ?
+ `, [artist, limit]);
+
+ let topSongsXml = '\n';
+ tracks.forEach(track => {
+ topSongsXml += buildSongXml(track, req);
+ });
+ topSongsXml += '';
+
+ sendSubsonicResponse(res, topSongsXml);
+ } catch (error) {
+ console.error('Error in getTopSongs:', error);
+ sendSubsonicResponse(res, '');
+ }
+});
+
// Helper to build song XML
function buildSongXml(track, req) {
const albumId = crypto.createHash('md5').update(`album-${track.album}-${track.album_artist}`).digest('hex');