The library search returned title, year, status and monitored. It now returns every field the app keeps (file on disk, quality, size, path, counts, ratings, genres, added…), and each module binds plex-api so the first ten hits carry Plex's view: rating key, library, resolution, added, watched, and a link that opens the item in Plex.
116 lines
4.8 KiB
TypeScript
116 lines
4.8 KiB
TypeScript
// sonarr's tools — ported from the shared hal sdk (novox/hq ADR 0039), importing sonarr's own
|
|
// client. They return structured data (not pre-formatted text as hal did); the mesh serves them
|
|
// through the sdk's tool harness.
|
|
|
|
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
|
import { SonarrClient } from "../client.js";
|
|
|
|
export function getSonarrTools(sonarr: SonarrClient): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: "sonarr_status",
|
|
description: "Sonarr status overview: version, series count, monitored count, queue size.",
|
|
input: {},
|
|
run: async () => {
|
|
const [status, content, queue] = await Promise.all([
|
|
sonarr.getStatus(),
|
|
sonarr.getContent(),
|
|
sonarr.getQueue(),
|
|
]);
|
|
return {
|
|
app: status.appName,
|
|
version: status.version,
|
|
series: content.length,
|
|
monitored: content.filter((c) => c.monitored).length,
|
|
queue: queue.totalRecords,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
name: "sonarr_library",
|
|
description: "List series from the Sonarr library.",
|
|
input: { limit: { type: "number", description: "max items to return (default 50)" } },
|
|
run: async (args) => {
|
|
const items = await sonarr.getContent(args.limit ? Number(args.limit) : 50);
|
|
return { count: items.length, series: items };
|
|
},
|
|
},
|
|
{
|
|
name: "sonarr_search",
|
|
description: "Search the Sonarr library (every field Sonarr keeps, plus where each of the first ten hits is in Plex: quality, added, watched, link) for series by title (filters existing content, not indexers).",
|
|
input: { query: { type: "string", description: "the search term" } },
|
|
run: async (args) => {
|
|
const query = String(args.query);
|
|
return { query, results: await sonarr.searchContent(query) };
|
|
},
|
|
},
|
|
{
|
|
name: "sonarr_queue",
|
|
description: "Show the Sonarr download queue — what is downloading and how far along.",
|
|
input: {},
|
|
run: async () => {
|
|
const queue = await sonarr.getQueue();
|
|
return { count: queue.totalRecords, items: queue.items };
|
|
},
|
|
},
|
|
{
|
|
name: "sonarr_calendar",
|
|
description: "Upcoming episode releases from the Sonarr calendar.",
|
|
input: { days: { type: "number", description: "how many days to look ahead (default 7)" } },
|
|
run: async (args) => {
|
|
const days = args.days ? Number(args.days) : 7;
|
|
const items = await sonarr.getCalendar(days);
|
|
items.sort((a, b) => a.date.localeCompare(b.date));
|
|
return { days, count: items.length, items };
|
|
},
|
|
},
|
|
{
|
|
name: "sonarr_lookup",
|
|
description: "Look a series up outside the library, on Sonarr's metadata source: title, year, TVDb id and whether it is already in the library.",
|
|
input: { query: { type: "string", description: "a title, or `tvdb:<id>`" } },
|
|
run: async (args) => {
|
|
const query = String(args.query);
|
|
return { query, results: await sonarr.lookup(query) };
|
|
},
|
|
},
|
|
{
|
|
name: "sonarr_add",
|
|
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 (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)" },
|
|
},
|
|
run: async (args) => sonarr.add(String(args.id), {
|
|
qualityProfile: args.qualityProfile ? String(args.qualityProfile) : undefined,
|
|
|
|
rootFolder: args.rootFolder ? String(args.rootFolder) : undefined,
|
|
monitored: args.monitored === undefined ? undefined : Boolean(args.monitored),
|
|
search: Boolean(args.search),
|
|
}),
|
|
},
|
|
{
|
|
name: "sonarr_remove",
|
|
description: "Remove a series from Sonarr's library by its Sonarr id. Files stay on disk unless `deleteFiles` is true.",
|
|
input: {
|
|
id: { type: "number", description: "the Sonarr id of the series" },
|
|
deleteFiles: { type: "boolean", description: "also delete its files (default false)" },
|
|
},
|
|
run: async (args) => sonarr.remove(Number(args.id), Boolean(args.deleteFiles)),
|
|
},
|
|
];
|
|
}
|
|
|
|
// The tools exist only when Sonarr is configured; without a URL and key, sonarr contributes none
|
|
// rather than failing the whole runtime.
|
|
registerModuleTools("sonarr", (env) => {
|
|
try {
|
|
return getSonarrTools(SonarrClient.fromEnv(env));
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|