From 5339ced26ee4f5596f23a10cc93e8879efddc9e6 Mon Sep 17 00:00:00 2001 From: Eduard Marbach Date: Thu, 27 Jun 2024 23:53:24 +0200 Subject: [PATCH] feat: make Trash template / QualityProfiles includable --- index.ts | 64 ++++++++++++++++++++++++++++++++---- src/trash-guide.test.ts | 10 ++++++ src/trash-guide.ts | 60 ++++++++++++++++++++++++++++++++-- src/types.ts | 72 ++++++++++++++++++++++++++++++++++++++++- src/util.ts | 6 ++-- 5 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 src/trash-guide.test.ts diff --git a/index.ts b/index.ts index 15f9c24..71afa66 100644 --- a/index.ts +++ b/index.ts @@ -21,13 +21,29 @@ import { mapQualityProfiles, } from "./src/quality-profiles"; import { cloneRecyclarrTemplateRepo, loadRecyclarrTemplates } from "./src/recyclarr-importer"; -import { cloneTrashRepo, loadQualityDefinitionSonarrFromTrash, loadSonarrTrashCFs } from "./src/trash-guide"; -import { ArrType, ConfigArrInstance, ConfigQualityProfile, MappedMergedTemplates, TrashQualityDefintion } from "./src/types"; +import { + cloneTrashRepo, + loadQPFromTrash, + loadQualityDefinitionSonarrFromTrash, + loadSonarrTrashCFs, + transformTrashQPCFs, + transformTrashQPToTemplate, +} from "./src/trash-guide"; +import { + ArrType, + ConfigArrInstance, + ConfigQualityProfile, + MappedMergedTemplates, + TrashQualityDefintion, + YamlConfigIncludeRecyclarr, + YamlConfigIncludeTrash, +} from "./src/types"; import { DEBUG_CREATE_FILES, IS_DRY_RUN } from "./src/util"; const pipeline = async (value: ConfigArrInstance, arrType: ArrType) => { const api = getArrApi(); const recyclarrTemplateMap = loadRecyclarrTemplates(arrType); + const trashTemplates = await loadQPFromTrash(arrType); const recylarrMergedTemplates: MappedMergedTemplates = { custom_formats: [], @@ -35,13 +51,34 @@ const pipeline = async (value: ConfigArrInstance, arrType: ArrType) => { }; if (value.include) { - logger.info(`Recyclarr includes ${value.include.length} templates`); - logger.debug( - value.include.map((e) => e.template), - "Included templates", + logger.info(`Found ${value.include.length} templates to include ...`); + + const mappedIncludes = value.include.reduce<{ recyclarr: YamlConfigIncludeRecyclarr[]; trash: YamlConfigIncludeTrash[] }>( + (previous, current) => { + if (current.type == null) { + previous.recyclarr.push(current as YamlConfigIncludeRecyclarr); + } else { + switch (current.type) { + case "TRASH": + previous.trash.push(current); + break; + case "RECYCLARR": + previous.recyclarr.push(current as YamlConfigIncludeRecyclarr); + break; + default: + logger.warn(`Unknown type for template requested: ${(current as any).type}. Ignoring.`); + } + } + + return previous; + }, + { recyclarr: [], trash: [] }, ); - value.include.forEach((e) => { + logger.debug(mappedIncludes.recyclarr, `Included ${mappedIncludes.recyclarr.length} templates [recyclarr]`); + logger.debug(mappedIncludes.trash, `Included ${mappedIncludes.trash.length} templates [trash]`); + + mappedIncludes.recyclarr.forEach((e) => { const template = recyclarrTemplateMap.get(e.template); if (!template) { @@ -65,6 +102,18 @@ const pipeline = async (value: ConfigArrInstance, arrType: ArrType) => { // TODO Ignore recursive include for now }); + + mappedIncludes.trash.forEach((e) => { + const template = trashTemplates.get(e.id); + + if (!template) { + logger.info(`Unknown trash template requested: ${e.id}`); + return; + } + + recylarrMergedTemplates.quality_profiles.push(transformTrashQPToTemplate(template)); + recylarrMergedTemplates.custom_formats.push(transformTrashQPCFs(template)); + }); } if (value.custom_formats) { @@ -103,6 +152,7 @@ const pipeline = async (value: ConfigArrInstance, arrType: ArrType) => { recylarrMergedTemplates.quality_profiles = filterInvalidQualityProfiles(recylarrMergedTemplates.quality_profiles); const trashCFs = await loadSonarrTrashCFs(arrType); + const localFileCFs = await loadLocalCfs(); const configCFDefinition = loadCFFromConfig(); const mergedCFs = mergeCfSources([trashCFs, localFileCFs, configCFDefinition]); diff --git a/src/trash-guide.test.ts b/src/trash-guide.test.ts new file mode 100644 index 0000000..8fb2774 --- /dev/null +++ b/src/trash-guide.test.ts @@ -0,0 +1,10 @@ +import { describe, test } from "vitest"; +import { loadQPFromTrash } from "./trash-guide"; + +describe("TrashGuide", async () => { + test("loadQPFromTrash - normal", async ({}) => { + const results = await loadQPFromTrash("RADARR"); + + console.log(results.keys()); + }); +}); diff --git a/src/trash-guide.ts b/src/trash-guide.ts index 993f97e..fbb345e 100644 --- a/src/trash-guide.ts +++ b/src/trash-guide.ts @@ -8,12 +8,16 @@ import { ArrType, CFProcessing, ConfigarrCF, + ConfigCustomFormat, QualityDefintionsRadarr, QualityDefintionsSonarr, TrashCF, + TrashQP, TrashQualityDefintion, + YamlConfigQualityProfile, + YamlConfigQualityProfileItems, } from "./types"; -import { loadJsonFile, mapImportCfToRequestCf, toCarrCF, trashRepoPaths } from "./util"; +import { loadJsonFile, mapImportCfToRequestCf, notEmpty, toCarrCF, trashRepoPaths } from "./util"; const DEFAULT_TRASH_GIT_URL = "https://github.com/TRaSH-Guides/Guides"; @@ -98,7 +102,7 @@ export const loadQualityDefinitionSonarrFromTrash = async ( qdType: QualityDefintionsSonarr | QualityDefintionsRadarr, arrType: ArrType, ): Promise => { - let trashPath = arrType === "RADARR" ? trashRepoPaths.radarrQuality : trashRepoPaths.sonarrQuality; + let trashPath = arrType === "RADARR" ? trashRepoPaths.radarrQualitySize : trashRepoPaths.sonarrQualitySize; switch (qdType) { case "anime": @@ -113,3 +117,55 @@ export const loadQualityDefinitionSonarrFromTrash = async ( throw new Error(`Unknown QualityDefintion type: ${qdType}`); } }; + +export const loadQPFromTrash = async (arrType: ArrType) => { + let trashPath = arrType === "RADARR" ? trashRepoPaths.radarrQP : trashRepoPaths.sonarrQP; + const map = new Map(); + + try { + const files = fs.readdirSync(`${trashPath}`).filter((fn) => fn.endsWith("json")); + + if (files.length <= 0) { + logger.info(`Not found any TrashGuide QualityProfiles. Skipping.`); + } + + for (const item of files) { + const importTrashQP = loadJsonFile(`${trashPath}/${item}`); + + map.set(importTrashQP.trash_id, importTrashQP); + } + } catch (err: any) { + logger.warn("Failed loading TrashGuide QualityProfiles. Continue without ...", err?.message); + } + + // const localPath = getLocalTemplatePath(); + + // if (localPath) { + // fillMap(localPath); + // } + + return map; +}; + +export const transformTrashQPToTemplate = (data: TrashQP): YamlConfigQualityProfile => { + return { + min_format_score: data.minFormatScore, + score_set: data.trash_score_set, + upgrade: { allowed: data.upgradeAllowed, until_quality: data.cutoff, until_score: data.cutoffFormatScore }, + name: data.name, + qualities: data.items + .map((e): YamlConfigQualityProfileItems | null => { + if (!e.allowed) { + return null; + } + return { name: e.name, qualities: e.items }; + }) + .filter(notEmpty) + .toReversed(), + quality_sort: "top", // default + }; +}; + +export const transformTrashQPCFs = (data: TrashQP): ConfigCustomFormat => { + return { assign_scores_to: [{ name: data.name }], trash_ids: Object.values(data.formatItems) }; +}; diff --git a/src/types.ts b/src/types.ts index b597c6a..2c15efa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,6 +64,26 @@ export type TrashCF = { trash_description?: string; } & ImportCF; +type TrashQPItem = { + name: string; + allowed: boolean; + items?: string[]; +}; + +export type TrashQP = { + trash_id: string; + name: string; + trash_score_set: keyof TrashCF["trash_scores"]; + upgradeAllowed: boolean; + cutoff: string; + minFormatScore: number; + cutoffFormatScore: number; + items: TrashQPItem[]; + formatItems: { + [key: string]: string; + }; +}; + export type ConfigarrCF = { configarr_id: string; configarr_scores?: TrashCF["trash_scores"]; @@ -100,6 +120,9 @@ export type ConfigSchema = InputConfigSchema & { export type InputConfigCustomFormat = { trash_ids?: string[]; + /** + * @deprecated replaced with assign_scores_to + */ quality_profiles?: { name: string; score?: number }[]; assign_scores_to?: { name: string; score?: number }[]; }; @@ -114,7 +137,7 @@ export type ConfigArrInstance = { quality_definition?: { type: string; }; - include?: { template: string }[]; + include?: YamlConfigIncludeItem[]; custom_formats: ConfigCustomFormat[]; quality_profiles: ConfigQualityProfile[]; }; @@ -166,6 +189,53 @@ export type RecyclarrConfigInstance = Omit custom_formats: RecyclarrCustomFormats[]; }; +// TODO +export type YamlConfigIncludeRecyclarr = { + template: string; + type: "RECYCLARR"; +}; + +export type YamlConfigIncludeTrash = { + // TODO or use template? + id: string; + type: "TRASH"; +}; +export type YamlConfigIncludeItem = YamlConfigIncludeRecyclarr | YamlConfigIncludeTrash; + +export type YamlConfigInstance = { + base_url: string; + api_key: string; + quality_definition?: { + type: string; + }; + include?: YamlConfigIncludeItem[]; + custom_formats: InputConfigCustomFormat[]; + quality_profiles: YamlConfigQualityProfile[]; +}; + +export type YamlConfigQualityProfile = { + name: string; + reset_unmatched_scores?: { + enabled: boolean; + except?: string[]; + }; + upgrade: { + allowed: boolean; + until_quality: string; + until_score: number; + }; + min_format_score: number; + score_set: keyof TrashCF["trash_scores"]; + quality_sort: "top" | "bottom"; + qualities: YamlConfigQualityProfileItems[]; +}; + +export type YamlConfigQualityProfileItems = { + name: string; + qualities?: string[]; +}; +// END TODO + export type RecyclarrTemplates = Partial< Pick >; diff --git a/src/util.ts b/src/util.ts index d2b1e8c..a2cddcb 100644 --- a/src/util.ts +++ b/src/util.ts @@ -29,10 +29,12 @@ const trashRepoRadarrRoot = `${trashRepoRoot}/${trashRepoPath}/radarr`; export const trashRepoPaths = { root: trashRepoRoot, sonarrCF: `${trashRepoSonarrRoot}/cf`, - sonarrQuality: `${trashRepoSonarrRoot}/quality-size`, + sonarrQualitySize: `${trashRepoSonarrRoot}/quality-size`, + sonarrQP: `${trashRepoSonarrRoot}/quality-profiles`, sonarrNaming: `${trashRepoSonarrRoot}/naming`, radarrCF: `${trashRepoRadarrRoot}/cf`, - radarrQuality: `${trashRepoRadarrRoot}/quality-size`, + radarrQualitySize: `${trashRepoRadarrRoot}/quality-size`, + radarrQP: `${trashRepoRadarrRoot}/quality-profiles`, radarrNaming: `${trashRepoRadarrRoot}/naming`, };