Files
requestarr/server/routes/music.ts
root 1cf0d541d6 Add Lidarr/Readarr backend support
- Add MUSIC and BOOK to MediaType enum
- Add permission flags for music/book requests
- Create Lidarr API adapter (artist/album search, add, remove)
- Create Readarr API adapter (book/author search, add, remove)
- Add Lidarr/Readarr settings interfaces and routes
- Add music and book API routes for search/detail
- Register all new routes in main router and settings router
2026-04-03 21:05:21 -05:00

136 lines
3.7 KiB
TypeScript

import LidarrAPI from '@server/api/servarr/lidarr';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { Router } from 'express';
const musicRoutes = Router();
musicRoutes.get('/search', async (req, res, next) => {
const { query } = req.query;
if (!query || typeof query !== 'string') {
return res.status(400).json({ error: 'Query parameter is required' });
}
try {
const settings = getSettings();
const lidarrSettings = settings.lidarr.find((l) => l.isDefault);
if (!lidarrSettings) {
return res.status(404).json({ error: 'No default Lidarr server configured' });
}
const lidarr = new LidarrAPI({
apiKey: lidarrSettings.apiKey,
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
});
const artists = await lidarr.searchArtist(query);
const results = artists.slice(0, 20).map((artist) => ({
id: artist.foreignArtistId,
mediaType: 'music',
name: artist.artistName,
artistType: artist.artistType,
disambiguation: artist.disambiguation,
status: artist.status,
images: artist.images,
genres: artist.genres,
foreignArtistId: artist.foreignArtistId,
statistics: artist.statistics,
inLibrary: !!artist.id && artist.id > 0,
}));
return res.status(200).json({
results,
totalResults: results.length,
});
} catch (e) {
logger.error('Failed to search music', {
label: 'Music API',
message: e.message,
});
next({ status: 500, message: 'Failed to search music' });
}
});
musicRoutes.get('/artist/:mbId', async (req, res, next) => {
try {
const settings = getSettings();
const lidarrSettings = settings.lidarr.find((l) => l.isDefault);
if (!lidarrSettings) {
return res.status(404).json({ error: 'No default Lidarr server configured' });
}
const lidarr = new LidarrAPI({
apiKey: lidarrSettings.apiKey,
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
});
// Search by MusicBrainz ID
const artists = await lidarr.searchArtist(`lidarr:${req.params.mbId}`);
const artist = artists[0];
if (!artist) {
return res.status(404).json({ error: 'Artist not found' });
}
// Get albums if artist is in library
let albums: any[] = [];
const existingArtist = await lidarr.getArtistByMbId(req.params.mbId);
if (existingArtist) {
albums = await lidarr.getAlbums(existingArtist.id);
}
return res.status(200).json({
...artist,
albums,
inLibrary: !!existingArtist,
});
} catch (e) {
logger.error('Failed to get artist details', {
label: 'Music API',
message: e.message,
});
next({ status: 500, message: 'Failed to get artist details' });
}
});
musicRoutes.get('/album/search', async (req, res, next) => {
const { query } = req.query;
if (!query || typeof query !== 'string') {
return res.status(400).json({ error: 'Query parameter is required' });
}
try {
const settings = getSettings();
const lidarrSettings = settings.lidarr.find((l) => l.isDefault);
if (!lidarrSettings) {
return res.status(404).json({ error: 'No default Lidarr server configured' });
}
const lidarr = new LidarrAPI({
apiKey: lidarrSettings.apiKey,
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
});
const albums = await lidarr.searchAlbum(query);
return res.status(200).json({
results: albums.slice(0, 20),
totalResults: albums.length,
});
} catch (e) {
logger.error('Failed to search albums', {
label: 'Music API',
message: e.message,
});
next({ status: 500, message: 'Failed to search albums' });
}
});
export default musicRoutes;