@muhammedaksam/easiarr 0.8.3 → 0.8.4
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 +1 -1
- package/src/api/jellyfin-api.ts +46 -4
- package/src/api/jellyseerr-api.ts +538 -0
- package/src/apps/registry.ts +1 -0
- package/src/ui/screens/FullAutoSetup.ts +104 -0
- package/src/ui/screens/JellyseerrSetup.ts +612 -0
- package/src/ui/screens/MainMenu.ts +129 -173
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhammedaksam/easiarr",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
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",
|
package/src/api/jellyfin-api.ts
CHANGED
|
@@ -66,13 +66,34 @@ export interface SystemInfo {
|
|
|
66
66
|
StartupWizardCompleted: boolean
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// ==========================================
|
|
70
|
+
// User Types
|
|
71
|
+
// ==========================================
|
|
72
|
+
|
|
73
|
+
export interface UserPolicy {
|
|
74
|
+
IsAdministrator: boolean
|
|
75
|
+
IsHidden: boolean
|
|
76
|
+
IsDisabled: boolean
|
|
77
|
+
EnableRemoteAccess: boolean
|
|
78
|
+
AuthenticationProviderId?: string
|
|
79
|
+
PasswordResetProviderId?: string
|
|
80
|
+
[key: string]: unknown // Allow other properties
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface UserDto {
|
|
84
|
+
Id: string
|
|
85
|
+
Name?: string
|
|
86
|
+
ServerId?: string
|
|
87
|
+
HasPassword: boolean
|
|
88
|
+
LastLoginDate?: string
|
|
89
|
+
Policy?: UserPolicy
|
|
90
|
+
[key: string]: unknown // Allow other properties
|
|
91
|
+
}
|
|
92
|
+
|
|
69
93
|
export interface AuthResult {
|
|
70
94
|
AccessToken: string
|
|
71
95
|
ServerId: string
|
|
72
|
-
User:
|
|
73
|
-
Id: string
|
|
74
|
-
Name: string
|
|
75
|
-
}
|
|
96
|
+
User: UserDto
|
|
76
97
|
}
|
|
77
98
|
|
|
78
99
|
// ==========================================
|
|
@@ -80,6 +101,27 @@ export interface AuthResult {
|
|
|
80
101
|
// ==========================================
|
|
81
102
|
|
|
82
103
|
export class JellyfinClient {
|
|
104
|
+
// ==========================================
|
|
105
|
+
// User Management
|
|
106
|
+
// ==========================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get a user's details
|
|
110
|
+
*/
|
|
111
|
+
async getUser(userId: string): Promise<UserDto> {
|
|
112
|
+
return this.request<UserDto>(`/Users/${userId}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Update a user's policy (permissions)
|
|
117
|
+
*/
|
|
118
|
+
async updateUserPolicy(userId: string, policy: Partial<UserPolicy>): Promise<void> {
|
|
119
|
+
await this.request(`/Users/${userId}/Policy`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: JSON.stringify(policy),
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
83
125
|
private baseUrl: string
|
|
84
126
|
private accessToken?: string
|
|
85
127
|
|
|
@@ -0,0 +1,538 @@
|
|
|
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 { debugLog } from "../utils/debug"
|
|
12
|
+
|
|
13
|
+
// ==========================================
|
|
14
|
+
// Enums (from Jellyseerr server/constants/server.ts)
|
|
15
|
+
// ==========================================
|
|
16
|
+
|
|
17
|
+
export enum MediaServerType {
|
|
18
|
+
PLEX = 1,
|
|
19
|
+
JELLYFIN = 2,
|
|
20
|
+
EMBY = 3,
|
|
21
|
+
NOT_CONFIGURED = 4,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ==========================================
|
|
25
|
+
// Types
|
|
26
|
+
// ==========================================
|
|
27
|
+
|
|
28
|
+
export interface JellyseerrPublicSettings {
|
|
29
|
+
initialized: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface JellyseerrMainSettings {
|
|
33
|
+
apiKey: string
|
|
34
|
+
appLanguage: string
|
|
35
|
+
applicationTitle: string
|
|
36
|
+
applicationUrl: string
|
|
37
|
+
mediaServerType: number
|
|
38
|
+
localLogin: boolean
|
|
39
|
+
newPlexLogin: boolean
|
|
40
|
+
defaultPermissions: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface JellyseerrJellyfinSettings {
|
|
44
|
+
name?: string
|
|
45
|
+
ip?: string
|
|
46
|
+
hostname?: string
|
|
47
|
+
port?: number
|
|
48
|
+
useSsl?: boolean
|
|
49
|
+
urlBase?: string
|
|
50
|
+
externalHostname?: string
|
|
51
|
+
adminUser?: string
|
|
52
|
+
adminPass?: string
|
|
53
|
+
serverId?: string
|
|
54
|
+
apiKey?: string
|
|
55
|
+
libraries?: JellyseerrLibrary[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface JellyseerrLibrary {
|
|
59
|
+
id: string
|
|
60
|
+
name: string
|
|
61
|
+
enabled: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface JellyseerrUser {
|
|
65
|
+
id: number
|
|
66
|
+
email: string
|
|
67
|
+
username?: string
|
|
68
|
+
jellyfinUsername?: string
|
|
69
|
+
jellyfinUserId?: string
|
|
70
|
+
userType: number
|
|
71
|
+
permissions: number
|
|
72
|
+
avatar?: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface JellyseerrRadarrSettings {
|
|
76
|
+
id?: number
|
|
77
|
+
name: string
|
|
78
|
+
hostname: string
|
|
79
|
+
port: number
|
|
80
|
+
apiKey: string
|
|
81
|
+
useSsl: boolean
|
|
82
|
+
baseUrl?: string
|
|
83
|
+
activeProfileId: number
|
|
84
|
+
activeProfileName: string
|
|
85
|
+
activeDirectory: string
|
|
86
|
+
is4k: boolean
|
|
87
|
+
minimumAvailability: string
|
|
88
|
+
isDefault: boolean
|
|
89
|
+
syncEnabled?: boolean
|
|
90
|
+
preventSearch?: boolean
|
|
91
|
+
externalUrl?: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface JellyseerrSonarrSettings {
|
|
95
|
+
id?: number
|
|
96
|
+
name: string
|
|
97
|
+
hostname: string
|
|
98
|
+
port: number
|
|
99
|
+
apiKey: string
|
|
100
|
+
useSsl: boolean
|
|
101
|
+
baseUrl?: string
|
|
102
|
+
activeProfileId: number
|
|
103
|
+
activeProfileName: string
|
|
104
|
+
activeDirectory: string
|
|
105
|
+
activeLanguageProfileId?: number
|
|
106
|
+
is4k: boolean
|
|
107
|
+
enableSeasonFolders: boolean
|
|
108
|
+
isDefault: boolean
|
|
109
|
+
syncEnabled?: boolean
|
|
110
|
+
preventSearch?: boolean
|
|
111
|
+
externalUrl?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ServiceProfile {
|
|
115
|
+
id: number
|
|
116
|
+
name: string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ServiceTestResult {
|
|
120
|
+
profiles: ServiceProfile[]
|
|
121
|
+
rootFolders?: { id: number; path: string }[]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Auth request for initial setup (unconfigured server) */
|
|
125
|
+
interface JellyfinSetupAuthRequest {
|
|
126
|
+
username: string
|
|
127
|
+
password: string
|
|
128
|
+
hostname: string
|
|
129
|
+
port: number
|
|
130
|
+
useSsl: boolean
|
|
131
|
+
urlBase: string
|
|
132
|
+
serverType: MediaServerType
|
|
133
|
+
email?: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Auth request for login (already configured server) */
|
|
137
|
+
interface JellyfinLoginRequest {
|
|
138
|
+
username: string
|
|
139
|
+
password: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ==========================================
|
|
143
|
+
// Client
|
|
144
|
+
// ==========================================
|
|
145
|
+
|
|
146
|
+
export class JellyseerrClient {
|
|
147
|
+
private baseUrl: string
|
|
148
|
+
private cookie?: string
|
|
149
|
+
|
|
150
|
+
constructor(host: string, port: number) {
|
|
151
|
+
this.baseUrl = `http://${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, {
|
|
168
|
+
...options,
|
|
169
|
+
headers,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Capture session cookie from auth responses
|
|
173
|
+
const setCookie = response.headers.get("set-cookie")
|
|
174
|
+
if (setCookie) {
|
|
175
|
+
this.cookie = setCookie.split(";")[0]
|
|
176
|
+
debugLog("Jellyseerr", "Session cookie captured")
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
const text = await response.text()
|
|
181
|
+
debugLog("Jellyseerr", `Error ${response.status}: ${text}`)
|
|
182
|
+
throw new Error(`Jellyseerr API error: ${response.status} - ${text}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const contentType = response.headers.get("content-type")
|
|
186
|
+
if (contentType?.includes("application/json")) {
|
|
187
|
+
return response.json()
|
|
188
|
+
}
|
|
189
|
+
return {} as T
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ==========================================
|
|
193
|
+
// Health & Status
|
|
194
|
+
// ==========================================
|
|
195
|
+
|
|
196
|
+
async isHealthy(): Promise<boolean> {
|
|
197
|
+
try {
|
|
198
|
+
await this.request<{ version: string }>("/status")
|
|
199
|
+
return true
|
|
200
|
+
} catch {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async isInitialized(): Promise<boolean> {
|
|
206
|
+
try {
|
|
207
|
+
const settings = await this.request<JellyseerrPublicSettings>("/settings/public")
|
|
208
|
+
return settings.initialized
|
|
209
|
+
} catch {
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ==========================================
|
|
215
|
+
// Main Settings
|
|
216
|
+
// ==========================================
|
|
217
|
+
|
|
218
|
+
async getMainSettings(): Promise<JellyseerrMainSettings> {
|
|
219
|
+
return this.request<JellyseerrMainSettings>("/settings/main")
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async updateMainSettings(settings: Partial<JellyseerrMainSettings>): Promise<JellyseerrMainSettings> {
|
|
223
|
+
return this.request<JellyseerrMainSettings>("/settings/main", {
|
|
224
|
+
method: "POST",
|
|
225
|
+
body: JSON.stringify(settings),
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Mark the setup wizard as complete.
|
|
231
|
+
* Must be called after configuring all settings.
|
|
232
|
+
*/
|
|
233
|
+
async initialize(): Promise<{ initialized: boolean }> {
|
|
234
|
+
return this.request<{ initialized: boolean }>("/settings/initialize", {
|
|
235
|
+
method: "POST",
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ==========================================
|
|
240
|
+
// Jellyfin Configuration
|
|
241
|
+
// ==========================================
|
|
242
|
+
|
|
243
|
+
async getJellyfinSettings(): Promise<JellyseerrJellyfinSettings> {
|
|
244
|
+
return this.request<JellyseerrJellyfinSettings>("/settings/jellyfin")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async updateJellyfinSettings(settings: Partial<JellyseerrJellyfinSettings>): Promise<JellyseerrJellyfinSettings> {
|
|
248
|
+
return this.request<JellyseerrJellyfinSettings>("/settings/jellyfin", {
|
|
249
|
+
method: "POST",
|
|
250
|
+
body: JSON.stringify(settings),
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async syncJellyfinLibraries(): Promise<JellyseerrLibrary[]> {
|
|
255
|
+
return this.request<JellyseerrLibrary[]>("/settings/jellyfin/library?sync=true")
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async enableLibraries(libraryIds: string[]): Promise<JellyseerrLibrary[]> {
|
|
259
|
+
const enable = libraryIds.join(",")
|
|
260
|
+
return this.request<JellyseerrLibrary[]>(`/settings/jellyfin/library?enable=${encodeURIComponent(enable)}`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ==========================================
|
|
264
|
+
// Authentication
|
|
265
|
+
// ==========================================
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Authenticate with Jellyfin credentials.
|
|
269
|
+
*
|
|
270
|
+
* This method handles two scenarios:
|
|
271
|
+
* 1. Fresh setup: Sends full payload with hostname, port, serverType
|
|
272
|
+
* 2. Already configured: If setup payload fails, retries with just username/password
|
|
273
|
+
*
|
|
274
|
+
* @param username - Jellyfin username
|
|
275
|
+
* @param password - Jellyfin password
|
|
276
|
+
* @param hostname - Jellyfin hostname (container name or IP)
|
|
277
|
+
* @param port - Jellyfin port (default 8096)
|
|
278
|
+
* @param email - Optional email for the Jellyseerr user
|
|
279
|
+
*/
|
|
280
|
+
async authenticateJellyfin(
|
|
281
|
+
username: string,
|
|
282
|
+
password: string,
|
|
283
|
+
hostname: string,
|
|
284
|
+
port: number,
|
|
285
|
+
email?: string
|
|
286
|
+
): Promise<JellyseerrUser> {
|
|
287
|
+
// Attempt 1: Full setup payload (for fresh installs)
|
|
288
|
+
const setupPayload: JellyfinSetupAuthRequest = {
|
|
289
|
+
username,
|
|
290
|
+
password,
|
|
291
|
+
hostname,
|
|
292
|
+
port,
|
|
293
|
+
useSsl: false,
|
|
294
|
+
urlBase: "",
|
|
295
|
+
serverType: MediaServerType.JELLYFIN,
|
|
296
|
+
email: email || `${username}@local`,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
debugLog(
|
|
300
|
+
"Jellyseerr",
|
|
301
|
+
`Auth attempt with setup payload: hostname=${hostname}, port=${port}, serverType=${MediaServerType.JELLYFIN}`
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
return await this.request<JellyseerrUser>("/auth/jellyfin", {
|
|
306
|
+
method: "POST",
|
|
307
|
+
body: JSON.stringify(setupPayload),
|
|
308
|
+
})
|
|
309
|
+
} catch (err: unknown) {
|
|
310
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
311
|
+
|
|
312
|
+
// Check if server is already configured
|
|
313
|
+
if (message.includes("already configured") || message.includes("hostname already configured")) {
|
|
314
|
+
debugLog("Jellyseerr", "Server already configured, retrying with login-only payload")
|
|
315
|
+
|
|
316
|
+
// Attempt 2: Login-only payload (server already configured)
|
|
317
|
+
const loginPayload: JellyfinLoginRequest = {
|
|
318
|
+
username,
|
|
319
|
+
password,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return this.request<JellyseerrUser>("/auth/jellyfin", {
|
|
323
|
+
method: "POST",
|
|
324
|
+
body: JSON.stringify(loginPayload),
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Re-throw other errors with more context
|
|
329
|
+
if (message.includes("NO_ADMIN_USER") || message.includes("NotAdmin")) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
`Jellyfin user "${username}" is not an administrator. Please ensure the user has admin permissions in Jellyfin.`
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (message.includes("InvalidCredentials") || message.includes("401")) {
|
|
336
|
+
throw new Error(`Invalid Jellyfin credentials for user "${username}".`)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (message.includes("InvalidUrl") || message.includes("INVALID_URL")) {
|
|
340
|
+
throw new Error(`Cannot reach Jellyfin at ${hostname}:${port}. Check the hostname and port.`)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
throw err
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Authenticate with Plex token
|
|
349
|
+
*/
|
|
350
|
+
async authenticatePlex(authToken: string): Promise<JellyseerrUser> {
|
|
351
|
+
return this.request<JellyseerrUser>("/auth/plex", {
|
|
352
|
+
method: "POST",
|
|
353
|
+
body: JSON.stringify({ authToken }),
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ==========================================
|
|
358
|
+
// Radarr Configuration
|
|
359
|
+
// ==========================================
|
|
360
|
+
|
|
361
|
+
async getRadarrSettings(): Promise<JellyseerrRadarrSettings[]> {
|
|
362
|
+
return this.request<JellyseerrRadarrSettings[]>("/settings/radarr")
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async testRadarr(config: {
|
|
366
|
+
hostname: string
|
|
367
|
+
port: number
|
|
368
|
+
apiKey: string
|
|
369
|
+
useSsl: boolean
|
|
370
|
+
baseUrl?: string
|
|
371
|
+
}): Promise<ServiceTestResult> {
|
|
372
|
+
return this.request<ServiceTestResult>("/settings/radarr/test", {
|
|
373
|
+
method: "POST",
|
|
374
|
+
body: JSON.stringify(config),
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async addRadarr(settings: JellyseerrRadarrSettings): Promise<JellyseerrRadarrSettings> {
|
|
379
|
+
return this.request<JellyseerrRadarrSettings>("/settings/radarr", {
|
|
380
|
+
method: "POST",
|
|
381
|
+
body: JSON.stringify(settings),
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ==========================================
|
|
386
|
+
// Sonarr Configuration
|
|
387
|
+
// ==========================================
|
|
388
|
+
|
|
389
|
+
async getSonarrSettings(): Promise<JellyseerrSonarrSettings[]> {
|
|
390
|
+
return this.request<JellyseerrSonarrSettings[]>("/settings/sonarr")
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async testSonarr(config: {
|
|
394
|
+
hostname: string
|
|
395
|
+
port: number
|
|
396
|
+
apiKey: string
|
|
397
|
+
useSsl: boolean
|
|
398
|
+
baseUrl?: string
|
|
399
|
+
}): Promise<ServiceTestResult> {
|
|
400
|
+
return this.request<ServiceTestResult>("/settings/sonarr/test", {
|
|
401
|
+
method: "POST",
|
|
402
|
+
body: JSON.stringify(config),
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async addSonarr(settings: JellyseerrSonarrSettings): Promise<JellyseerrSonarrSettings> {
|
|
407
|
+
return this.request<JellyseerrSonarrSettings>("/settings/sonarr", {
|
|
408
|
+
method: "POST",
|
|
409
|
+
body: JSON.stringify(settings),
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ==========================================
|
|
414
|
+
// Full Setup Wizard
|
|
415
|
+
// ==========================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Run the full setup wizard for Jellyfin
|
|
419
|
+
* Returns the API key on success
|
|
420
|
+
*/
|
|
421
|
+
async runJellyfinSetup(
|
|
422
|
+
jellyfinHostname: string,
|
|
423
|
+
port: number,
|
|
424
|
+
username: string,
|
|
425
|
+
password: string,
|
|
426
|
+
email?: string
|
|
427
|
+
): Promise<string> {
|
|
428
|
+
// Step 1: Authenticate (creates first admin if none exists)
|
|
429
|
+
await this.authenticateJellyfin(username, password, jellyfinHostname, port, email)
|
|
430
|
+
|
|
431
|
+
// Step 2: Update Jellyfin settings with full URL
|
|
432
|
+
const fullUrl = `http://${jellyfinHostname}:${port}`
|
|
433
|
+
await this.updateJellyfinSettings({
|
|
434
|
+
hostname: fullUrl,
|
|
435
|
+
adminUser: username,
|
|
436
|
+
adminPass: password,
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
// Step 3: Sync libraries
|
|
440
|
+
const libraries = await this.syncJellyfinLibraries()
|
|
441
|
+
|
|
442
|
+
// Step 4: Enable all libraries
|
|
443
|
+
const libraryIds = libraries.map((lib) => lib.id)
|
|
444
|
+
if (libraryIds.length > 0) {
|
|
445
|
+
await this.enableLibraries(libraryIds)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Step 5: Get API key
|
|
449
|
+
const mainSettings = await this.getMainSettings()
|
|
450
|
+
return mainSettings.apiKey
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Configure Radarr connection with auto-detection of profiles
|
|
455
|
+
*/
|
|
456
|
+
async configureRadarr(
|
|
457
|
+
hostname: string,
|
|
458
|
+
port: number,
|
|
459
|
+
apiKey: string,
|
|
460
|
+
rootFolder: string
|
|
461
|
+
): Promise<JellyseerrRadarrSettings | null> {
|
|
462
|
+
try {
|
|
463
|
+
const testResult = await this.testRadarr({
|
|
464
|
+
hostname,
|
|
465
|
+
port,
|
|
466
|
+
apiKey,
|
|
467
|
+
useSsl: false,
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
if (!testResult.profiles || testResult.profiles.length === 0) {
|
|
471
|
+
debugLog("Jellyseerr", "No Radarr profiles found")
|
|
472
|
+
return null
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const profile = testResult.profiles[0]
|
|
476
|
+
|
|
477
|
+
return await this.addRadarr({
|
|
478
|
+
name: "Radarr",
|
|
479
|
+
hostname,
|
|
480
|
+
port,
|
|
481
|
+
apiKey,
|
|
482
|
+
useSsl: false,
|
|
483
|
+
activeProfileId: profile.id,
|
|
484
|
+
activeProfileName: profile.name,
|
|
485
|
+
activeDirectory: rootFolder,
|
|
486
|
+
is4k: false,
|
|
487
|
+
minimumAvailability: "announced",
|
|
488
|
+
isDefault: true,
|
|
489
|
+
})
|
|
490
|
+
} catch (e) {
|
|
491
|
+
debugLog("Jellyseerr", `Radarr config failed: ${e}`)
|
|
492
|
+
return null
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Configure Sonarr connection with auto-detection of profiles
|
|
498
|
+
*/
|
|
499
|
+
async configureSonarr(
|
|
500
|
+
hostname: string,
|
|
501
|
+
port: number,
|
|
502
|
+
apiKey: string,
|
|
503
|
+
rootFolder: string
|
|
504
|
+
): Promise<JellyseerrSonarrSettings | null> {
|
|
505
|
+
try {
|
|
506
|
+
const testResult = await this.testSonarr({
|
|
507
|
+
hostname,
|
|
508
|
+
port,
|
|
509
|
+
apiKey,
|
|
510
|
+
useSsl: false,
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
if (!testResult.profiles || testResult.profiles.length === 0) {
|
|
514
|
+
debugLog("Jellyseerr", "No Sonarr profiles found")
|
|
515
|
+
return null
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const profile = testResult.profiles[0]
|
|
519
|
+
|
|
520
|
+
return await this.addSonarr({
|
|
521
|
+
name: "Sonarr",
|
|
522
|
+
hostname,
|
|
523
|
+
port,
|
|
524
|
+
apiKey,
|
|
525
|
+
useSsl: false,
|
|
526
|
+
activeProfileId: profile.id,
|
|
527
|
+
activeProfileName: profile.name,
|
|
528
|
+
activeDirectory: rootFolder,
|
|
529
|
+
is4k: false,
|
|
530
|
+
enableSeasonFolders: true,
|
|
531
|
+
isDefault: true,
|
|
532
|
+
})
|
|
533
|
+
} catch (e) {
|
|
534
|
+
debugLog("Jellyseerr", `Sonarr config failed: ${e}`)
|
|
535
|
+
return null
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|