@muhammedaksam/easiarr 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/api/arr-api.ts +15 -4
- package/src/api/prowlarr-api.ts +25 -3
- package/src/index.ts +9 -0
- package/src/ui/screens/AppConfigurator.ts +8 -1
- package/src/utils/debug.ts +48 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhammedaksam/easiarr",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"dev": "bun run --watch src/index.ts",
|
|
38
|
+
"dev:debug": "bun run dev --debug",
|
|
38
39
|
"build": "bun build src/index.ts --outdir dist --target bun",
|
|
39
40
|
"typecheck": "bun x tsc --noEmit",
|
|
40
41
|
"lint": "eslint src/",
|
package/src/api/arr-api.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Interacts with Radarr, Sonarr, Lidarr, Readarr, Whisparr APIs
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { debugLog } from "../utils/debug"
|
|
7
|
+
|
|
6
8
|
// Types for Root Folder API
|
|
7
9
|
export interface RootFolder {
|
|
8
10
|
id?: number
|
|
@@ -15,6 +17,7 @@ export interface RootFolder {
|
|
|
15
17
|
// Options for adding root folder (some apps like Lidarr need extra fields)
|
|
16
18
|
export interface AddRootFolderOptions {
|
|
17
19
|
path: string
|
|
20
|
+
name?: string // Required for Lidarr
|
|
18
21
|
defaultMetadataProfileId?: number
|
|
19
22
|
defaultQualityProfileId?: number
|
|
20
23
|
}
|
|
@@ -124,16 +127,24 @@ export class ArrApiClient {
|
|
|
124
127
|
...options.headers,
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
debugLog("ArrAPI", `${options.method || "GET"} ${url}`)
|
|
131
|
+
if (options.body) {
|
|
132
|
+
debugLog("ArrAPI", `Request Body: ${options.body}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
127
135
|
const response = await fetch(url, { ...options, headers })
|
|
136
|
+
const text = await response.text()
|
|
137
|
+
|
|
138
|
+
debugLog("ArrAPI", `Response ${response.status} from ${endpoint}`)
|
|
139
|
+
if (text && text.length < 2000) {
|
|
140
|
+
debugLog("ArrAPI", `Response Body: ${text}`)
|
|
141
|
+
}
|
|
128
142
|
|
|
129
143
|
if (!response.ok) {
|
|
130
|
-
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
|
|
144
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${text}`)
|
|
131
145
|
}
|
|
132
146
|
|
|
133
|
-
// Handle empty responses (DELETE returns empty body)
|
|
134
|
-
const text = await response.text()
|
|
135
147
|
if (!text) return {} as T
|
|
136
|
-
|
|
137
148
|
return JSON.parse(text) as T
|
|
138
149
|
}
|
|
139
150
|
|
package/src/api/prowlarr-api.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Manages Indexer Proxies, Sync Profiles, and FlareSolverr integration
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { debugLog } from "../utils/debug"
|
|
7
|
+
|
|
6
8
|
export interface IndexerProxy {
|
|
7
9
|
id?: number
|
|
8
10
|
name: string
|
|
@@ -55,13 +57,23 @@ export class ProwlarrClient {
|
|
|
55
57
|
...((options.headers as Record<string, string>) || {}),
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
debugLog("Prowlarr", `${options.method || "GET"} ${url}`)
|
|
61
|
+
if (options.body) {
|
|
62
|
+
debugLog("Prowlarr", `Request Body: ${options.body}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
const response = await fetch(url, { ...options, headers })
|
|
66
|
+
const text = await response.text()
|
|
67
|
+
|
|
68
|
+
debugLog("Prowlarr", `Response ${response.status} from ${endpoint}`)
|
|
69
|
+
if (text && text.length < 2000) {
|
|
70
|
+
debugLog("Prowlarr", `Response Body: ${text}`)
|
|
71
|
+
}
|
|
59
72
|
|
|
60
73
|
if (!response.ok) {
|
|
61
|
-
throw new Error(`Prowlarr API request failed: ${response.status} ${response.statusText}`)
|
|
74
|
+
throw new Error(`Prowlarr API request failed: ${response.status} ${response.statusText} - ${text}`)
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
const text = await response.text()
|
|
65
77
|
if (!text) return {} as T
|
|
66
78
|
return JSON.parse(text) as T
|
|
67
79
|
}
|
|
@@ -248,11 +260,21 @@ export class ProwlarrClient {
|
|
|
248
260
|
appApiKey: string,
|
|
249
261
|
syncLevel: "disabled" | "addOnly" | "fullSync" = "fullSync"
|
|
250
262
|
): Promise<Application> {
|
|
263
|
+
// Default sync categories for each app type
|
|
264
|
+
// Radarr: Movies (2000, 2010, etc), Sonarr: TV (5000, 5010, etc)
|
|
265
|
+
// Lidarr: Audio (3000), Readarr: Books (7000, 8010)
|
|
266
|
+
const syncCategoriesMap: Record<ArrAppType, number[]> = {
|
|
267
|
+
Radarr: [2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080],
|
|
268
|
+
Sonarr: [5000, 5010, 5020, 5030, 5040, 5045, 5050, 5060, 5070, 5080],
|
|
269
|
+
Lidarr: [3000, 3010, 3020, 3030, 3040],
|
|
270
|
+
Readarr: [7000, 7010, 7020, 7030, 8000, 8010, 8020],
|
|
271
|
+
}
|
|
272
|
+
|
|
251
273
|
const fields: { name: string; value: unknown }[] = [
|
|
252
274
|
{ name: "prowlarrUrl", value: prowlarrUrl },
|
|
253
275
|
{ name: "baseUrl", value: appUrl },
|
|
254
276
|
{ name: "apiKey", value: appApiKey },
|
|
255
|
-
{ name: "syncCategories", value: [] },
|
|
277
|
+
{ name: "syncCategories", value: syncCategoriesMap[appType] || [] },
|
|
256
278
|
]
|
|
257
279
|
|
|
258
280
|
return this.request<Application>("/applications", {
|
package/src/index.ts
CHANGED
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Easiarr Entry Point
|
|
4
4
|
* TUI tool for generating docker-compose files for the *arr ecosystem
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* easiarr - Start the TUI
|
|
8
|
+
* easiarr --debug - Start with debug logging enabled
|
|
9
|
+
* easiarr -d - Same as --debug
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { createCliRenderer } from "@opentui/core"
|
|
8
13
|
import { App } from "./ui/App"
|
|
14
|
+
import { initDebug } from "./utils/debug"
|
|
9
15
|
|
|
10
16
|
async function main() {
|
|
17
|
+
// Initialize debug logging if enabled
|
|
18
|
+
initDebug()
|
|
19
|
+
|
|
11
20
|
const renderer = await createCliRenderer({
|
|
12
21
|
consoleOptions: {
|
|
13
22
|
startInDebugMode: false,
|
|
@@ -313,12 +313,13 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
313
313
|
throw new Error("Already configured")
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
// Add root folder - Lidarr requires profile IDs
|
|
316
|
+
// Add root folder - Lidarr requires profile IDs and name
|
|
317
317
|
if (appId === "lidarr") {
|
|
318
318
|
const metadataProfiles = await client.getMetadataProfiles()
|
|
319
319
|
const qualityProfiles = await client.getQualityProfiles()
|
|
320
320
|
await client.addRootFolder({
|
|
321
321
|
path: appDef.rootFolder.path,
|
|
322
|
+
name: "Music", // Required by Lidarr
|
|
322
323
|
defaultMetadataProfileId: metadataProfiles[0]?.id || 1,
|
|
323
324
|
defaultQualityProfileId: qualityProfiles[0]?.id || 1,
|
|
324
325
|
})
|
|
@@ -560,6 +561,12 @@ export class AppConfigurator extends BoxRenderable {
|
|
|
560
561
|
const client = new ArrApiClient("localhost", port, apiKey, appDef.rootFolder.apiVersion)
|
|
561
562
|
|
|
562
563
|
try {
|
|
564
|
+
// Check if download client already exists
|
|
565
|
+
const existingClients = await client.getDownloadClients()
|
|
566
|
+
const clientName = type === "qbittorrent" ? "qBittorrent" : "SABnzbd"
|
|
567
|
+
const alreadyExists = existingClients.some((c) => c.name === clientName)
|
|
568
|
+
if (alreadyExists) continue
|
|
569
|
+
|
|
563
570
|
if (type === "qbittorrent") {
|
|
564
571
|
const config = createQBittorrentConfig(this.qbHost, this.qbPort, this.qbUser, this.qbPass, appConfig.id)
|
|
565
572
|
await client.addDownloadClient(config)
|
package/src/utils/debug.ts
CHANGED
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Debug logging utility for Easiarr
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Enable debug logging via:
|
|
5
|
+
* - CLI flag: easiarr --debug
|
|
6
|
+
* - Environment variable: EASIARR_DEBUG=1 bun run dev
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { appendFileSync } from "fs"
|
|
9
|
+
import { appendFileSync, writeFileSync } from "fs"
|
|
9
10
|
import { join } from "path"
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
const
|
|
12
|
+
// Check CLI args for --debug flag
|
|
13
|
+
const hasDebugFlag = process.argv.includes("--debug") || process.argv.includes("-d")
|
|
14
|
+
const hasEnvDebug = process.env.EASIARR_DEBUG === "1" || process.env.EASIARR_DEBUG === "true"
|
|
15
|
+
|
|
16
|
+
export const DEBUG_ENABLED = hasDebugFlag || hasEnvDebug
|
|
17
|
+
|
|
18
|
+
// Save debug log to ~/.easiarr/ like other config files
|
|
19
|
+
const easiarrDir = join(process.env.HOME || "~", ".easiarr")
|
|
20
|
+
const logFile = join(easiarrDir, "debug.log")
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
|
-
*
|
|
23
|
+
* Initialize debug mode - clears old log file
|
|
24
|
+
*/
|
|
25
|
+
export function initDebug(): void {
|
|
26
|
+
if (!DEBUG_ENABLED) return
|
|
27
|
+
try {
|
|
28
|
+
writeFileSync(logFile, `=== Easiarr Debug Log - ${new Date().toISOString()} ===\n`)
|
|
29
|
+
} catch {
|
|
30
|
+
// Ignore
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Log a debug message to debug.log file if debug mode is enabled
|
|
16
36
|
*/
|
|
17
37
|
export function debugLog(category: string, message: string): void {
|
|
18
38
|
if (!DEBUG_ENABLED) return
|
|
@@ -25,3 +45,25 @@ export function debugLog(category: string, message: string): void {
|
|
|
25
45
|
// Ignore logging errors
|
|
26
46
|
}
|
|
27
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Log API request details for debugging
|
|
51
|
+
*/
|
|
52
|
+
export function debugRequest(method: string, url: string, body?: unknown): void {
|
|
53
|
+
if (!DEBUG_ENABLED) return
|
|
54
|
+
debugLog("API", `${method} ${url}`)
|
|
55
|
+
if (body) {
|
|
56
|
+
debugLog("API", `Body: ${JSON.stringify(body, null, 2)}`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Log API response details for debugging
|
|
62
|
+
*/
|
|
63
|
+
export function debugResponse(status: number, url: string, body?: string): void {
|
|
64
|
+
if (!DEBUG_ENABLED) return
|
|
65
|
+
debugLog("API", `Response ${status} from ${url}`)
|
|
66
|
+
if (body && body.length < 2000) {
|
|
67
|
+
debugLog("API", `Response Body: ${body}`)
|
|
68
|
+
}
|
|
69
|
+
}
|