arr add: a root folder must be named when several exist; lidarr's lookup shows the artist's name
Sonarr's first root folder is /anime, so an unnamed add landed a series there. A profile is a preference and the first stands in; a root folder decides where data lands, so with several it is named or the refusal lists them. Lidarr's lookup used title where the field is artistName.
This commit is contained in:
@@ -106,7 +106,7 @@ export class LidarrClient {
|
||||
async lookup(term: string): Promise<Record<string, unknown>[]> {
|
||||
const found = (await this.get("artist/lookup", { term })) as Record<string, unknown>[];
|
||||
return (found ?? []).map((x) => ({
|
||||
title: x.title, year: x.year, foreignArtistId: x.foreignArtistId,
|
||||
title: x.artistName ?? x.title, year: x.year, foreignArtistId: x.foreignArtistId,
|
||||
overview: typeof x.overview === "string" ? x.overview.slice(0, 200) : undefined,
|
||||
inLibrary: typeof x.id === "number" && x.id > 0,
|
||||
}));
|
||||
@@ -139,15 +139,18 @@ export class LidarrClient {
|
||||
if (typeof record.id === "number" && record.id > 0) {
|
||||
return { added: false, alreadyInLibrary: true, id: record.id, title: record.title };
|
||||
}
|
||||
const pick = <T extends { id: number; name?: string; path?: string }>(list: T[], want: string | undefined, what: string): T => {
|
||||
// A profile is a preference, so the first stands in when none is named; a root folder decides
|
||||
// where the data lands, so with several it must be named — the refusal lists them.
|
||||
const pick = <T extends { id: number; name?: string; path?: string }>(list: T[], want: string | undefined, what: string, firstWillDo = true): T => {
|
||||
if (!list.length) throw new Error(`Lidarr has no ${what} configured`);
|
||||
if (!want) return list[0];
|
||||
if (!want && (firstWillDo || list.length === 1)) return list[0];
|
||||
if (!want) throw new Error(`Lidarr has ${list.length} ${what}s — name one: ${list.map((x) => x.name ?? x.path).join(", ")}`);
|
||||
const hit = list.find((x) => x.name === want || x.path === want);
|
||||
if (!hit) throw new Error(`Lidarr has no ${what} ${JSON.stringify(want)}; it has ${list.map((x) => x.name ?? x.path).join(", ")}`);
|
||||
return hit;
|
||||
};
|
||||
const quality = pick(await this.qualityProfiles(), opts.qualityProfile, "quality profile");
|
||||
const root = pick(await this.rootFolders(), opts.rootFolder, "root folder");
|
||||
const root = pick(await this.rootFolders(), opts.rootFolder, "root folder", false);
|
||||
const metadata = pick(await this.metadataProfiles(), opts.metadataProfile, "metadata profile");
|
||||
const search = opts.search ?? false;
|
||||
const body = {
|
||||
@@ -159,7 +162,7 @@ export class LidarrClient {
|
||||
addOptions: { monitor: "all", searchForMissingAlbums: search },
|
||||
};
|
||||
const made = (await this.send("POST", "artist", body)) as Record<string, unknown>;
|
||||
return { added: true, id: made.id, title: made.title, foreignArtistId: made.foreignArtistId, rootFolder: root.path, qualityProfile: quality.name, metadataProfile: metadata.name, monitored: body.monitored, searchRequested: search };
|
||||
return { added: true, id: made.id, title: made.artistName ?? made.title, foreignArtistId: made.foreignArtistId, rootFolder: root.path, qualityProfile: quality.name, metadataProfile: metadata.name, monitored: body.monitored, searchRequested: search };
|
||||
}
|
||||
|
||||
/** Remove a artist from the library by its Lidarr id. Files stay on disk unless `deleteFiles`. */
|
||||
|
||||
@@ -75,12 +75,12 @@ export function getLidarrTools(lidarr: LidarrClient): ToolDefinition[] {
|
||||
},
|
||||
{
|
||||
name: "lidarr_add",
|
||||
description: "Add a artist to Lidarr by its MusicBrainz artist id (from lidarr_lookup). Uses the first quality and metadata profile and root folder unless named. Does not search the indexers unless `search` is true.",
|
||||
description: "Add a artist to Lidarr by its MusicBrainz artist id (from lidarr_lookup). The first quality and metadata profiles stand in unless named; the root folder must be named when several exist. Does not search the indexers unless `search` is true.",
|
||||
input: {
|
||||
id: { type: "string", description: "the MusicBrainz artist id" },
|
||||
qualityProfile: { type: "string", description: "a quality profile's name (default: the first)" },
|
||||
metadataProfile: { type: "string", description: "a metadata profile's name (default: the first)" },
|
||||
rootFolder: { type: "string", description: "a root folder path (default: the first)" },
|
||||
rootFolder: { type: "string", description: "a root folder path (required when the app has several)" },
|
||||
monitored: { type: "boolean", description: "monitor it (default true)" },
|
||||
search: { type: "boolean", description: "search the indexers right away (default false)" },
|
||||
},
|
||||
|
||||
@@ -135,15 +135,18 @@ export class RadarrClient {
|
||||
if (typeof record.id === "number" && record.id > 0) {
|
||||
return { added: false, alreadyInLibrary: true, id: record.id, title: record.title };
|
||||
}
|
||||
const pick = <T extends { id: number; name?: string; path?: string }>(list: T[], want: string | undefined, what: string): T => {
|
||||
// A profile is a preference, so the first stands in when none is named; a root folder decides
|
||||
// where the data lands, so with several it must be named — the refusal lists them.
|
||||
const pick = <T extends { id: number; name?: string; path?: string }>(list: T[], want: string | undefined, what: string, firstWillDo = true): T => {
|
||||
if (!list.length) throw new Error(`Radarr has no ${what} configured`);
|
||||
if (!want) return list[0];
|
||||
if (!want && (firstWillDo || list.length === 1)) return list[0];
|
||||
if (!want) throw new Error(`Radarr has ${list.length} ${what}s — name one: ${list.map((x) => x.name ?? x.path).join(", ")}`);
|
||||
const hit = list.find((x) => x.name === want || x.path === want);
|
||||
if (!hit) throw new Error(`Radarr has no ${what} ${JSON.stringify(want)}; it has ${list.map((x) => x.name ?? x.path).join(", ")}`);
|
||||
return hit;
|
||||
};
|
||||
const quality = pick(await this.qualityProfiles(), opts.qualityProfile, "quality profile");
|
||||
const root = pick(await this.rootFolders(), opts.rootFolder, "root folder");
|
||||
const root = pick(await this.rootFolders(), opts.rootFolder, "root folder", false);
|
||||
|
||||
const search = opts.search ?? false;
|
||||
const body = {
|
||||
|
||||
@@ -75,12 +75,12 @@ export function getRadarrTools(radarr: RadarrClient): ToolDefinition[] {
|
||||
},
|
||||
{
|
||||
name: "radarr_add",
|
||||
description: "Add a movie to Radarr by its TMDb id (from radarr_lookup). Uses the first quality profile and root folder unless named. Does not search the indexers unless `search` is true.",
|
||||
description: "Add a movie to Radarr by its TMDb id (from radarr_lookup). The first quality profile stands in unless named; the root folder must be named when several exist. Does not search the indexers unless `search` is true.",
|
||||
input: {
|
||||
id: { type: "string", description: "the TMDb id" },
|
||||
qualityProfile: { type: "string", description: "a quality profile's name (default: the first)" },
|
||||
|
||||
rootFolder: { type: "string", description: "a root folder path (default: the first)" },
|
||||
rootFolder: { type: "string", description: "a root folder path (required when the app has several)" },
|
||||
monitored: { type: "boolean", description: "monitor it (default true)" },
|
||||
search: { type: "boolean", description: "search the indexers right away (default false)" },
|
||||
},
|
||||
|
||||
@@ -135,15 +135,18 @@ export class SonarrClient {
|
||||
if (typeof record.id === "number" && record.id > 0) {
|
||||
return { added: false, alreadyInLibrary: true, id: record.id, title: record.title };
|
||||
}
|
||||
const pick = <T extends { id: number; name?: string; path?: string }>(list: T[], want: string | undefined, what: string): T => {
|
||||
// A profile is a preference, so the first stands in when none is named; a root folder decides
|
||||
// where the data lands, so with several it must be named — the refusal lists them.
|
||||
const pick = <T extends { id: number; name?: string; path?: string }>(list: T[], want: string | undefined, what: string, firstWillDo = true): T => {
|
||||
if (!list.length) throw new Error(`Sonarr has no ${what} configured`);
|
||||
if (!want) return list[0];
|
||||
if (!want && (firstWillDo || list.length === 1)) return list[0];
|
||||
if (!want) throw new Error(`Sonarr has ${list.length} ${what}s — name one: ${list.map((x) => x.name ?? x.path).join(", ")}`);
|
||||
const hit = list.find((x) => x.name === want || x.path === want);
|
||||
if (!hit) throw new Error(`Sonarr has no ${what} ${JSON.stringify(want)}; it has ${list.map((x) => x.name ?? x.path).join(", ")}`);
|
||||
return hit;
|
||||
};
|
||||
const quality = pick(await this.qualityProfiles(), opts.qualityProfile, "quality profile");
|
||||
const root = pick(await this.rootFolders(), opts.rootFolder, "root folder");
|
||||
const root = pick(await this.rootFolders(), opts.rootFolder, "root folder", false);
|
||||
|
||||
const search = opts.search ?? false;
|
||||
const body = {
|
||||
|
||||
@@ -75,12 +75,12 @@ export function getSonarrTools(sonarr: SonarrClient): ToolDefinition[] {
|
||||
},
|
||||
{
|
||||
name: "sonarr_add",
|
||||
description: "Add a series to Sonarr by its TVDb id (from sonarr_lookup). Uses the first quality profile and root folder unless named. Does not search the indexers unless `search` is true.",
|
||||
description: "Add a series to Sonarr by its TVDb id (from sonarr_lookup). The first quality profile stands in unless named; the root folder must be named when several exist. Does not search the indexers unless `search` is true.",
|
||||
input: {
|
||||
id: { type: "string", description: "the TVDb id" },
|
||||
qualityProfile: { type: "string", description: "a quality profile's name (default: the first)" },
|
||||
|
||||
rootFolder: { type: "string", description: "a root folder path (default: the first)" },
|
||||
rootFolder: { type: "string", description: "a root folder path (required when the app has several)" },
|
||||
monitored: { type: "boolean", description: "monitor it (default true)" },
|
||||
search: { type: "boolean", description: "search the indexers right away (default false)" },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user