@muhammedaksam/easiarr 1.4.0 → 1.4.1
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/dist/index.js +29837 -0
- package/package.json +15 -15
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -521
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/base-api.ts +0 -216
- package/src/api/bazarr-api.ts +0 -297
- package/src/api/cloudflare-api.ts +0 -493
- package/src/api/custom-format-api.ts +0 -224
- package/src/api/grafana-api.ts +0 -230
- package/src/api/heimdall-api.ts +0 -141
- package/src/api/homarr-api.ts +0 -209
- package/src/api/huntarr-api.ts +0 -457
- package/src/api/index.ts +0 -9
- package/src/api/jellyfin-api.ts +0 -389
- package/src/api/jellyseerr-api.ts +0 -559
- package/src/api/maintainerr-api.ts +0 -461
- package/src/api/naming-config.ts +0 -101
- package/src/api/overseerr-api.ts +0 -392
- package/src/api/plex-api.ts +0 -308
- package/src/api/portainer-api.ts +0 -374
- package/src/api/profilarr-api.ts +0 -391
- package/src/api/prowlarr-api.ts +0 -488
- package/src/api/qbittorrent-api.ts +0 -300
- package/src/api/quality-profile-api.ts +0 -272
- package/src/api/tautulli-api.ts +0 -244
- package/src/api/uptime-kuma-api.ts +0 -343
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1275
- package/src/compose/caddy-config.ts +0 -130
- package/src/compose/generator.ts +0 -287
- package/src/compose/index.ts +0 -7
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -187
- package/src/config/bookmarks-generator.ts +0 -160
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -381
- package/src/config/index.ts +0 -6
- package/src/config/manager.ts +0 -150
- package/src/config/recyclarr-config.ts +0 -181
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -86
- package/src/config/soularr-config.ts +0 -105
- package/src/config/trash-quality-definitions.ts +0 -213
- package/src/data/lidarr-custom-formats.ts +0 -127
- package/src/data/trash-profiles.ts +0 -333
- package/src/docker/client.ts +0 -312
- package/src/docker/index.ts +0 -18
- package/src/index.ts +0 -77
- package/src/setup/actions/arr-common.ts +0 -307
- package/src/setup/actions/dashboards.ts +0 -79
- package/src/setup/actions/download-clients.ts +0 -54
- package/src/setup/actions/jellyseerr.ts +0 -331
- package/src/setup/actions/media-servers.ts +0 -68
- package/src/setup/actions/monitoring.ts +0 -120
- package/src/setup/actions/prowlarr.ts +0 -137
- package/src/setup/actions/utilities.ts +0 -490
- package/src/setup/index.ts +0 -78
- package/src/setup/types.ts +0 -144
- package/src/structure/manager.ts +0 -129
- package/src/ui/App.ts +0 -111
- package/src/ui/components/ApplicationSelector.ts +0 -275
- package/src/ui/components/CredentialsForm.ts +0 -180
- package/src/ui/components/DownloadClientForm.ts +0 -114
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -160
- package/src/ui/components/UpdateNotification.ts +0 -102
- package/src/ui/index.ts +0 -7
- package/src/ui/screens/AdvancedSettings.ts +0 -188
- package/src/ui/screens/ApiKeyViewer.ts +0 -511
- package/src/ui/screens/AppConfigurator.ts +0 -543
- package/src/ui/screens/AppManager.ts +0 -284
- package/src/ui/screens/ContainerControl.ts +0 -581
- package/src/ui/screens/LogsViewer.ts +0 -478
- package/src/ui/screens/MainMenu.ts +0 -299
- package/src/ui/screens/MonitorDashboard.ts +0 -881
- package/src/ui/screens/SecretsEditor.ts +0 -216
- package/src/ui/screens/SettingsScreen.ts +0 -633
- package/src/ui/screens/index.ts +0 -22
- package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
- package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
- package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
- package/src/ui/screens/setup/HomepageSetup.ts +0 -165
- package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
- package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
- package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
- package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
- package/src/ui/screens/setup/QuickSetup.ts +0 -1169
- package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
- package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
- package/src/utils/arch.ts +0 -78
- package/src/utils/browser.ts +0 -27
- package/src/utils/categories.ts +0 -61
- package/src/utils/debug.ts +0 -84
- package/src/utils/env.ts +0 -138
- package/src/utils/index.ts +0 -8
- package/src/utils/logs.ts +0 -119
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
- package/src/utils/migrations.ts +0 -165
- package/src/utils/password.ts +0 -42
- package/src/utils/paths.ts +0 -33
- package/src/utils/unraid.ts +0 -103
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recyclarr Setup Screen
|
|
3
|
-
* Configure TRaSH Guides profile sync and trigger manual runs
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { CliRenderer, KeyEvent, TextRenderable } from "@opentui/core"
|
|
7
|
-
|
|
8
|
-
import type { MenuItem } from "~/ui/screens/setup/BaseAppSetupScreen"
|
|
9
|
-
import { saveRecyclarrConfig } from "~/config/recyclarr-config"
|
|
10
|
-
import { EasiarrConfig } from "~/config/schema"
|
|
11
|
-
import { RADARR_PRESETS, SONARR_PRESETS } from "~/data/trash-profiles"
|
|
12
|
-
import { composeRun } from "~/docker"
|
|
13
|
-
import { BaseAppSetupScreen } from "~/ui/screens/setup/BaseAppSetupScreen"
|
|
14
|
-
|
|
15
|
-
type ViewMode = "main" | "radarr" | "sonarr"
|
|
16
|
-
|
|
17
|
-
export class RecyclarrSetup extends BaseAppSetupScreen {
|
|
18
|
-
private viewMode: ViewMode = "main"
|
|
19
|
-
private radarrPreset: string = "hd-bluray-web"
|
|
20
|
-
private sonarrPreset: string = "web-1080p-v4"
|
|
21
|
-
private statusMessage: string = ""
|
|
22
|
-
private statusColor: string = "#888888"
|
|
23
|
-
|
|
24
|
-
constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
|
|
25
|
-
super(cliRenderer, config, onBack)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getTitle(): string {
|
|
29
|
-
return "♻️ Recyclarr Setup"
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
getStepInfo(): string {
|
|
33
|
-
return "Configure TRaSH Guides profile sync"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getMenuItems(): MenuItem[] {
|
|
37
|
-
const radarrEnabled = this.config.apps.some((a) => a.id === "radarr" && a.enabled)
|
|
38
|
-
const sonarrEnabled = this.config.apps.some((a) => a.id === "sonarr" && a.enabled)
|
|
39
|
-
|
|
40
|
-
if (this.viewMode === "radarr") {
|
|
41
|
-
return [
|
|
42
|
-
...RADARR_PRESETS.map((p) => ({
|
|
43
|
-
name: `${this.radarrPreset === p.id ? "● " : "○ "}${p.name}`,
|
|
44
|
-
description: p.description,
|
|
45
|
-
action: () => {
|
|
46
|
-
this.radarrPreset = p.id
|
|
47
|
-
this.statusMessage = `✓ Radarr profile set to: ${p.name}`
|
|
48
|
-
this.statusColor = "#50fa7b"
|
|
49
|
-
this.viewMode = "main"
|
|
50
|
-
this.menuIndex = 0
|
|
51
|
-
this.refreshContent()
|
|
52
|
-
},
|
|
53
|
-
})),
|
|
54
|
-
{
|
|
55
|
-
name: "◀ Back",
|
|
56
|
-
description: "Return to main menu",
|
|
57
|
-
action: () => {
|
|
58
|
-
this.viewMode = "main"
|
|
59
|
-
this.menuIndex = 0
|
|
60
|
-
this.refreshContent()
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
]
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (this.viewMode === "sonarr") {
|
|
67
|
-
return [
|
|
68
|
-
...SONARR_PRESETS.map((p) => ({
|
|
69
|
-
name: `${this.sonarrPreset === p.id ? "● " : "○ "}${p.name}`,
|
|
70
|
-
description: p.description,
|
|
71
|
-
action: () => {
|
|
72
|
-
this.sonarrPreset = p.id
|
|
73
|
-
this.statusMessage = `✓ Sonarr profile set to: ${p.name}`
|
|
74
|
-
this.statusColor = "#50fa7b"
|
|
75
|
-
this.viewMode = "main"
|
|
76
|
-
this.menuIndex = 0
|
|
77
|
-
this.refreshContent()
|
|
78
|
-
},
|
|
79
|
-
})),
|
|
80
|
-
{
|
|
81
|
-
name: "◀ Back",
|
|
82
|
-
description: "Return to main menu",
|
|
83
|
-
action: () => {
|
|
84
|
-
this.viewMode = "main"
|
|
85
|
-
this.menuIndex = 0
|
|
86
|
-
this.refreshContent()
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
]
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Main Menu
|
|
93
|
-
const options: MenuItem[] = []
|
|
94
|
-
if (radarrEnabled) {
|
|
95
|
-
options.push({
|
|
96
|
-
name: "🎬 Configure Radarr Profile",
|
|
97
|
-
description: "Select TRaSH Guide profile for movies",
|
|
98
|
-
action: () => {
|
|
99
|
-
this.viewMode = "radarr"
|
|
100
|
-
this.menuIndex = 0
|
|
101
|
-
this.refreshContent()
|
|
102
|
-
},
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
if (sonarrEnabled) {
|
|
106
|
-
options.push({
|
|
107
|
-
name: "📺 Configure Sonarr Profile",
|
|
108
|
-
description: "Select TRaSH Guide profile for TV shows",
|
|
109
|
-
action: () => {
|
|
110
|
-
this.viewMode = "sonarr"
|
|
111
|
-
this.menuIndex = 0
|
|
112
|
-
this.refreshContent()
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
options.push({
|
|
117
|
-
name: "🔄 Run Sync Now",
|
|
118
|
-
description: "Manually trigger Recyclarr sync",
|
|
119
|
-
action: () => this.runSync(),
|
|
120
|
-
})
|
|
121
|
-
options.push({
|
|
122
|
-
name: "💾 Save & Generate Config",
|
|
123
|
-
description: "Save recyclarr.yml configuration",
|
|
124
|
-
action: () => this.saveConfig(),
|
|
125
|
-
})
|
|
126
|
-
options.push({
|
|
127
|
-
name: "↩️ Back",
|
|
128
|
-
description: "Return to main menu",
|
|
129
|
-
action: () => this.cleanup(),
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
return options
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
protected getMenuDescription(): string {
|
|
136
|
-
if (this.viewMode === "radarr") return "Select a TRaSH Guide profile for Radarr (Movies):"
|
|
137
|
-
if (this.viewMode === "sonarr") return "Select a TRaSH Guide profile for Sonarr (TV Shows):"
|
|
138
|
-
|
|
139
|
-
return "Recyclarr syncs TRaSH Guides custom formats and quality profiles to your *arr apps."
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
protected renderCustomContent(): boolean {
|
|
143
|
-
if (this.currentStep === "menu" && this.viewMode === "main") {
|
|
144
|
-
const radarrEnabled = this.config.apps.some((a) => a.id === "radarr" && a.enabled)
|
|
145
|
-
const sonarrEnabled = this.config.apps.some((a) => a.id === "sonarr" && a.enabled)
|
|
146
|
-
const recyclarrEnabled = this.config.apps.some((a) => a.id === "recyclarr" && a.enabled)
|
|
147
|
-
|
|
148
|
-
if (this.statusMessage) {
|
|
149
|
-
this.contentBox.add(
|
|
150
|
-
new TextRenderable(this.cliRenderer, {
|
|
151
|
-
content: this.statusMessage + "\n\n",
|
|
152
|
-
fg: this.statusColor,
|
|
153
|
-
})
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!recyclarrEnabled) {
|
|
158
|
-
this.contentBox.add(
|
|
159
|
-
new TextRenderable(this.cliRenderer, {
|
|
160
|
-
content: "⚠️ Recyclarr is not enabled. Enable it in App Manager first.\n\n",
|
|
161
|
-
fg: "#ff5555",
|
|
162
|
-
})
|
|
163
|
-
)
|
|
164
|
-
// We still show the menu but actions will mostly fail or we can filter them
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Current config status
|
|
168
|
-
if (radarrEnabled || sonarrEnabled) {
|
|
169
|
-
if (radarrEnabled) {
|
|
170
|
-
const preset = RADARR_PRESETS.find((p) => p.id === this.radarrPreset)
|
|
171
|
-
this.contentBox.add(
|
|
172
|
-
new TextRenderable(this.cliRenderer, {
|
|
173
|
-
content: `🎬 Radarr: ${preset?.name || "Default"}\n`,
|
|
174
|
-
fg: "#50fa7b",
|
|
175
|
-
})
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
if (sonarrEnabled) {
|
|
179
|
-
const preset = SONARR_PRESETS.find((p) => p.id === this.sonarrPreset)
|
|
180
|
-
this.contentBox.add(
|
|
181
|
-
new TextRenderable(this.cliRenderer, {
|
|
182
|
-
content: `📺 Sonarr: ${preset?.name || "Default"}\n`,
|
|
183
|
-
fg: "#50fa7b",
|
|
184
|
-
})
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
this.contentBox.add(new TextRenderable(this.cliRenderer, { content: "\n" }))
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Let the base class render the menu after this
|
|
191
|
-
this.renderMenu()
|
|
192
|
-
return true
|
|
193
|
-
}
|
|
194
|
-
return false
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
protected handleCustomKeys(key: KeyEvent): boolean {
|
|
198
|
-
if (this.viewMode !== "main" && (key.name === "escape" || key.name === "q")) {
|
|
199
|
-
this.viewMode = "main"
|
|
200
|
-
this.menuIndex = 0
|
|
201
|
-
this.refreshContent()
|
|
202
|
-
return true
|
|
203
|
-
}
|
|
204
|
-
return false
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ============================================
|
|
208
|
-
// ACTIONS
|
|
209
|
-
// ============================================
|
|
210
|
-
|
|
211
|
-
private async runSync(): Promise<void> {
|
|
212
|
-
this.currentStep = "running"
|
|
213
|
-
this.results = [
|
|
214
|
-
{ name: "Save Config", status: "configuring" },
|
|
215
|
-
{ name: "Recyclarr Sync", status: "pending" },
|
|
216
|
-
]
|
|
217
|
-
this.refreshContent()
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
// Save config first
|
|
221
|
-
await saveRecyclarrConfig(this.config)
|
|
222
|
-
this.results[0].status = "success"
|
|
223
|
-
this.refreshContent()
|
|
224
|
-
|
|
225
|
-
// Run sync
|
|
226
|
-
this.results[1].status = "configuring"
|
|
227
|
-
this.refreshContent()
|
|
228
|
-
const result = await composeRun("recyclarr", "sync")
|
|
229
|
-
|
|
230
|
-
if (result.success) {
|
|
231
|
-
this.results[1].status = "success"
|
|
232
|
-
this.statusMessage = "✓ Recyclarr sync completed successfully!"
|
|
233
|
-
this.statusColor = "#50fa7b"
|
|
234
|
-
} else {
|
|
235
|
-
this.results[1].status = "error"
|
|
236
|
-
this.results[1].message = "Sync completed with warnings"
|
|
237
|
-
this.statusMessage = "⚠ Sync completed with warnings"
|
|
238
|
-
this.statusColor = "#f1fa8c"
|
|
239
|
-
}
|
|
240
|
-
} catch (err) {
|
|
241
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
242
|
-
const current = this.results.find((r) => r.status === "configuring")
|
|
243
|
-
if (current) {
|
|
244
|
-
current.status = "error"
|
|
245
|
-
current.message = msg
|
|
246
|
-
}
|
|
247
|
-
this.statusMessage = `✗ Sync failed: ${msg}`
|
|
248
|
-
this.statusColor = "#ff5555"
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
this.currentStep = "done"
|
|
252
|
-
this.refreshContent()
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private async saveConfig(): Promise<void> {
|
|
256
|
-
await this.runAction("Save Config", async () => {
|
|
257
|
-
try {
|
|
258
|
-
await saveRecyclarrConfig(this.config)
|
|
259
|
-
this.statusMessage = "✓ Recyclarr config saved!"
|
|
260
|
-
this.statusColor = "#50fa7b"
|
|
261
|
-
return { success: true }
|
|
262
|
-
} catch (err) {
|
|
263
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
264
|
-
this.statusMessage = `✗ Failed to save config: ${msg}`
|
|
265
|
-
this.statusColor = "#ff5555"
|
|
266
|
-
return { success: false, message: msg }
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
|
-
}
|
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TRaSH Profile Setup Screen
|
|
3
|
-
* Allows users to configure TRaSH-recommended quality profiles and custom formats
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { CliRenderer, KeyEvent, TextRenderable } from "@opentui/core"
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
SetupResult as BaseSetupResult,
|
|
10
|
-
MenuItem,
|
|
11
|
-
} from "~/ui/screens/setup/BaseAppSetupScreen"
|
|
12
|
-
import { ArrApiClient } from "~/api/arr-api"
|
|
13
|
-
import { CustomFormatClient, getCFNamesForCategories } from "~/api/custom-format-api"
|
|
14
|
-
import { QualityProfileClient } from "~/api/quality-profile-api"
|
|
15
|
-
import { getApp } from "~/apps/registry"
|
|
16
|
-
import { AppId, EasiarrConfig } from "~/config/schema"
|
|
17
|
-
import { LIDARR_CUSTOM_FORMATS } from "~/data/lidarr-custom-formats"
|
|
18
|
-
import { getPresetsForApp, TRaSHProfilePreset } from "~/data/trash-profiles"
|
|
19
|
-
import { BaseAppSetupScreen } from "~/ui/screens/setup/BaseAppSetupScreen"
|
|
20
|
-
import { readEnvSync } from "~/utils/env"
|
|
21
|
-
|
|
22
|
-
interface SetupResult extends BaseSetupResult {
|
|
23
|
-
appId: AppId
|
|
24
|
-
appName: string
|
|
25
|
-
profile: string
|
|
26
|
-
cfCount: number
|
|
27
|
-
namingConfigured: boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type SubStep = "select-apps" | "select-profiles"
|
|
31
|
-
|
|
32
|
-
export class TRaSHProfileSetup extends BaseAppSetupScreen {
|
|
33
|
-
private subStep: SubStep = "select-apps"
|
|
34
|
-
private selectedApps: Map<AppId, boolean> = new Map()
|
|
35
|
-
private selectedProfiles: Map<AppId, string> = new Map()
|
|
36
|
-
private currentIndex = 0
|
|
37
|
-
private availableApps: AppId[] = []
|
|
38
|
-
|
|
39
|
-
// Override results with more specific type
|
|
40
|
-
protected results: SetupResult[] = []
|
|
41
|
-
|
|
42
|
-
constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
|
|
43
|
-
super(cliRenderer, config, onBack)
|
|
44
|
-
|
|
45
|
-
// Get enabled *arr apps that support quality profiles
|
|
46
|
-
this.availableApps = config.apps
|
|
47
|
-
.filter((a) => a.enabled && ["radarr", "sonarr", "lidarr"].includes(a.id))
|
|
48
|
-
.map((a) => a.id)
|
|
49
|
-
|
|
50
|
-
// Initialize selections
|
|
51
|
-
this.availableApps.forEach((id) => {
|
|
52
|
-
this.selectedApps.set(id, true)
|
|
53
|
-
const presets = getPresetsForApp(id as "radarr" | "sonarr" | "lidarr")
|
|
54
|
-
if (presets.length > 0) {
|
|
55
|
-
this.selectedProfiles.set(id, presets[0].id)
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
this.refreshContent()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getTitle(): string {
|
|
63
|
-
return "TRaSH Guide Setup"
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
getStepInfo(): string {
|
|
67
|
-
return "Configure quality profiles and custom formats"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// We don't use the default menu items system for this screen's wizard steps
|
|
71
|
-
getMenuItems(): MenuItem[] {
|
|
72
|
-
return []
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
protected renderCustomContent(): boolean {
|
|
76
|
-
if (this.currentStep === "running" || this.currentStep === "done") {
|
|
77
|
-
this.renderResults()
|
|
78
|
-
return true
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (this.subStep === "select-apps") {
|
|
82
|
-
this.renderSelectApps()
|
|
83
|
-
return true
|
|
84
|
-
} else if (this.subStep === "select-profiles") {
|
|
85
|
-
this.renderSelectProfiles()
|
|
86
|
-
return true
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
protected handleCustomKeys(key: KeyEvent): boolean {
|
|
93
|
-
if (this.currentStep === "running") return true
|
|
94
|
-
if (this.currentStep === "done") return false // Base class handles return/esc
|
|
95
|
-
|
|
96
|
-
if (this.subStep === "select-apps") {
|
|
97
|
-
this.handleSelectAppsKeys(key)
|
|
98
|
-
return true
|
|
99
|
-
} else if (this.subStep === "select-profiles") {
|
|
100
|
-
this.handleSelectProfilesKeys(key)
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return false
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ============================================
|
|
108
|
-
// SELECTION RENDERING
|
|
109
|
-
// ============================================
|
|
110
|
-
|
|
111
|
-
private renderSelectApps(): void {
|
|
112
|
-
this.contentBox.add(
|
|
113
|
-
new TextRenderable(this.cliRenderer, {
|
|
114
|
-
content:
|
|
115
|
-
"Select apps to configure with TRaSH profiles:\n(Space to toggle, Enter to continue)\n\n",
|
|
116
|
-
fg: "#aaaaaa",
|
|
117
|
-
})
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
this.availableApps.forEach((appId, idx) => {
|
|
121
|
-
const app = getApp(appId)
|
|
122
|
-
const selected = this.selectedApps.get(appId)
|
|
123
|
-
const pointer = idx === this.currentIndex ? "→ " : " "
|
|
124
|
-
const check = selected ? "[✓]" : "[ ]"
|
|
125
|
-
const fg = idx === this.currentIndex ? "#50fa7b" : selected ? "#8be9fd" : "#6272a4"
|
|
126
|
-
|
|
127
|
-
this.contentBox.add(
|
|
128
|
-
new TextRenderable(this.cliRenderer, {
|
|
129
|
-
content: `${pointer}${check} ${app?.name || appId}\n`,
|
|
130
|
-
fg,
|
|
131
|
-
})
|
|
132
|
-
)
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private renderSelectProfiles(): void {
|
|
137
|
-
const selectedAppIds = this.availableApps.filter((id) => this.selectedApps.get(id))
|
|
138
|
-
|
|
139
|
-
this.contentBox.add(
|
|
140
|
-
new TextRenderable(this.cliRenderer, {
|
|
141
|
-
content:
|
|
142
|
-
"Select quality profile for each app:\n(↑↓ change profile, Tab next app, Enter apply)\n\n",
|
|
143
|
-
fg: "#aaaaaa",
|
|
144
|
-
})
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
selectedAppIds.forEach((appId, appIdx) => {
|
|
148
|
-
const app = getApp(appId)
|
|
149
|
-
const presets = getPresetsForApp(appId as "radarr" | "sonarr" | "lidarr")
|
|
150
|
-
const selectedPresetId = this.selectedProfiles.get(appId)
|
|
151
|
-
const isCurrent = appIdx === this.currentIndex
|
|
152
|
-
|
|
153
|
-
this.contentBox.add(
|
|
154
|
-
new TextRenderable(this.cliRenderer, {
|
|
155
|
-
content: `${isCurrent ? "→ " : " "}${app?.name}:\n`,
|
|
156
|
-
fg: isCurrent ? "#50fa7b" : "#8be9fd",
|
|
157
|
-
})
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
presets.forEach((preset) => {
|
|
161
|
-
const isSelected = preset.id === selectedPresetId
|
|
162
|
-
const bullet = isSelected ? "●" : "○"
|
|
163
|
-
this.contentBox.add(
|
|
164
|
-
new TextRenderable(this.cliRenderer, {
|
|
165
|
-
content: ` ${bullet} ${preset.name}\n`,
|
|
166
|
-
fg: isSelected ? "#f1fa8c" : "#6272a4",
|
|
167
|
-
})
|
|
168
|
-
)
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
protected renderResults(): void {
|
|
174
|
-
const headerText =
|
|
175
|
-
this.currentStep === "done" ? "✓ Configuration Complete!\n\n" : "Configuring...\n\n"
|
|
176
|
-
this.contentBox.add(
|
|
177
|
-
new TextRenderable(this.cliRenderer, {
|
|
178
|
-
content: headerText,
|
|
179
|
-
fg: this.currentStep === "done" ? "#50fa7b" : "#f1fa8c",
|
|
180
|
-
})
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
for (const result of this.results) {
|
|
184
|
-
let status = ""
|
|
185
|
-
let fg = "#aaaaaa"
|
|
186
|
-
switch (result.status) {
|
|
187
|
-
case "pending":
|
|
188
|
-
status = "⏳"
|
|
189
|
-
break
|
|
190
|
-
case "configuring":
|
|
191
|
-
status = "🔄"
|
|
192
|
-
fg = "#f1fa8c"
|
|
193
|
-
break
|
|
194
|
-
case "success":
|
|
195
|
-
status = "✓"
|
|
196
|
-
fg = "#50fa7b"
|
|
197
|
-
break
|
|
198
|
-
case "error":
|
|
199
|
-
status = "✗"
|
|
200
|
-
fg = "#ff5555"
|
|
201
|
-
break
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let content = `${status} ${result.appName}: ${result.profile}`
|
|
205
|
-
if (result.status === "success") {
|
|
206
|
-
content += ` (${result.cfCount} CF scores, naming configured)`
|
|
207
|
-
}
|
|
208
|
-
if (result.message) {
|
|
209
|
-
content += ` - ${result.message}`
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
this.contentBox.add(new TextRenderable(this.cliRenderer, { content: content + "\n", fg }))
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (this.currentStep === "done") {
|
|
216
|
-
this.contentBox.add(
|
|
217
|
-
new TextRenderable(this.cliRenderer, {
|
|
218
|
-
content: "\nPress Enter or Esc to continue...",
|
|
219
|
-
fg: "#6272a4",
|
|
220
|
-
})
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ============================================
|
|
226
|
-
// KEY HANDLING
|
|
227
|
-
// ============================================
|
|
228
|
-
|
|
229
|
-
private handleSelectAppsKeys(key: KeyEvent): void {
|
|
230
|
-
if (key.name === "up" && this.currentIndex > 0) {
|
|
231
|
-
this.currentIndex--
|
|
232
|
-
this.refreshContent()
|
|
233
|
-
} else if (key.name === "down" && this.currentIndex < this.availableApps.length - 1) {
|
|
234
|
-
this.currentIndex++
|
|
235
|
-
this.refreshContent()
|
|
236
|
-
} else if (key.name === "space") {
|
|
237
|
-
const app = this.availableApps[this.currentIndex]
|
|
238
|
-
this.selectedApps.set(app, !this.selectedApps.get(app))
|
|
239
|
-
this.refreshContent()
|
|
240
|
-
} else if (key.name === "return") {
|
|
241
|
-
const hasSelected = Array.from(this.selectedApps.values()).some((v) => v)
|
|
242
|
-
if (hasSelected) {
|
|
243
|
-
this.subStep = "select-profiles"
|
|
244
|
-
this.currentIndex = 0
|
|
245
|
-
this.refreshContent()
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private handleSelectProfilesKeys(key: KeyEvent): void {
|
|
251
|
-
const selectedAppIds = this.availableApps.filter((id) => this.selectedApps.get(id))
|
|
252
|
-
const app = selectedAppIds[this.currentIndex]
|
|
253
|
-
const presets = getPresetsForApp(app as "radarr" | "sonarr" | "lidarr")
|
|
254
|
-
|
|
255
|
-
if (key.name === "up") {
|
|
256
|
-
const current = this.selectedProfiles.get(app)
|
|
257
|
-
const idx = presets.findIndex((p) => p.id === current)
|
|
258
|
-
if (idx > 0) {
|
|
259
|
-
this.selectedProfiles.set(app, presets[idx - 1].id)
|
|
260
|
-
this.refreshContent()
|
|
261
|
-
}
|
|
262
|
-
} else if (key.name === "down") {
|
|
263
|
-
const current = this.selectedProfiles.get(app)
|
|
264
|
-
const idx = presets.findIndex((p) => p.id === current)
|
|
265
|
-
if (idx < presets.length - 1) {
|
|
266
|
-
this.selectedProfiles.set(app, presets[idx + 1].id)
|
|
267
|
-
this.refreshContent()
|
|
268
|
-
}
|
|
269
|
-
} else if (key.name === "tab" || key.name === "right") {
|
|
270
|
-
if (this.currentIndex < selectedAppIds.length - 1) {
|
|
271
|
-
this.currentIndex++
|
|
272
|
-
this.refreshContent()
|
|
273
|
-
}
|
|
274
|
-
} else if (key.name === "left" && this.currentIndex > 0) {
|
|
275
|
-
this.currentIndex--
|
|
276
|
-
this.refreshContent()
|
|
277
|
-
} else if (key.name === "return") {
|
|
278
|
-
this.startImport()
|
|
279
|
-
} else if (key.name === "backspace" || key.name === "b") {
|
|
280
|
-
this.subStep = "select-apps"
|
|
281
|
-
this.currentIndex = 0
|
|
282
|
-
this.refreshContent()
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// ============================================
|
|
287
|
-
// ACTIONS
|
|
288
|
-
// ============================================
|
|
289
|
-
|
|
290
|
-
private async startImport(): Promise<void> {
|
|
291
|
-
this.currentStep = "running"
|
|
292
|
-
this.results = []
|
|
293
|
-
|
|
294
|
-
const selectedAppIds = this.availableApps.filter((id) => this.selectedApps.get(id))
|
|
295
|
-
|
|
296
|
-
for (const appId of selectedAppIds) {
|
|
297
|
-
const appDef = getApp(appId)
|
|
298
|
-
const profileId = this.selectedProfiles.get(appId)
|
|
299
|
-
const preset = getPresetsForApp(appId as "radarr" | "sonarr" | "lidarr").find(
|
|
300
|
-
(p) => p.id === profileId
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
if (!appDef || !preset) continue
|
|
304
|
-
|
|
305
|
-
this.results.push({
|
|
306
|
-
name: appId, // Base class requires name
|
|
307
|
-
appId,
|
|
308
|
-
appName: appDef.name,
|
|
309
|
-
profile: preset.name,
|
|
310
|
-
cfCount: 0,
|
|
311
|
-
namingConfigured: false,
|
|
312
|
-
status: "configuring",
|
|
313
|
-
})
|
|
314
|
-
this.refreshContent()
|
|
315
|
-
|
|
316
|
-
try {
|
|
317
|
-
await this.configureApp(appId, preset)
|
|
318
|
-
const result = this.results.find((r) => r.appId === appId)
|
|
319
|
-
if (result) {
|
|
320
|
-
result.status = "success"
|
|
321
|
-
result.cfCount = Object.keys(preset.cfScores).length
|
|
322
|
-
result.namingConfigured = true
|
|
323
|
-
}
|
|
324
|
-
} catch (error) {
|
|
325
|
-
const result = this.results.find((r) => r.appId === appId)
|
|
326
|
-
if (result) {
|
|
327
|
-
result.status = "error"
|
|
328
|
-
result.message = error instanceof Error ? error.message : "Unknown error"
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
this.refreshContent()
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
this.currentStep = "done"
|
|
335
|
-
this.refreshContent()
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
private async configureApp(appId: AppId, preset: TRaSHProfilePreset): Promise<void> {
|
|
339
|
-
const appDef = getApp(appId)
|
|
340
|
-
if (!appDef) throw new Error("App not found")
|
|
341
|
-
|
|
342
|
-
const env = readEnvSync()
|
|
343
|
-
const apiKey = env[`API_KEY_${appId.toUpperCase()}`]
|
|
344
|
-
if (!apiKey) throw new Error("API key not found - run Extract API Keys first")
|
|
345
|
-
|
|
346
|
-
const port = this.config.apps.find((a) => a.id === appId)?.port || appDef.defaultPort
|
|
347
|
-
const apiVersion = appDef.rootFolder?.apiVersion || "v3"
|
|
348
|
-
const qpClient = new QualityProfileClient("localhost", port, apiKey, apiVersion)
|
|
349
|
-
const cfClient = new CustomFormatClient("localhost", port, apiKey, apiVersion)
|
|
350
|
-
|
|
351
|
-
if (appId === "lidarr") {
|
|
352
|
-
await cfClient.importCustomFormats(LIDARR_CUSTOM_FORMATS)
|
|
353
|
-
} else {
|
|
354
|
-
const cfCategories = ["unwanted", "misc"]
|
|
355
|
-
if (preset.id.includes("uhd") || preset.id.includes("2160")) {
|
|
356
|
-
cfCategories.push("hdr")
|
|
357
|
-
}
|
|
358
|
-
if (preset.id.includes("remux")) {
|
|
359
|
-
cfCategories.push("audio")
|
|
360
|
-
}
|
|
361
|
-
const cfNames = getCFNamesForCategories(appId as "radarr" | "sonarr", cfCategories)
|
|
362
|
-
const { cfs } = await CustomFormatClient.fetchTRaSHCustomFormats(
|
|
363
|
-
appId as "radarr" | "sonarr",
|
|
364
|
-
cfNames
|
|
365
|
-
)
|
|
366
|
-
await cfClient.importCustomFormats(cfs)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
await qpClient.createTRaSHProfile(
|
|
370
|
-
preset.name,
|
|
371
|
-
preset.cutoffQuality,
|
|
372
|
-
preset.allowedQualities,
|
|
373
|
-
preset.cfScores
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
if (appId !== "lidarr") {
|
|
377
|
-
const arrClient = new ArrApiClient("localhost", port, apiKey, apiVersion)
|
|
378
|
-
await arrClient.configureTRaSHNaming(appId as "radarr" | "sonarr")
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|