@muhammedaksam/easiarr 1.3.5 → 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 (94) hide show
  1. package/README.md +8 -5
  2. package/dist/index.js +29837 -0
  3. package/package.json +18 -16
  4. package/src/VersionInfo.ts +0 -12
  5. package/src/api/arr-api.ts +0 -508
  6. package/src/api/auto-setup-types.ts +0 -64
  7. package/src/api/bazarr-api.ts +0 -356
  8. package/src/api/cloudflare-api.ts +0 -577
  9. package/src/api/custom-format-api.ts +0 -212
  10. package/src/api/grafana-api.ts +0 -314
  11. package/src/api/heimdall-api.ts +0 -209
  12. package/src/api/homarr-api.ts +0 -296
  13. package/src/api/huntarr-api.ts +0 -622
  14. package/src/api/index.ts +0 -1
  15. package/src/api/jellyfin-api.ts +0 -473
  16. package/src/api/jellyseerr-api.ts +0 -660
  17. package/src/api/maintainerr-api.ts +0 -731
  18. package/src/api/naming-config.ts +0 -93
  19. package/src/api/overseerr-api.ts +0 -513
  20. package/src/api/plex-api.ts +0 -358
  21. package/src/api/portainer-api.ts +0 -467
  22. package/src/api/profilarr-api.ts +0 -380
  23. package/src/api/prowlarr-api.ts +0 -859
  24. package/src/api/qbittorrent-api.ts +0 -339
  25. package/src/api/quality-profile-api.ts +0 -269
  26. package/src/api/tautulli-api.ts +0 -277
  27. package/src/api/uptime-kuma-api.ts +0 -342
  28. package/src/apps/categories.ts +0 -14
  29. package/src/apps/index.ts +0 -2
  30. package/src/apps/registry.ts +0 -1255
  31. package/src/compose/caddy-config.ts +0 -129
  32. package/src/compose/generator.ts +0 -262
  33. package/src/compose/index.ts +0 -3
  34. package/src/compose/templates.ts +0 -78
  35. package/src/compose/traefik-config.ts +0 -185
  36. package/src/config/bookmarks-generator.ts +0 -149
  37. package/src/config/defaults.ts +0 -37
  38. package/src/config/homepage-config.ts +0 -376
  39. package/src/config/index.ts +0 -3
  40. package/src/config/manager.ts +0 -149
  41. package/src/config/recyclarr-config.ts +0 -179
  42. package/src/config/schema.ts +0 -310
  43. package/src/config/slskd-config.ts +0 -85
  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 -326
  48. package/src/docker/client.ts +0 -291
  49. package/src/docker/index.ts +0 -1
  50. package/src/index.ts +0 -77
  51. package/src/structure/manager.ts +0 -128
  52. package/src/ui/App.ts +0 -109
  53. package/src/ui/components/ApplicationSelector.ts +0 -272
  54. package/src/ui/components/FileEditor.ts +0 -91
  55. package/src/ui/components/FooterHint.ts +0 -119
  56. package/src/ui/components/PageLayout.ts +0 -152
  57. package/src/ui/components/UpdateNotification.ts +0 -101
  58. package/src/ui/index.ts +0 -1
  59. package/src/ui/screens/AdvancedSettings.ts +0 -181
  60. package/src/ui/screens/ApiKeyViewer.ts +0 -500
  61. package/src/ui/screens/AppConfigurator.ts +0 -731
  62. package/src/ui/screens/AppManager.ts +0 -276
  63. package/src/ui/screens/CloudflaredSetup.ts +0 -974
  64. package/src/ui/screens/ContainerControl.ts +0 -579
  65. package/src/ui/screens/FullAutoSetup.ts +0 -1719
  66. package/src/ui/screens/HomepageSetup.ts +0 -306
  67. package/src/ui/screens/JellyfinSetup.ts +0 -471
  68. package/src/ui/screens/JellyseerrSetup.ts +0 -612
  69. package/src/ui/screens/LogsViewer.ts +0 -468
  70. package/src/ui/screens/MainMenu.ts +0 -288
  71. package/src/ui/screens/MonitorDashboard.ts +0 -866
  72. package/src/ui/screens/ProwlarrSetup.ts +0 -623
  73. package/src/ui/screens/QBittorrentSetup.ts +0 -268
  74. package/src/ui/screens/QuickSetup.ts +0 -1161
  75. package/src/ui/screens/RecyclarrSetup.ts +0 -418
  76. package/src/ui/screens/SecretsEditor.ts +0 -214
  77. package/src/ui/screens/SettingsScreen.ts +0 -605
  78. package/src/ui/screens/TRaSHProfileSetup.ts +0 -395
  79. package/src/ui/screens/index.ts +0 -4
  80. package/src/util/arch.ts +0 -78
  81. package/src/utils/browser.ts +0 -26
  82. package/src/utils/categories.ts +0 -59
  83. package/src/utils/debug.ts +0 -91
  84. package/src/utils/env.ts +0 -137
  85. package/src/utils/logs.ts +0 -118
  86. package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -101
  87. package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -90
  88. package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -44
  89. package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -107
  90. package/src/utils/migrations.ts +0 -164
  91. package/src/utils/password.ts +0 -42
  92. package/src/utils/unraid.ts +0 -101
  93. package/src/utils/update-checker.ts +0 -210
  94. package/src/utils/url-utils.ts +0 -38
@@ -1,473 +0,0 @@
1
- /**
2
- * Jellyfin API Client
3
- * Handles setup wizard automation and media library management
4
- */
5
-
6
- import { debugLog } from "../utils/debug"
7
- import type { IAutoSetupClient, AutoSetupOptions, AutoSetupResult } from "./auto-setup-types"
8
-
9
- // ==========================================
10
- // Startup Wizard Types
11
- // ==========================================
12
-
13
- export interface StartupConfiguration {
14
- UICulture?: string
15
- MetadataCountryCode?: string
16
- PreferredMetadataLanguage?: string
17
- }
18
-
19
- export interface StartupUser {
20
- Name: string
21
- Password: string
22
- }
23
-
24
- export interface StartupRemoteAccess {
25
- EnableRemoteAccess: boolean
26
- EnableAutomaticPortMapping: boolean
27
- }
28
-
29
- // ==========================================
30
- // Library Types
31
- // ==========================================
32
-
33
- export interface VirtualFolderInfo {
34
- Name: string
35
- Locations: string[]
36
- CollectionType: LibraryType
37
- ItemId?: string
38
- }
39
-
40
- export type LibraryType =
41
- | "movies"
42
- | "tvshows"
43
- | "music"
44
- | "books"
45
- | "homevideos"
46
- | "musicvideos"
47
- | "photos"
48
- | "playlists"
49
- | "boxsets"
50
-
51
- export interface AddVirtualFolderOptions {
52
- name: string
53
- collectionType: LibraryType
54
- paths: string[]
55
- refreshLibrary?: boolean
56
- }
57
-
58
- // ==========================================
59
- // System Types
60
- // ==========================================
61
-
62
- export interface SystemInfo {
63
- ServerName: string
64
- Version: string
65
- Id: string
66
- OperatingSystem: string
67
- StartupWizardCompleted: boolean
68
- }
69
-
70
- // ==========================================
71
- // User Types
72
- // ==========================================
73
-
74
- export interface UserPolicy {
75
- IsAdministrator: boolean
76
- IsHidden: boolean
77
- IsDisabled: boolean
78
- EnableRemoteAccess: boolean
79
- AuthenticationProviderId?: string
80
- PasswordResetProviderId?: string
81
- [key: string]: unknown // Allow other properties
82
- }
83
-
84
- export interface UserDto {
85
- Id: string
86
- Name?: string
87
- ServerId?: string
88
- HasPassword: boolean
89
- LastLoginDate?: string
90
- Policy?: UserPolicy
91
- [key: string]: unknown // Allow other properties
92
- }
93
-
94
- export interface AuthResult {
95
- AccessToken: string
96
- ServerId: string
97
- User: UserDto
98
- }
99
-
100
- // ==========================================
101
- // Jellyfin Client
102
- // ==========================================
103
-
104
- export class JellyfinClient implements IAutoSetupClient {
105
- // ==========================================
106
- // User Management
107
- // ==========================================
108
-
109
- /**
110
- * Get a user's details
111
- */
112
- async getUser(userId: string): Promise<UserDto> {
113
- return this.request<UserDto>(`/Users/${userId}`)
114
- }
115
-
116
- /**
117
- * Update a user's policy (permissions)
118
- */
119
- async updateUserPolicy(userId: string, policy: Partial<UserPolicy>): Promise<void> {
120
- await this.request(`/Users/${userId}/Policy`, {
121
- method: "POST",
122
- body: JSON.stringify(policy),
123
- })
124
- }
125
-
126
- private baseUrl: string
127
- private accessToken?: string
128
-
129
- constructor(host: string, port: number, accessToken?: string) {
130
- this.baseUrl = `http://${host}:${port}`
131
- this.accessToken = accessToken
132
- }
133
-
134
- private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
135
- const url = `${this.baseUrl}${endpoint}`
136
- const headers: Record<string, string> = {
137
- "Content-Type": "application/json",
138
- // Jellyfin requires client identification
139
- "X-Emby-Authorization":
140
- 'MediaBrowser Client="easiarr", Device="Server", DeviceId="easiarr-setup", Version="1.0.0"' +
141
- (this.accessToken ? `, Token="${this.accessToken}"` : ""),
142
- ...((options.headers as Record<string, string>) || {}),
143
- }
144
-
145
- debugLog("JellyfinAPI", `${options.method || "GET"} ${url}`)
146
- if (options.body) {
147
- debugLog("JellyfinAPI", `Request Body: ${options.body}`)
148
- }
149
-
150
- const response = await fetch(url, { ...options, headers })
151
- const text = await response.text()
152
-
153
- debugLog("JellyfinAPI", `Response ${response.status} from ${endpoint}`)
154
- if (text && text.length < 2000) {
155
- debugLog("JellyfinAPI", `Response Body: ${text}`)
156
- }
157
-
158
- if (!response.ok) {
159
- throw new Error(`Jellyfin API request failed: ${response.status} ${response.statusText} - ${text}`)
160
- }
161
-
162
- if (!text) return {} as T
163
- return JSON.parse(text) as T
164
- }
165
-
166
- // ==========================================
167
- // Setup Wizard Methods (no auth required)
168
- // ==========================================
169
-
170
- /**
171
- * Check if the startup wizard has been completed
172
- */
173
- async isStartupComplete(): Promise<boolean> {
174
- try {
175
- const info = await this.request<SystemInfo>("/System/Info/Public")
176
- return info.StartupWizardCompleted === true
177
- } catch {
178
- return false
179
- }
180
- }
181
-
182
- /**
183
- * Get current startup configuration
184
- */
185
- async getStartupConfiguration(): Promise<StartupConfiguration> {
186
- return this.request<StartupConfiguration>("/Startup/Configuration")
187
- }
188
-
189
- /**
190
- * Set startup configuration (metadata language, UI culture)
191
- */
192
- async setStartupConfiguration(config: StartupConfiguration): Promise<void> {
193
- await this.request("/Startup/Configuration", {
194
- method: "POST",
195
- body: JSON.stringify(config),
196
- })
197
- }
198
-
199
- /**
200
- * Get the first user (initializes user in database)
201
- */
202
- async getFirstUser(): Promise<{ Name: string; Password: string }> {
203
- return this.request<{ Name: string; Password: string }>("/Startup/FirstUser")
204
- }
205
-
206
- /**
207
- * Create/update the initial admin user
208
- * Must call getFirstUser first to initialize the user
209
- */
210
- async createAdminUser(name: string, password: string): Promise<void> {
211
- // First, get the initial user (this initializes the user in the database)
212
- await this.getFirstUser()
213
-
214
- // Then update with our credentials
215
- const user: StartupUser = { Name: name, Password: password }
216
- await this.request("/Startup/User", {
217
- method: "POST",
218
- body: JSON.stringify(user),
219
- })
220
- }
221
-
222
- /**
223
- * Configure remote access settings
224
- */
225
- async setRemoteAccess(enableRemote: boolean, enableUPnP: boolean = false): Promise<void> {
226
- const config: StartupRemoteAccess = {
227
- EnableRemoteAccess: enableRemote,
228
- EnableAutomaticPortMapping: enableUPnP,
229
- }
230
- await this.request("/Startup/RemoteAccess", {
231
- method: "POST",
232
- body: JSON.stringify(config),
233
- })
234
- }
235
-
236
- /**
237
- * Complete the startup wizard
238
- */
239
- async completeStartup(): Promise<void> {
240
- await this.request("/Startup/Complete", {
241
- method: "POST",
242
- })
243
- }
244
-
245
- /**
246
- * Run the full setup wizard with sensible defaults
247
- */
248
- async runSetupWizard(
249
- adminName: string,
250
- adminPassword: string,
251
- options: {
252
- uiCulture?: string
253
- metadataCountry?: string
254
- metadataLanguage?: string
255
- enableRemoteAccess?: boolean
256
- enableUPnP?: boolean
257
- } = {}
258
- ): Promise<void> {
259
- const {
260
- uiCulture = "en-US",
261
- metadataCountry = "US",
262
- metadataLanguage = "en",
263
- enableRemoteAccess = true,
264
- enableUPnP = false,
265
- } = options
266
-
267
- // Step 1: Set UI culture and metadata language
268
- await this.setStartupConfiguration({
269
- UICulture: uiCulture,
270
- MetadataCountryCode: metadataCountry,
271
- PreferredMetadataLanguage: metadataLanguage,
272
- })
273
-
274
- // Step 2: Create admin user
275
- await this.createAdminUser(adminName, adminPassword)
276
-
277
- // Step 3: Configure remote access
278
- await this.setRemoteAccess(enableRemoteAccess, enableUPnP)
279
-
280
- // Step 4: Complete the wizard
281
- await this.completeStartup()
282
- }
283
-
284
- // ==========================================
285
- // Authentication (post-setup)
286
- // ==========================================
287
-
288
- /**
289
- * Authenticate with username/password and get access token
290
- */
291
- async authenticate(username: string, password: string): Promise<AuthResult> {
292
- const result = await this.request<AuthResult>("/Users/AuthenticateByName", {
293
- method: "POST",
294
- body: JSON.stringify({
295
- Username: username,
296
- Pw: password,
297
- }),
298
- })
299
-
300
- // Store token for subsequent requests
301
- this.accessToken = result.AccessToken
302
- return result
303
- }
304
-
305
- /**
306
- * Set access token directly (if already known)
307
- */
308
- setAccessToken(token: string): void {
309
- this.accessToken = token
310
- }
311
-
312
- // ==========================================
313
- // Library Management (requires auth)
314
- // ==========================================
315
-
316
- /**
317
- * Get all virtual folders (media libraries)
318
- */
319
- async getVirtualFolders(): Promise<VirtualFolderInfo[]> {
320
- return this.request<VirtualFolderInfo[]>("/Library/VirtualFolders")
321
- }
322
-
323
- /**
324
- * Add a new media library
325
- */
326
- async addVirtualFolder(options: AddVirtualFolderOptions): Promise<void> {
327
- const params = new URLSearchParams({
328
- name: options.name,
329
- collectionType: options.collectionType,
330
- refreshLibrary: String(options.refreshLibrary ?? true),
331
- })
332
-
333
- // Paths need to be added to the body
334
- await this.request(`/Library/VirtualFolders?${params.toString()}`, {
335
- method: "POST",
336
- body: JSON.stringify({
337
- LibraryOptions: {
338
- PathInfos: options.paths.map((path) => ({ Path: path })),
339
- },
340
- }),
341
- })
342
- }
343
-
344
- /**
345
- * Add default media libraries based on common media stack paths
346
- */
347
- async addDefaultLibraries(): Promise<void> {
348
- const defaultLibraries: AddVirtualFolderOptions[] = [
349
- { name: "Movies", collectionType: "movies", paths: ["/data/media/movies"] },
350
- { name: "TV Shows", collectionType: "tvshows", paths: ["/data/media/tv"] },
351
- { name: "Music", collectionType: "music", paths: ["/data/media/music"] },
352
- ]
353
-
354
- for (const lib of defaultLibraries) {
355
- try {
356
- await this.addVirtualFolder(lib)
357
- } catch (error) {
358
- // Library might already exist, continue with others
359
- debugLog("JellyfinAPI", `Failed to add library ${lib.name}: ${error}`)
360
- }
361
- }
362
- }
363
-
364
- // ==========================================
365
- // API Key Management (requires auth)
366
- // ==========================================
367
-
368
- /**
369
- * Create an API key for external access (e.g., Homepage widget)
370
- */
371
- async createApiKey(appName: string): Promise<string> {
372
- await this.request(`/Auth/Keys?app=${encodeURIComponent(appName)}`, {
373
- method: "POST",
374
- })
375
-
376
- // Get all keys and find the one we just created
377
- const keys = await this.getApiKeys()
378
- const key = keys.find((k) => k.AppName === appName)
379
- return key?.AccessToken || ""
380
- }
381
-
382
- /**
383
- * Get all API keys
384
- */
385
- async getApiKeys(): Promise<{ AccessToken: string; AppName: string; DateCreated: string }[]> {
386
- const result = await this.request<{
387
- Items: { AccessToken: string; AppName: string; DateCreated: string }[]
388
- }>("/Auth/Keys")
389
- return result.Items || []
390
- }
391
-
392
- // ==========================================
393
- // Health Check
394
- // ==========================================
395
-
396
- /**
397
- * Check if Jellyfin is running and accessible
398
- */
399
- async isHealthy(): Promise<boolean> {
400
- try {
401
- await this.request<SystemInfo>("/System/Info/Public")
402
- return true
403
- } catch {
404
- return false
405
- }
406
- }
407
-
408
- /**
409
- * Get public system info (no auth required)
410
- */
411
- async getPublicSystemInfo(): Promise<SystemInfo> {
412
- return this.request<SystemInfo>("/System/Info/Public")
413
- }
414
-
415
- /**
416
- * Check if already configured (wizard completed)
417
- */
418
- async isInitialized(): Promise<boolean> {
419
- return this.isStartupComplete()
420
- }
421
-
422
- /**
423
- * Run the auto-setup process for Jellyfin
424
- */
425
- async setup(options: AutoSetupOptions): Promise<AutoSetupResult> {
426
- const { username, password } = options
427
-
428
- try {
429
- // Check if reachable
430
- const healthy = await this.isHealthy()
431
- if (!healthy) {
432
- return { success: false, message: "Jellyfin not reachable" }
433
- }
434
-
435
- // Check if wizard already completed
436
- const initialized = await this.isStartupComplete()
437
- if (initialized) {
438
- return {
439
- success: true,
440
- message: "Already configured",
441
- data: { alreadyInitialized: true },
442
- }
443
- }
444
-
445
- // Run the setup wizard
446
- await this.runSetupWizard(username, password)
447
-
448
- // Try to authenticate to get access token
449
- const authResult = await this.authenticate(username, password)
450
-
451
- // Create API key for Homepage etc.
452
- let apiKey: string | undefined
453
- try {
454
- apiKey = await this.createApiKey("easiarr")
455
- } catch {
456
- // API key creation may fail if permission issues
457
- }
458
-
459
- return {
460
- success: true,
461
- message: "Setup wizard completed",
462
- data: {
463
- accessToken: authResult.AccessToken,
464
- serverId: authResult.ServerId,
465
- apiKey,
466
- },
467
- envUpdates: apiKey ? { API_KEY_JELLYFIN: apiKey } : undefined,
468
- }
469
- } catch (error) {
470
- return { success: false, message: `${error}` }
471
- }
472
- }
473
- }