Skip to content

Commit 87cd0ee

Browse files
committed
add '--all' flag to display hidden fields
1 parent 10f9189 commit 87cd0ee

File tree

10 files changed

+80
-23
lines changed

10 files changed

+80
-23
lines changed

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ A CLI tool for querying and inspecting the media in your Radarr and Sonarr insta
2424

2525
<!-- tocstop -->
2626

27-
If you're looking for a tool to manage/change your \*arrs, check out [managarr](https://github.com/Dark-Alex-17/managarr).
27+
If you're looking for a more general tool to manage/change your \*arrs, check out [managarr](https://github.com/Dark-Alex-17/managarr).
2828

2929
## Installation
3030

@@ -127,6 +127,21 @@ inspectarr radarr 'monitored && videoCodec *= 265'
127127
- `season` (alias: `s`) - Season number
128128
- `episode` (alias: `e`) - Episode number
129129

130+
#### Hidden Fields
131+
132+
Some fields are hidden by default to prevent the markdown output from being too wide. Note: The JSON output will always include all fields.
133+
134+
To display hidden fields, use the `--all` flag.
135+
136+
- `qualityProfile` (alias: `qp`) - Quality profile name
137+
- `rawResolution` - Total pixel count
138+
- `rawSize` - File size in bytes
139+
140+
> [!TIP]
141+
> If the markdown output is too wide, use the `EXCLUDE` operation to exclude specific fields/columns.
142+
>
143+
> For example: `inspectarr sonarr --all '* | EXCLUDE rawResolution rawSize'`
144+
130145
#### Operations
131146

132147
In addition to the [built-in FilterQL operations](https://github.com/adamhl8/filterql#operations), the following operations are available:
@@ -148,10 +163,11 @@ inspectarr radarr 'source == bluray | EXCLUDE resolution size'
148163
- `--output <md|json>`: The type of output to generate ("json" implies --quiet) (default: "md")
149164
- `--quiet`: Suppress all output except the markdown/JSON
150165
- `--short-headers`: Use the field aliases as the markdown table headers (can help reduce the width of the table)
166+
- `--all`: Show fields that are hidden by default in the markdown table
151167

152168
**Sonarr-specific flags:**
153169

154170
By default, media from Sonarr is displayed by series.
155171

156-
- `--by-season`: Display media by individual season
157172
- `--by-episode`: Display media by individual episode
173+
- `--by-season`: Display media by individual season

bun.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "inspectarr",
3-
"version": "1.1.1",
3+
"version": "1.2.0",
44
"type": "module",
55
"description": "A CLI tool for querying and inspecting the media in your Radarr and Sonarr instances",
66
"repository": {
@@ -26,7 +26,7 @@
2626
},
2727
"dependencies": {
2828
"cleye": "^1.3.4",
29-
"filterql": "^3.0.0",
29+
"filterql": "^3.0.1",
3030
"ky": "^1.9.1",
3131
"tablemark": "github:haltcase/tablemark#v4",
3232
"ts-explicit-errors": "^3.0.0"

src/arr-client/radarr-client.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { paths } from "~/generated/radarr-schema.ts"
77
import { formatSize, getRawResolution } from "~/utils.ts"
88

99
type RadarrAllMedia = paths["/api/v3/movie"]["get"]["responses"]["200"]["content"]["application/json"][number]
10+
type RadarrQualityProfiles = paths["/api/v3/qualityprofile"]["get"]["responses"]["200"]["content"]["application/json"]
1011

1112
export class RadarrClient extends ArrClient {
1213
public constructor(baseUrl: string, apiKey: string) {
@@ -17,10 +18,17 @@ export class RadarrClient extends ArrClient {
1718
return this.makeRequest<RadarrAllMedia[]>("movie")
1819
}
1920

21+
public getAllQualityProfiles(): Promise<Result<RadarrQualityProfiles>> {
22+
return this.makeRequest<RadarrQualityProfiles>("qualityprofile")
23+
}
24+
2025
public async getMediaData() {
2126
const allMedia = await this.getAllMedia()
2227
if (isErr(allMedia)) return err("failed to get radarr media", allMedia)
2328

29+
const qualityProfiles = await this.getAllQualityProfiles()
30+
if (isErr(qualityProfiles)) return err("failed to get radarr quality profiles", qualityProfiles)
31+
2432
const data: RadarrMediaData = []
2533
for (const movie of allMedia) {
2634
if (!movie.hasFile) continue
@@ -34,6 +42,7 @@ export class RadarrClient extends ArrClient {
3442
monitored: movie.monitored,
3543
releaseGroup: movieFile?.releaseGroup,
3644
source: movieFile?.quality?.quality?.source,
45+
qualityProfile: qualityProfiles.find((profile) => profile.id === movie.qualityProfileId)?.name,
3746
videoCodec: mediaDetails?.videoCodec,
3847
audioCodec: mediaDetails?.audioCodec,
3948
audioChannels: mediaDetails?.audioChannels,

src/arr-client/sonarr-client.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { formatSize, getRawResolution } from "~/utils.ts"
88

99
type SonarrAllMedia = paths["/api/v3/series"]["get"]["responses"]["200"]["content"]["application/json"]
1010
type SonarrSeries = SonarrAllMedia[number]
11+
type SonarrQualityProfiles = paths["/api/v3/qualityprofile"]["get"]["responses"]["200"]["content"]["application/json"]
1112

1213
type SeriesEpisodes = paths["/api/v3/episode"]["get"]["responses"]["200"]["content"]["application/json"]
1314
type SeriesEpisode = SeriesEpisodes[number] | undefined
@@ -38,6 +39,8 @@ type SeriesBySeason = { series: SonarrSeries; seasons: SeasonsArray }
3839

3940
const seriesTitle = (series: SonarrSeries) => series.title
4041
const seriesYear = (series: SonarrSeries) => series.year
42+
const seriesQualityProfile = (series: SonarrSeries, qualityProfiles: SonarrQualityProfiles) =>
43+
qualityProfiles.find((profile) => profile.id === series.qualityProfileId)?.name
4144
const seasonIdentifier = (seasonNumber: number) => seasonNumber.toString().padStart(2, "0")
4245
const episodeIdentifier = (episode?: SeriesEpisode) => episode?.episodeNumber?.toString().padStart(2, "0")
4346
const seriesType = (series: SonarrSeries) => series.seriesType
@@ -84,6 +87,10 @@ export class SonarrClient extends ArrClient {
8487
return await this.makeRequest<SonarrAllMedia>("series")
8588
}
8689

90+
public getAllQualityProfiles(): Promise<Result<SonarrQualityProfiles>> {
91+
return this.makeRequest<SonarrQualityProfiles>("qualityprofile")
92+
}
93+
8794
private async getAllEpisodesForSeries(series: SonarrSeries): Promise<Result<SeriesEpisodes>> {
8895
if (!series.id) return err(`series id is missing for series '${series.title}'`)
8996
const episodes = await this.makeRequest<SeriesEpisodes>(`episode?seriesId=${series.id}&includeEpisodeFile=true`)
@@ -144,6 +151,9 @@ export class SonarrClient extends ArrClient {
144151
const allSeriesBySeason = await this.getAllSeriesBySeason()
145152
if (isErr(allSeriesBySeason)) return allSeriesBySeason
146153

154+
const qualityProfiles = await this.getAllQualityProfiles()
155+
if (isErr(qualityProfiles)) return err("failed to get sonarr quality profiles", qualityProfiles)
156+
147157
const data: SonarrMediaData = []
148158

149159
if (this.#options.byEpisode) {
@@ -159,6 +169,7 @@ export class SonarrClient extends ArrClient {
159169
episode: episodeIdentifier(episode),
160170
type: seriesType(series),
161171
monitored: episode?.monitored,
172+
qualityProfile: seriesQualityProfile(series, qualityProfiles),
162173
releaseGroup: episodeReleaseGroup(episode),
163174
source: episodeSource(episode),
164175
videoCodec: episodeVideoCodec(episode),
@@ -182,6 +193,7 @@ export class SonarrClient extends ArrClient {
182193
season: seasonIdentifier(seasonNumber),
183194
type: seriesType(series),
184195
monitored: series.seasons?.find((seasonElement) => seasonElement.seasonNumber === seasonNumber)?.monitored,
196+
qualityProfile: seriesQualityProfile(series, qualityProfiles),
185197
releaseGroup: gatherEpisodeData([season], episodeReleaseGroup),
186198
source: gatherEpisodeData([season], episodeSource),
187199
videoCodec: gatherEpisodeData([season], episodeVideoCodec),
@@ -202,6 +214,7 @@ export class SonarrClient extends ArrClient {
202214
year: seriesYear(series),
203215
type: seriesType(series),
204216
monitored: series.monitored,
217+
qualityProfile: seriesQualityProfile(series, qualityProfiles),
205218
releaseGroup: gatherEpisodeData(seasons, episodeReleaseGroup),
206219
source: gatherEpisodeData(seasons, episodeSource),
207220
videoCodec: gatherEpisodeData(seasons, episodeVideoCodec),

src/cli/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ export function getServiceInfo(): CommandInfo {
6363
const filterql = new FilterQL({ schema, customOperations })
6464
const query = Object.hasOwn(args._, "query") ? (args._.query ?? "") : ""
6565

66-
let { output, quiet, shortHeaders } = args.flags
66+
let { output, quiet, shortHeaders, all } = args.flags
6767
const outputResult = handleFlagResult(output, showHelpAndExit)
6868
if (outputResult === "json") quiet = true
69-
const logger = new Logger({ output: outputResult, quiet, shortHeaders }, schema)
69+
const logger = new Logger({ output: outputResult, quiet, shortHeaders, all }, schema)
7070

7171
return {
7272
client,

src/cli/shared.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ const outputFlags = {
4242
description: "Use the field aliases as the markdown table headers (can help reduce the width of the table)",
4343
default: false,
4444
},
45+
all: {
46+
type: Boolean,
47+
description: "Show fields that are hidden by default in the markdown table",
48+
default: false,
49+
},
4550
} as const satisfies Flags
4651
export type OutputFlags = FlagsToType<typeof outputFlags>
4752

@@ -56,6 +61,7 @@ export const baseParameters = ["[query]"] as const satisfies CliParameters
5661
* These are extra fields we want to attach to the data, but don't want to be displayed in the markdown output at all
5762
*/
5863
const hiddenFields = {
64+
qualityProfile: { type: "string", alias: "qp" },
5965
rawResolution: { type: "number" },
6066
rawSize: { type: "number" },
6167
} as const satisfies Schema

src/cli/sonarr.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import { baseFlags, baseParameters, baseSchema } from "~/cli/shared.ts"
66
import type { Flags, FlagsToType, SchemaToType, ToMediaData } from "~/cli/types.ts"
77

88
const sonarrOptionFlags = {
9-
bySeason: {
9+
byEpisode: {
1010
type: Boolean,
1111
default: false,
12-
description: "Display media by individual season",
12+
description: "Display media by individual episode",
1313
},
14-
byEpisode: {
14+
bySeason: {
1515
type: Boolean,
1616
default: false,
17-
description: "Display media by individual episode",
17+
description: "Display media by individual season",
1818
},
1919
} as const satisfies Flags
2020
export type SonarrOptions = FlagsToType<typeof sonarrOptionFlags>

src/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,29 @@ async function inspectarr(): Promise<Result> {
2020
const mediaData = await client.getNormalizedMediaData()
2121
if (isErr(mediaData)) return mediaData
2222

23-
const filteredMedia = attempt(() => filterql.query(mediaData, query))
23+
const ast = attempt(() => filterql.parse(query))
24+
if (isErr(ast)) return err(`failed to parse query '${query}'`, ast)
25+
26+
const filteredMedia = attempt(() => filterql.applyFilter(mediaData, ast.filter))
2427
if (isErr(filteredMedia)) return err(`failed to filter media with query '${query}'`, filteredMedia)
2528

29+
// don't apply EXCLUDE operation if output is JSON
30+
const operations =
31+
logger.options.output === "json" ? ast.operations.filter(({ name }) => name !== "EXCLUDE") : ast.operations
32+
33+
const transformedMedia = attempt(() => filterql.applyOperations(filteredMedia, operations))
34+
if (isErr(transformedMedia)) return err(`failed to apply operations to media with query '${query}'`, transformedMedia)
35+
2636
if (!process.env["IS_VHS_DEMO"]) {
2737
logger.info(`${client.name} has: ${getStats(mediaData)}`)
2838
// only display query stats if they're actually different
29-
if (mediaData.length !== filteredMedia.length) logger.info(`The query matched: ${getStats(filteredMedia)}`)
39+
if (mediaData.length !== transformedMedia.length) logger.info(`The query matched: ${getStats(transformedMedia)}`)
3040
}
3141

32-
if (filteredMedia.length === 0) return
42+
if (transformedMedia.length === 0) return
3343

3444
logger.info("")
35-
logger.printMediaData(filteredMedia)
45+
logger.printMediaData(transformedMedia)
3646
}
3747

3848
async function main(): Promise<number> {

src/logger.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,26 @@ import { tablemark } from "tablemark"
55

66
import type { OutputFlags } from "~/cli/shared.ts"
77
import { hiddenFieldKeys, mergedFieldKeys } from "~/cli/shared.ts"
8-
import type { JsonifiableMediaData } from "~/cli/types.ts"
8+
import type { AllMediaDataKeys, JsonifiableMediaData } from "~/cli/types.ts"
99
import { formatQualifiedValue, omit } from "~/utils.ts"
1010

1111
const pipeRegex = /\|/g
1212

1313
export class Logger {
14-
readonly #options: OutputFlags
14+
public readonly options: OutputFlags
1515
readonly #schema: Schema
1616

1717
public constructor(options: OutputFlags, schema: Schema) {
18-
this.#options = options
18+
this.options = options
1919
this.#schema = schema
2020
}
2121

2222
public info(message: string): void {
23-
if (!this.#options.quiet) console.info(message)
23+
if (!this.options.quiet) console.info(message)
2424
}
2525

2626
public printMediaData(data: JsonifiableMediaData) {
27-
const output = this.#options.output === "md" ? this.toMarkdown(data) : this.toJson(data)
27+
const output = this.options.output === "md" ? this.toMarkdown(data) : this.toJson(data)
2828
process.stdout.write(output)
2929
}
3030

@@ -37,7 +37,7 @@ export class Logger {
3737
return value.toString().replaceAll(pipeRegex, "\\|") // need to escape any pipe characters
3838
},
3939
}
40-
if (this.#options.shortHeaders)
40+
if (this.options.shortHeaders)
4141
tablemarkOptions.toHeaderTitle = ({ key, title }) => this.#schema[key]?.alias ?? title.toLowerCase()
4242

4343
const markdownData = data.map((dataObject) => {
@@ -49,7 +49,10 @@ export class Logger {
4949
newDataObject.audioCodec = formatQualifiedValue(newDataObject.audioCodec, newDataObject.audioChannels)
5050
}
5151

52-
return omit(newDataObject, [...hiddenFieldKeys, ...mergedFieldKeys])
52+
const fieldsToOmit: AllMediaDataKeys[] = [...mergedFieldKeys]
53+
if (!this.options.all) fieldsToOmit.push(...hiddenFieldKeys)
54+
55+
return omit(newDataObject, fieldsToOmit)
5356
})
5457

5558
return tablemark(markdownData, tablemarkOptions)

0 commit comments

Comments
 (0)