@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.
Files changed (109) hide show
  1. package/dist/index.js +29837 -0
  2. package/package.json +15 -15
  3. package/src/VersionInfo.ts +0 -12
  4. package/src/api/arr-api.ts +0 -521
  5. package/src/api/auto-setup-types.ts +0 -64
  6. package/src/api/base-api.ts +0 -216
  7. package/src/api/bazarr-api.ts +0 -297
  8. package/src/api/cloudflare-api.ts +0 -493
  9. package/src/api/custom-format-api.ts +0 -224
  10. package/src/api/grafana-api.ts +0 -230
  11. package/src/api/heimdall-api.ts +0 -141
  12. package/src/api/homarr-api.ts +0 -209
  13. package/src/api/huntarr-api.ts +0 -457
  14. package/src/api/index.ts +0 -9
  15. package/src/api/jellyfin-api.ts +0 -389
  16. package/src/api/jellyseerr-api.ts +0 -559
  17. package/src/api/maintainerr-api.ts +0 -461
  18. package/src/api/naming-config.ts +0 -101
  19. package/src/api/overseerr-api.ts +0 -392
  20. package/src/api/plex-api.ts +0 -308
  21. package/src/api/portainer-api.ts +0 -374
  22. package/src/api/profilarr-api.ts +0 -391
  23. package/src/api/prowlarr-api.ts +0 -488
  24. package/src/api/qbittorrent-api.ts +0 -300
  25. package/src/api/quality-profile-api.ts +0 -272
  26. package/src/api/tautulli-api.ts +0 -244
  27. package/src/api/uptime-kuma-api.ts +0 -343
  28. package/src/apps/categories.ts +0 -14
  29. package/src/apps/index.ts +0 -2
  30. package/src/apps/registry.ts +0 -1275
  31. package/src/compose/caddy-config.ts +0 -130
  32. package/src/compose/generator.ts +0 -287
  33. package/src/compose/index.ts +0 -7
  34. package/src/compose/templates.ts +0 -78
  35. package/src/compose/traefik-config.ts +0 -187
  36. package/src/config/bookmarks-generator.ts +0 -160
  37. package/src/config/defaults.ts +0 -37
  38. package/src/config/homepage-config.ts +0 -381
  39. package/src/config/index.ts +0 -6
  40. package/src/config/manager.ts +0 -150
  41. package/src/config/recyclarr-config.ts +0 -181
  42. package/src/config/schema.ts +0 -310
  43. package/src/config/slskd-config.ts +0 -86
  44. package/src/config/soularr-config.ts +0 -105
  45. package/src/config/trash-quality-definitions.ts +0 -213
  46. package/src/data/lidarr-custom-formats.ts +0 -127
  47. package/src/data/trash-profiles.ts +0 -333
  48. package/src/docker/client.ts +0 -312
  49. package/src/docker/index.ts +0 -18
  50. package/src/index.ts +0 -77
  51. package/src/setup/actions/arr-common.ts +0 -307
  52. package/src/setup/actions/dashboards.ts +0 -79
  53. package/src/setup/actions/download-clients.ts +0 -54
  54. package/src/setup/actions/jellyseerr.ts +0 -331
  55. package/src/setup/actions/media-servers.ts +0 -68
  56. package/src/setup/actions/monitoring.ts +0 -120
  57. package/src/setup/actions/prowlarr.ts +0 -137
  58. package/src/setup/actions/utilities.ts +0 -490
  59. package/src/setup/index.ts +0 -78
  60. package/src/setup/types.ts +0 -144
  61. package/src/structure/manager.ts +0 -129
  62. package/src/ui/App.ts +0 -111
  63. package/src/ui/components/ApplicationSelector.ts +0 -275
  64. package/src/ui/components/CredentialsForm.ts +0 -180
  65. package/src/ui/components/DownloadClientForm.ts +0 -114
  66. package/src/ui/components/FileEditor.ts +0 -91
  67. package/src/ui/components/FooterHint.ts +0 -119
  68. package/src/ui/components/PageLayout.ts +0 -160
  69. package/src/ui/components/UpdateNotification.ts +0 -102
  70. package/src/ui/index.ts +0 -7
  71. package/src/ui/screens/AdvancedSettings.ts +0 -188
  72. package/src/ui/screens/ApiKeyViewer.ts +0 -511
  73. package/src/ui/screens/AppConfigurator.ts +0 -543
  74. package/src/ui/screens/AppManager.ts +0 -284
  75. package/src/ui/screens/ContainerControl.ts +0 -581
  76. package/src/ui/screens/LogsViewer.ts +0 -478
  77. package/src/ui/screens/MainMenu.ts +0 -299
  78. package/src/ui/screens/MonitorDashboard.ts +0 -881
  79. package/src/ui/screens/SecretsEditor.ts +0 -216
  80. package/src/ui/screens/SettingsScreen.ts +0 -633
  81. package/src/ui/screens/index.ts +0 -22
  82. package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
  83. package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
  84. package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
  85. package/src/ui/screens/setup/HomepageSetup.ts +0 -165
  86. package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
  87. package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
  88. package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
  89. package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
  90. package/src/ui/screens/setup/QuickSetup.ts +0 -1169
  91. package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
  92. package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
  93. package/src/utils/arch.ts +0 -78
  94. package/src/utils/browser.ts +0 -27
  95. package/src/utils/categories.ts +0 -61
  96. package/src/utils/debug.ts +0 -84
  97. package/src/utils/env.ts +0 -138
  98. package/src/utils/index.ts +0 -8
  99. package/src/utils/logs.ts +0 -119
  100. package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
  101. package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
  102. package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
  103. package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
  104. package/src/utils/migrations.ts +0 -165
  105. package/src/utils/password.ts +0 -42
  106. package/src/utils/paths.ts +0 -33
  107. package/src/utils/unraid.ts +0 -103
  108. package/src/utils/update-checker.ts +0 -210
  109. package/src/utils/url-utils.ts +0 -38
@@ -1,559 +0,0 @@
1
- /**
2
- * Jellyseerr API Client
3
- * Handles setup wizard automation and service configuration
4
- *
5
- * Based on Jellyseerr source code analysis:
6
- * - Auth endpoint: POST /api/v1/auth/jellyfin
7
- * - Setup mode: requires hostname, port, serverType (2=Jellyfin, 3=Emby), useSsl
8
- * - Login mode: only requires username and password (when server already configured)
9
- */
10
-
11
- import type { AutoSetupOptions, AutoSetupResult, IAutoSetupClient } from "./auto-setup-types"
12
- import { debugLog } from "~/utils/debug"
13
- import { BaseApiClient } from "./base-api"
14
-
15
- // ==========================================
16
- // Enums (from Jellyseerr server/constants/server.ts)
17
- // ==========================================
18
-
19
- export enum MediaServerType {
20
- PLEX = 1,
21
- JELLYFIN = 2,
22
- EMBY = 3,
23
- NOT_CONFIGURED = 4,
24
- }
25
-
26
- // ==========================================
27
- // Types
28
- // ==========================================
29
-
30
- export interface JellyseerrPublicSettings {
31
- initialized: boolean
32
- }
33
-
34
- export interface JellyseerrMainSettings {
35
- apiKey: string
36
- appLanguage: string
37
- applicationTitle: string
38
- applicationUrl: string
39
- mediaServerType: number
40
- localLogin: boolean
41
- newPlexLogin: boolean
42
- defaultPermissions: number
43
- }
44
-
45
- export interface JellyseerrJellyfinSettings {
46
- name?: string
47
- ip?: string
48
- hostname?: string
49
- port?: number
50
- useSsl?: boolean
51
- urlBase?: string
52
- externalHostname?: string
53
- adminUser?: string
54
- adminPass?: string
55
- serverId?: string
56
- apiKey?: string
57
- libraries?: JellyseerrLibrary[]
58
- }
59
-
60
- export interface JellyseerrLibrary {
61
- id: string
62
- name: string
63
- enabled: boolean
64
- }
65
-
66
- export interface JellyseerrUser {
67
- id: number
68
- email: string
69
- username?: string
70
- jellyfinUsername?: string
71
- jellyfinUserId?: string
72
- userType: number
73
- permissions: number
74
- avatar?: string
75
- }
76
-
77
- export interface JellyseerrRadarrSettings {
78
- id?: number
79
- name: string
80
- hostname: string
81
- port: number
82
- apiKey: string
83
- useSsl: boolean
84
- baseUrl?: string
85
- activeProfileId: number
86
- activeProfileName: string
87
- activeDirectory: string
88
- is4k: boolean
89
- minimumAvailability: string
90
- isDefault: boolean
91
- syncEnabled?: boolean
92
- preventSearch?: boolean
93
- externalUrl?: string
94
- }
95
-
96
- export interface JellyseerrSonarrSettings {
97
- id?: number
98
- name: string
99
- hostname: string
100
- port: number
101
- apiKey: string
102
- useSsl: boolean
103
- baseUrl?: string
104
- activeProfileId: number
105
- activeProfileName: string
106
- activeDirectory: string
107
- activeLanguageProfileId?: number
108
- is4k: boolean
109
- enableSeasonFolders: boolean
110
- isDefault: boolean
111
- syncEnabled?: boolean
112
- preventSearch?: boolean
113
- externalUrl?: string
114
- }
115
-
116
- export interface ServiceProfile {
117
- id: number
118
- name: string
119
- }
120
-
121
- export interface ServiceTestResult {
122
- profiles: ServiceProfile[]
123
- rootFolders?: { id: number; path: string }[]
124
- }
125
-
126
- interface JellyfinSetupAuthRequest {
127
- username: string
128
- password: string
129
- hostname: string
130
- port: number
131
- useSsl: boolean
132
- urlBase: string
133
- serverType: MediaServerType
134
- email?: string
135
- }
136
-
137
- interface JellyfinLoginRequest {
138
- username: string
139
- password: string
140
- }
141
-
142
- // ==========================================
143
- // Client
144
- // ==========================================
145
-
146
- export class JellyseerrClient extends BaseApiClient implements IAutoSetupClient {
147
- protected readonly logPrefix = "Jellyseerr"
148
- private cookie?: string
149
-
150
- constructor(host: string, port: number) {
151
- super(host, port)
152
- }
153
-
154
- private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
155
- const url = `${this.baseUrl}/api/v1${endpoint}`
156
- const headers: Record<string, string> = {
157
- "Content-Type": "application/json",
158
- ...(options.headers as Record<string, string>),
159
- }
160
-
161
- if (this.cookie) {
162
- headers["Cookie"] = this.cookie
163
- }
164
-
165
- debugLog("Jellyseerr", `${options.method || "GET"} ${endpoint}`)
166
-
167
- const response = await fetch(url, { ...options, headers })
168
-
169
- const setCookie = response.headers.get("set-cookie")
170
- if (setCookie) {
171
- this.cookie = setCookie.split(";")[0]
172
- }
173
-
174
- if (!response.ok) {
175
- const text = await response.text()
176
- throw new Error(`Jellyseerr API error: ${response.status} - ${text}`)
177
- }
178
-
179
- const contentType = response.headers.get("content-type")
180
- if (contentType?.includes("application/json")) {
181
- return response.json()
182
- }
183
- return {} as T
184
- }
185
-
186
- // ==========================================
187
- // Health & Status
188
- // ==========================================
189
-
190
- async isHealthy(): Promise<boolean> {
191
- try {
192
- await this.request<{ version: string }>("/status")
193
- return true
194
- } catch {
195
- return false
196
- }
197
- }
198
-
199
- async isInitialized(): Promise<boolean> {
200
- try {
201
- const settings = await this.request<JellyseerrPublicSettings>("/settings/public")
202
- return settings.initialized
203
- } catch {
204
- return false
205
- }
206
- }
207
-
208
- // ==========================================
209
- // Main Settings
210
- // ==========================================
211
-
212
- async getMainSettings(): Promise<JellyseerrMainSettings> {
213
- return this.request<JellyseerrMainSettings>("/settings/main")
214
- }
215
-
216
- async updateMainSettings(
217
- settings: Partial<JellyseerrMainSettings>
218
- ): Promise<JellyseerrMainSettings> {
219
- return this.request<JellyseerrMainSettings>("/settings/main", {
220
- method: "POST",
221
- body: JSON.stringify(settings),
222
- })
223
- }
224
-
225
- async initialize(): Promise<{ initialized: boolean }> {
226
- return this.request<{ initialized: boolean }>("/settings/initialize", { method: "POST" })
227
- }
228
-
229
- async setApplicationUrl(applicationUrl: string): Promise<JellyseerrMainSettings> {
230
- return this.updateMainSettings({ applicationUrl })
231
- }
232
-
233
- // ==========================================
234
- // Jellyfin Configuration
235
- // ==========================================
236
-
237
- async getJellyfinSettings(): Promise<JellyseerrJellyfinSettings> {
238
- return this.request<JellyseerrJellyfinSettings>("/settings/jellyfin")
239
- }
240
-
241
- async updateJellyfinSettings(
242
- settings: Partial<JellyseerrJellyfinSettings>
243
- ): Promise<JellyseerrJellyfinSettings> {
244
- return this.request<JellyseerrJellyfinSettings>("/settings/jellyfin", {
245
- method: "POST",
246
- body: JSON.stringify(settings),
247
- })
248
- }
249
-
250
- async syncJellyfinLibraries(): Promise<JellyseerrLibrary[]> {
251
- return this.request<JellyseerrLibrary[]>("/settings/jellyfin/library?sync=true")
252
- }
253
-
254
- async enableLibraries(libraryIds: string[]): Promise<JellyseerrLibrary[]> {
255
- const enable = libraryIds.join(",")
256
- return this.request<JellyseerrLibrary[]>(
257
- `/settings/jellyfin/library?enable=${encodeURIComponent(enable)}`
258
- )
259
- }
260
-
261
- // ==========================================
262
- // Authentication
263
- // ==========================================
264
-
265
- async authenticateJellyfin(
266
- username: string,
267
- password: string,
268
- hostname: string,
269
- port: number,
270
- email?: string
271
- ): Promise<JellyseerrUser> {
272
- const setupPayload: JellyfinSetupAuthRequest = {
273
- username,
274
- password,
275
- hostname,
276
- port,
277
- useSsl: false,
278
- urlBase: "",
279
- serverType: MediaServerType.JELLYFIN,
280
- email: email || `${username}@local`,
281
- }
282
-
283
- try {
284
- return await this.request<JellyseerrUser>("/auth/jellyfin", {
285
- method: "POST",
286
- body: JSON.stringify(setupPayload),
287
- })
288
- } catch (err: unknown) {
289
- const message = err instanceof Error ? err.message : String(err)
290
-
291
- if (
292
- message.includes("already configured") ||
293
- message.includes("hostname already configured")
294
- ) {
295
- const loginPayload: JellyfinLoginRequest = { username, password }
296
- return this.request<JellyseerrUser>("/auth/jellyfin", {
297
- method: "POST",
298
- body: JSON.stringify(loginPayload),
299
- })
300
- }
301
-
302
- if (message.includes("NO_ADMIN_USER") || message.includes("NotAdmin")) {
303
- throw new Error(`Jellyfin user "${username}" is not an administrator.`, { cause: err })
304
- }
305
-
306
- if (message.includes("InvalidCredentials") || message.includes("401")) {
307
- throw new Error(`Invalid Jellyfin credentials for user "${username}".`, { cause: err })
308
- }
309
-
310
- if (message.includes("InvalidUrl") || message.includes("INVALID_URL")) {
311
- throw new Error(`Cannot reach Jellyfin at ${hostname}:${port}.`, { cause: err })
312
- }
313
-
314
- throw err
315
- }
316
- }
317
-
318
- async authenticatePlex(authToken: string): Promise<JellyseerrUser> {
319
- return this.request<JellyseerrUser>("/auth/plex", {
320
- method: "POST",
321
- body: JSON.stringify({ authToken }),
322
- })
323
- }
324
-
325
- // ==========================================
326
- // Radarr Configuration
327
- // ==========================================
328
-
329
- async getRadarrSettings(): Promise<JellyseerrRadarrSettings[]> {
330
- return this.request<JellyseerrRadarrSettings[]>("/settings/radarr")
331
- }
332
-
333
- async testRadarr(config: {
334
- hostname: string
335
- port: number
336
- apiKey: string
337
- useSsl: boolean
338
- baseUrl?: string
339
- }): Promise<ServiceTestResult> {
340
- return this.request<ServiceTestResult>("/settings/radarr/test", {
341
- method: "POST",
342
- body: JSON.stringify(config),
343
- })
344
- }
345
-
346
- async addRadarr(settings: JellyseerrRadarrSettings): Promise<JellyseerrRadarrSettings> {
347
- return this.request<JellyseerrRadarrSettings>("/settings/radarr", {
348
- method: "POST",
349
- body: JSON.stringify(settings),
350
- })
351
- }
352
-
353
- async updateRadarr(
354
- id: number,
355
- settings: Partial<JellyseerrRadarrSettings>
356
- ): Promise<JellyseerrRadarrSettings> {
357
- return this.request<JellyseerrRadarrSettings>(`/settings/radarr/${id}`, {
358
- method: "PUT",
359
- body: JSON.stringify(settings),
360
- })
361
- }
362
-
363
- // ==========================================
364
- // Sonarr Configuration
365
- // ==========================================
366
-
367
- async getSonarrSettings(): Promise<JellyseerrSonarrSettings[]> {
368
- return this.request<JellyseerrSonarrSettings[]>("/settings/sonarr")
369
- }
370
-
371
- async testSonarr(config: {
372
- hostname: string
373
- port: number
374
- apiKey: string
375
- useSsl: boolean
376
- baseUrl?: string
377
- }): Promise<ServiceTestResult> {
378
- return this.request<ServiceTestResult>("/settings/sonarr/test", {
379
- method: "POST",
380
- body: JSON.stringify(config),
381
- })
382
- }
383
-
384
- async addSonarr(settings: JellyseerrSonarrSettings): Promise<JellyseerrSonarrSettings> {
385
- return this.request<JellyseerrSonarrSettings>("/settings/sonarr", {
386
- method: "POST",
387
- body: JSON.stringify(settings),
388
- })
389
- }
390
-
391
- async updateSonarr(
392
- id: number,
393
- settings: Partial<JellyseerrSonarrSettings>
394
- ): Promise<JellyseerrSonarrSettings> {
395
- return this.request<JellyseerrSonarrSettings>(`/settings/sonarr/${id}`, {
396
- method: "PUT",
397
- body: JSON.stringify(settings),
398
- })
399
- }
400
-
401
- // ==========================================
402
- // Full Setup Wizard
403
- // ==========================================
404
-
405
- async runJellyfinSetup(
406
- jellyfinHostname: string,
407
- port: number,
408
- username: string,
409
- password: string,
410
- email?: string
411
- ): Promise<string> {
412
- await this.authenticateJellyfin(username, password, jellyfinHostname, port, email)
413
-
414
- const fullUrl = `http://${jellyfinHostname}:${port}`
415
- await this.updateJellyfinSettings({
416
- hostname: fullUrl,
417
- adminUser: username,
418
- adminPass: password,
419
- })
420
-
421
- const libraries = await this.syncJellyfinLibraries()
422
- const libraryIds = libraries.map((lib) => lib.id)
423
- if (libraryIds.length > 0) {
424
- await this.enableLibraries(libraryIds)
425
- }
426
-
427
- const mainSettings = await this.getMainSettings()
428
- return mainSettings.apiKey
429
- }
430
-
431
- async configureRadarr(
432
- hostname: string,
433
- port: number,
434
- apiKey: string,
435
- rootFolder: string,
436
- externalUrl?: string
437
- ): Promise<JellyseerrRadarrSettings | null> {
438
- try {
439
- const existingConfigs = await this.getRadarrSettings()
440
- const existingConfig = existingConfigs.find((c) => c.hostname === hostname && c.port === port)
441
-
442
- if (existingConfig?.id) {
443
- const { id, ...configWithoutId } = existingConfig
444
- return await this.updateRadarr(id, { ...configWithoutId, externalUrl })
445
- }
446
-
447
- const testResult = await this.testRadarr({ hostname, port, apiKey, useSsl: false })
448
- if (!testResult.profiles || testResult.profiles.length === 0) {
449
- return null
450
- }
451
-
452
- const profile = testResult.profiles[0]
453
- return await this.addRadarr({
454
- name: "Radarr",
455
- hostname,
456
- port,
457
- apiKey,
458
- useSsl: false,
459
- activeProfileId: profile.id,
460
- activeProfileName: profile.name,
461
- activeDirectory: rootFolder,
462
- is4k: false,
463
- minimumAvailability: "announced",
464
- isDefault: true,
465
- externalUrl,
466
- })
467
- } catch {
468
- return null
469
- }
470
- }
471
-
472
- async configureSonarr(
473
- hostname: string,
474
- port: number,
475
- apiKey: string,
476
- rootFolder: string,
477
- externalUrl?: string
478
- ): Promise<JellyseerrSonarrSettings | null> {
479
- try {
480
- const existingConfigs = await this.getSonarrSettings()
481
- const existingConfig = existingConfigs.find((c) => c.hostname === hostname && c.port === port)
482
-
483
- if (existingConfig?.id) {
484
- const { id, ...configWithoutId } = existingConfig
485
- return await this.updateSonarr(id, { ...configWithoutId, externalUrl })
486
- }
487
-
488
- const testResult = await this.testSonarr({ hostname, port, apiKey, useSsl: false })
489
- if (!testResult.profiles || testResult.profiles.length === 0) {
490
- return null
491
- }
492
-
493
- const profile = testResult.profiles[0]
494
- return await this.addSonarr({
495
- name: "Sonarr",
496
- hostname,
497
- port,
498
- apiKey,
499
- useSsl: false,
500
- activeProfileId: profile.id,
501
- activeProfileName: profile.name,
502
- activeDirectory: rootFolder,
503
- is4k: false,
504
- enableSeasonFolders: true,
505
- isDefault: true,
506
- externalUrl,
507
- })
508
- } catch {
509
- return null
510
- }
511
- }
512
-
513
- // ==========================================
514
- // Auto-Setup
515
- // ==========================================
516
-
517
- async setup(options: AutoSetupOptions): Promise<AutoSetupResult> {
518
- const { username, password, env } = options
519
-
520
- try {
521
- const healthy = await this.isHealthy()
522
- if (!healthy) {
523
- return { success: false, message: "Jellyseerr not reachable" }
524
- }
525
-
526
- const jellyfinHost = env["JELLYFIN_HOST"] || "jellyfin"
527
- const jellyfinPort = parseInt(env["JELLYFIN_PORT"] || "8096", 10)
528
-
529
- const initialized = await this.isInitialized()
530
- if (initialized) {
531
- try {
532
- await this.authenticateJellyfin(username, password, jellyfinHost, jellyfinPort)
533
- } catch {
534
- return { success: true, message: "Already configured (could not authenticate)" }
535
- }
536
-
537
- const settings = await this.getMainSettings()
538
- return {
539
- success: true,
540
- message: "Already configured",
541
- data: { apiKey: settings.apiKey },
542
- envUpdates: { API_KEY_JELLYSEERR: settings.apiKey },
543
- }
544
- }
545
-
546
- const apiKey = await this.runJellyfinSetup(jellyfinHost, jellyfinPort, username, password)
547
- await this.initialize()
548
-
549
- return {
550
- success: true,
551
- message: "Jellyseerr configured with Jellyfin",
552
- data: { apiKey },
553
- envUpdates: { API_KEY_JELLYSEERR: apiKey },
554
- }
555
- } catch (error) {
556
- return { success: false, message: `${error}` }
557
- }
558
- }
559
- }