Phase 1: Add ESLint/Prettier config + baseline auto-fixes
This commit is contained in:
@@ -11,12 +11,12 @@ module.exports = function(ctx, helpers) {
|
||||
const results = { radarr: null, sonarr: null };
|
||||
|
||||
// Step 1: Authenticate with Overseerr via Plex token
|
||||
let overseerrUrl = `http://host.docker.internal:${APP_PORTS.overseerr}`;
|
||||
const overseerrUrl = `http://host.docker.internal:${APP_PORTS.overseerr}`;
|
||||
const overseerrSession = await helpers.getOverseerrSession();
|
||||
|
||||
if (!overseerrSession) {
|
||||
return ctx.errorResponse(res, 502, 'Could not authenticate with Overseerr. Make sure Plex and Overseerr are running.', {
|
||||
hint: 'Complete Overseerr setup wizard and link your Plex account first, then try again.'
|
||||
hint: 'Complete Overseerr setup wizard and link your Plex account first, then try again.',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ module.exports = function(ctx, helpers) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': overseerrSession.cookie,
|
||||
...options.headers
|
||||
}
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
return response;
|
||||
};
|
||||
@@ -41,12 +41,12 @@ module.exports = function(ctx, helpers) {
|
||||
const statusRes = await overseerrFetch('/api/v1/status');
|
||||
if (!statusRes.ok) {
|
||||
return ctx.errorResponse(res, 502, 'Cannot connect to Overseerr', {
|
||||
hint: 'Make sure Overseerr is running on port 5055'
|
||||
hint: 'Make sure Overseerr is running on port 5055',
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
return ctx.errorResponse(res, 502, `Cannot reach Overseerr: ${e.message}`, {
|
||||
hint: 'Check if Overseerr container is running'
|
||||
hint: 'Check if Overseerr container is running',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,14 +59,14 @@ module.exports = function(ctx, helpers) {
|
||||
|
||||
// Fetch quality profiles from Radarr
|
||||
const profilesRes = await ctx.fetchT(`${radarrBaseUrl}/api/v3/qualityprofile`, {
|
||||
headers: { 'X-Api-Key': radarr.apiKey }
|
||||
headers: { 'X-Api-Key': radarr.apiKey },
|
||||
});
|
||||
const profiles = profilesRes.ok ? await profilesRes.json() : [];
|
||||
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
|
||||
|
||||
// Fetch root folders from Radarr
|
||||
const rootFoldersRes = await ctx.fetchT(`${radarrBaseUrl}/api/v3/rootfolder`, {
|
||||
headers: { 'X-Api-Key': radarr.apiKey }
|
||||
headers: { 'X-Api-Key': radarr.apiKey },
|
||||
});
|
||||
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
|
||||
const defaultRootFolder = rootFolders[0]?.path || '/movies';
|
||||
@@ -87,12 +87,12 @@ module.exports = function(ctx, helpers) {
|
||||
minimumAvailability: 'released',
|
||||
isDefault: true,
|
||||
externalUrl: radarr.url,
|
||||
tags: []
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const radarrRes = await overseerrFetch('/api/v1/settings/radarr', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(radarrConfig)
|
||||
body: JSON.stringify(radarrConfig),
|
||||
});
|
||||
|
||||
if (radarrRes.ok) {
|
||||
@@ -115,14 +115,14 @@ module.exports = function(ctx, helpers) {
|
||||
|
||||
// Fetch quality profiles from Sonarr
|
||||
const profilesRes = await ctx.fetchT(`${sonarrBaseUrl}/api/v3/qualityprofile`, {
|
||||
headers: { 'X-Api-Key': sonarr.apiKey }
|
||||
headers: { 'X-Api-Key': sonarr.apiKey },
|
||||
});
|
||||
const profiles = profilesRes.ok ? await profilesRes.json() : [];
|
||||
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
|
||||
|
||||
// Fetch root folders from Sonarr
|
||||
const rootFoldersRes = await ctx.fetchT(`${sonarrBaseUrl}/api/v3/rootfolder`, {
|
||||
headers: { 'X-Api-Key': sonarr.apiKey }
|
||||
headers: { 'X-Api-Key': sonarr.apiKey },
|
||||
});
|
||||
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
|
||||
const defaultRootFolder = rootFolders[0]?.path || '/tv';
|
||||
@@ -131,7 +131,7 @@ module.exports = function(ctx, helpers) {
|
||||
let languageProfileId = 1;
|
||||
try {
|
||||
const langRes = await ctx.fetchT(`${sonarrBaseUrl}/api/v3/languageprofile`, {
|
||||
headers: { 'X-Api-Key': sonarr.apiKey }
|
||||
headers: { 'X-Api-Key': sonarr.apiKey },
|
||||
});
|
||||
if (langRes.ok) {
|
||||
const langProfiles = await langRes.json();
|
||||
@@ -158,12 +158,12 @@ module.exports = function(ctx, helpers) {
|
||||
isDefault: true,
|
||||
enableSeasonFolders: true,
|
||||
externalUrl: sonarr.url,
|
||||
tags: []
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const sonarrRes = await overseerrFetch('/api/v1/settings/sonarr', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(sonarrConfig)
|
||||
body: JSON.stringify(sonarrConfig),
|
||||
});
|
||||
|
||||
if (sonarrRes.ok) {
|
||||
@@ -182,7 +182,7 @@ module.exports = function(ctx, helpers) {
|
||||
res.json({
|
||||
success: anyConfigured,
|
||||
message: anyConfigured ? 'Services configured in Overseerr' : 'Configuration failed',
|
||||
results
|
||||
results,
|
||||
});
|
||||
}, 'arr-configure-overseerr'));
|
||||
|
||||
@@ -210,7 +210,7 @@ module.exports = function(ctx, helpers) {
|
||||
}
|
||||
|
||||
// Normalize URL - remove trailing slash
|
||||
let baseUrl = url.replace(/\/+$/, '');
|
||||
const baseUrl = url.replace(/\/+$/, '');
|
||||
|
||||
// Build the API endpoint
|
||||
let apiEndpoint;
|
||||
@@ -233,7 +233,7 @@ module.exports = function(ctx, helpers) {
|
||||
const response = await ctx.fetchT(apiEndpoint, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
@@ -244,7 +244,7 @@ module.exports = function(ctx, helpers) {
|
||||
return res.json({
|
||||
success: true,
|
||||
version,
|
||||
appName
|
||||
appName,
|
||||
});
|
||||
} else if (response.status === 401) {
|
||||
return ctx.errorResponse(res, 401, 'Invalid API key');
|
||||
@@ -288,7 +288,7 @@ module.exports = function(ctx, helpers) {
|
||||
containerName: container.Names[0]?.replace(/^\//, ''),
|
||||
port: exposedPort,
|
||||
url: `http://host.docker.internal:${exposedPort}`,
|
||||
localUrl: `http://localhost:${exposedPort}`
|
||||
localUrl: `http://localhost:${exposedPort}`,
|
||||
};
|
||||
|
||||
// Extract API key for arr services
|
||||
@@ -305,7 +305,7 @@ module.exports = function(ctx, helpers) {
|
||||
radarrFound: !!detected.radarr?.apiKey,
|
||||
sonarrFound: !!detected.sonarr?.apiKey,
|
||||
lidarrFound: !!detected.lidarr?.apiKey,
|
||||
prowlarrFound: !!detected.prowlarr?.apiKey
|
||||
prowlarrFound: !!detected.prowlarr?.apiKey,
|
||||
};
|
||||
|
||||
ctx.log.info('arr', 'Detected services', summary);
|
||||
@@ -313,14 +313,14 @@ module.exports = function(ctx, helpers) {
|
||||
if (!summary.overseerrFound) {
|
||||
return ctx.errorResponse(res, 400, 'Overseerr is not running. Deploy it first.', {
|
||||
detected,
|
||||
summary
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (!summary.radarrFound && !summary.sonarrFound) {
|
||||
return ctx.errorResponse(res, 400, 'No Radarr or Sonarr found with valid API keys. Deploy at least one first.', {
|
||||
detected,
|
||||
summary
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ module.exports = function(ctx, helpers) {
|
||||
return ctx.errorResponse(res, 502, 'Could not authenticate with Overseerr. Make sure Plex and Overseerr are running.', {
|
||||
setupUrl: detected.overseerr.localUrl,
|
||||
detected,
|
||||
summary
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -344,8 +344,8 @@ module.exports = function(ctx, helpers) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': overseerrSession.cookie,
|
||||
...options.headers
|
||||
}
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -356,14 +356,14 @@ module.exports = function(ctx, helpers) {
|
||||
try {
|
||||
// Fetch quality profiles from Radarr
|
||||
const profilesRes = await ctx.fetchT(`${detected.radarr.localUrl}/api/v3/qualityprofile`, {
|
||||
headers: { 'X-Api-Key': detected.radarr.apiKey }
|
||||
headers: { 'X-Api-Key': detected.radarr.apiKey },
|
||||
});
|
||||
const profiles = profilesRes.ok ? await profilesRes.json() : [];
|
||||
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
|
||||
|
||||
// Fetch root folders from Radarr
|
||||
const rootFoldersRes = await ctx.fetchT(`${detected.radarr.localUrl}/api/v3/rootfolder`, {
|
||||
headers: { 'X-Api-Key': detected.radarr.apiKey }
|
||||
headers: { 'X-Api-Key': detected.radarr.apiKey },
|
||||
});
|
||||
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
|
||||
const defaultRootFolder = rootFolders[0]?.path || '/movies';
|
||||
@@ -384,12 +384,12 @@ module.exports = function(ctx, helpers) {
|
||||
minimumAvailability: 'released',
|
||||
isDefault: true,
|
||||
externalUrl: detected.radarr.localUrl,
|
||||
tags: []
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const resp = await overseerrFetch('/api/v1/settings/radarr', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(radarrConfig)
|
||||
body: JSON.stringify(radarrConfig),
|
||||
});
|
||||
|
||||
configResults.radarr = resp.ok ? 'configured' : `failed: ${await resp.text()}`;
|
||||
@@ -403,14 +403,14 @@ module.exports = function(ctx, helpers) {
|
||||
try {
|
||||
// Fetch quality profiles from Sonarr
|
||||
const profilesRes = await ctx.fetchT(`${detected.sonarr.localUrl}/api/v3/qualityprofile`, {
|
||||
headers: { 'X-Api-Key': detected.sonarr.apiKey }
|
||||
headers: { 'X-Api-Key': detected.sonarr.apiKey },
|
||||
});
|
||||
const profiles = profilesRes.ok ? await profilesRes.json() : [];
|
||||
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
|
||||
|
||||
// Fetch root folders from Sonarr
|
||||
const rootFoldersRes = await ctx.fetchT(`${detected.sonarr.localUrl}/api/v3/rootfolder`, {
|
||||
headers: { 'X-Api-Key': detected.sonarr.apiKey }
|
||||
headers: { 'X-Api-Key': detected.sonarr.apiKey },
|
||||
});
|
||||
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
|
||||
const defaultRootFolder = rootFolders[0]?.path || '/tv';
|
||||
@@ -419,7 +419,7 @@ module.exports = function(ctx, helpers) {
|
||||
let languageProfileId = 1;
|
||||
try {
|
||||
const langRes = await ctx.fetchT(`${detected.sonarr.localUrl}/api/v3/languageprofile`, {
|
||||
headers: { 'X-Api-Key': detected.sonarr.apiKey }
|
||||
headers: { 'X-Api-Key': detected.sonarr.apiKey },
|
||||
});
|
||||
if (langRes.ok) {
|
||||
const langProfiles = await langRes.json();
|
||||
@@ -444,12 +444,12 @@ module.exports = function(ctx, helpers) {
|
||||
isDefault: true,
|
||||
enableSeasonFolders: true,
|
||||
externalUrl: detected.sonarr.localUrl,
|
||||
tags: []
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const resp = await overseerrFetch('/api/v1/settings/sonarr', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(sonarrConfig)
|
||||
body: JSON.stringify(sonarrConfig),
|
||||
});
|
||||
|
||||
configResults.sonarr = resp.ok ? 'configured' : `failed: ${await resp.text()}`;
|
||||
@@ -466,7 +466,7 @@ module.exports = function(ctx, helpers) {
|
||||
'deploymentSuccess',
|
||||
'Arr Stack Auto-Connected',
|
||||
`Overseerr configured: ${Object.entries(configResults).filter(([k,v]) => v === 'configured').map(([k]) => k).join(', ')}`,
|
||||
'success'
|
||||
'success',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ module.exports = function(ctx, helpers) {
|
||||
message: anyConfigured ? 'Auto-setup completed successfully!' : 'Configuration failed',
|
||||
detected,
|
||||
configResults,
|
||||
summary
|
||||
summary,
|
||||
});
|
||||
}, 'arr-auto-setup'));
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = function(ctx, helpers) {
|
||||
service,
|
||||
source: url ? 'external' : 'local',
|
||||
url: url || null,
|
||||
storedAt: new Date().toISOString()
|
||||
storedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Test connection if URL is known
|
||||
@@ -77,7 +77,7 @@ module.exports = function(ctx, helpers) {
|
||||
return ctx.errorResponse(res, 400, 'Invalid seedbox base URL');
|
||||
}
|
||||
await ctx.credentialManager.store('arr.seedbox.baseurl', seedboxBaseUrl, {
|
||||
storedAt: new Date().toISOString()
|
||||
storedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ module.exports = function(ctx, helpers) {
|
||||
success: true,
|
||||
message: `${service} API key stored`,
|
||||
connectionTest,
|
||||
url: resolvedUrl
|
||||
url: resolvedUrl,
|
||||
});
|
||||
}, 'arr-credentials-store'));
|
||||
|
||||
@@ -106,7 +106,7 @@ module.exports = function(ctx, helpers) {
|
||||
url: metadata?.url || null,
|
||||
lastVerified: metadata?.lastVerified || null,
|
||||
version: metadata?.version || null,
|
||||
source: metadata?.source || null
|
||||
source: metadata?.source || null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ module.exports = function(ctx, helpers) {
|
||||
sonarr: null,
|
||||
overseerr: null,
|
||||
lidarr: null,
|
||||
prowlarr: null
|
||||
prowlarr: null,
|
||||
};
|
||||
|
||||
// Service detection patterns
|
||||
@@ -35,7 +35,7 @@ module.exports = function(ctx, helpers) {
|
||||
image: container.Image,
|
||||
port: exposedPort,
|
||||
status: container.State,
|
||||
url: helpers.getServiceUrl(containerName, exposedPort)
|
||||
url: helpers.getServiceUrl(containerName, exposedPort),
|
||||
};
|
||||
|
||||
// Get API key for arr services (not Plex or Overseerr)
|
||||
@@ -58,8 +58,8 @@ module.exports = function(ctx, helpers) {
|
||||
plexReady: !!(detected.plex?.token),
|
||||
radarrReady: !!(detected.radarr?.apiKey),
|
||||
sonarrReady: !!(detected.sonarr?.apiKey),
|
||||
overseerrRunning: !!detected.overseerr
|
||||
}
|
||||
overseerrRunning: !!detected.overseerr,
|
||||
},
|
||||
});
|
||||
}, 'arr-detect'));
|
||||
|
||||
@@ -86,7 +86,7 @@ module.exports = function(ctx, helpers) {
|
||||
containerId: container.Id,
|
||||
containerName: container.Names[0]?.replace(/^\//, ''),
|
||||
port: portInfo?.PublicPort || config.port,
|
||||
status: container.State
|
||||
status: container.State,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ module.exports = function(ctx, helpers) {
|
||||
hasToken: false,
|
||||
containerId: null,
|
||||
containerName: null,
|
||||
version: null
|
||||
version: null,
|
||||
};
|
||||
|
||||
// Check Docker first
|
||||
@@ -143,7 +143,7 @@ module.exports = function(ctx, helpers) {
|
||||
// Store for later use
|
||||
await ctx.credentialManager.store('arr.plex.token', token, {
|
||||
service: 'plex', source: 'local', url: entry.url,
|
||||
lastVerified: new Date().toISOString()
|
||||
lastVerified: new Date().toISOString(),
|
||||
});
|
||||
} else {
|
||||
entry.status = 'needs_key';
|
||||
@@ -160,7 +160,7 @@ module.exports = function(ctx, helpers) {
|
||||
try {
|
||||
const radarrCheck = await ctx.fetchT(`http://host.docker.internal:${dc.port}/api/v1/settings/radarr`, {
|
||||
headers: { 'Cookie': session.cookie },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (radarrCheck.ok) {
|
||||
const radarrSettings = await radarrCheck.json();
|
||||
@@ -170,7 +170,7 @@ module.exports = function(ctx, helpers) {
|
||||
try {
|
||||
const sonarrCheck = await ctx.fetchT(`http://host.docker.internal:${dc.port}/api/v1/settings/sonarr`, {
|
||||
headers: { 'Cookie': session.cookie },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (sonarrCheck.ok) {
|
||||
const sonarrSettings = await sonarrCheck.json();
|
||||
@@ -180,7 +180,7 @@ module.exports = function(ctx, helpers) {
|
||||
try {
|
||||
const plexCheck = await ctx.fetchT(`http://host.docker.internal:${dc.port}/api/v1/settings/plex`, {
|
||||
headers: { 'Cookie': session.cookie },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (plexCheck.ok) {
|
||||
const plexSettings = await plexCheck.json();
|
||||
@@ -273,7 +273,7 @@ module.exports = function(ctx, helpers) {
|
||||
fullyConnected: statuses.filter(s => s.status === 'connected').length,
|
||||
needsApiKey: statuses.filter(s => s.status === 'needs_key').length,
|
||||
errors: statuses.filter(s => s.status === 'error').length,
|
||||
readyForAutoConnect: statuses.filter(s => s.status === 'connected').length >= 2
|
||||
readyForAutoConnect: statuses.filter(s => s.status === 'connected').length >= 2,
|
||||
};
|
||||
|
||||
res.json({ success: true, services: result, seedboxBaseUrl: detectedSeedboxUrl, summary });
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = function(ctx) {
|
||||
const exec = await dockerContainer.exec({
|
||||
Cmd: ['cat', '/config/config.xml'],
|
||||
AttachStdout: true,
|
||||
AttachStderr: true
|
||||
AttachStderr: true,
|
||||
});
|
||||
|
||||
const stream = await exec.start();
|
||||
@@ -38,7 +38,7 @@ module.exports = function(ctx) {
|
||||
try {
|
||||
const containers = await ctx.docker.client.listContainers({ all: false });
|
||||
const container = containers.find(c =>
|
||||
c.Names.some(n => n.toLowerCase().includes(containerName.toLowerCase()) || n.toLowerCase().includes('plex'))
|
||||
c.Names.some(n => n.toLowerCase().includes(containerName.toLowerCase()) || n.toLowerCase().includes('plex')),
|
||||
);
|
||||
|
||||
if (!container) return null;
|
||||
@@ -47,7 +47,7 @@ module.exports = function(ctx) {
|
||||
const exec = await dockerContainer.exec({
|
||||
Cmd: ['cat', '/config/Library/Application Support/Plex Media Server/Preferences.xml'],
|
||||
AttachStdout: true,
|
||||
AttachStderr: true
|
||||
AttachStderr: true,
|
||||
});
|
||||
|
||||
const stream = await exec.start();
|
||||
@@ -97,7 +97,7 @@ module.exports = function(ctx) {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ authToken: plexToken }),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!authRes.ok) {
|
||||
@@ -125,7 +125,7 @@ module.exports = function(ctx) {
|
||||
// 1. Get Plex server identity (for return info)
|
||||
const identityRes = await ctx.fetchT(`${plexUrl}/identity`, {
|
||||
headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
if (!identityRes.ok) throw new Error('Cannot reach Plex server');
|
||||
const identity = await identityRes.json();
|
||||
@@ -136,16 +136,16 @@ module.exports = function(ctx) {
|
||||
const plexConfig = {
|
||||
ip: 'host.docker.internal',
|
||||
port: APP_PORTS.plex,
|
||||
useSsl: false
|
||||
useSsl: false,
|
||||
};
|
||||
|
||||
const configRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': sessionCookie
|
||||
'Cookie': sessionCookie,
|
||||
},
|
||||
body: JSON.stringify(plexConfig)
|
||||
body: JSON.stringify(plexConfig),
|
||||
});
|
||||
|
||||
if (!configRes.ok) {
|
||||
@@ -157,7 +157,7 @@ module.exports = function(ctx) {
|
||||
await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex/sync`, {
|
||||
method: 'POST',
|
||||
headers: { 'Cookie': sessionCookie },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
} catch (e) {
|
||||
ctx.log.warn('arr', 'Plex library sync trigger failed (non-fatal)', { error: e.message });
|
||||
@@ -168,7 +168,7 @@ module.exports = function(ctx) {
|
||||
try {
|
||||
const libRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
|
||||
headers: { 'Cookie': sessionCookie },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (libRes.ok) {
|
||||
const plexSettings = await libRes.json();
|
||||
@@ -188,7 +188,7 @@ module.exports = function(ctx) {
|
||||
try {
|
||||
const existingRes = await ctx.fetchT(`${prowlarrUrl}/api/v1/applications`, {
|
||||
headers: { 'X-Api-Key': prowlarrApiKey },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
existingApps = existingRes.ok ? await existingRes.json() : [];
|
||||
} catch (e) {
|
||||
@@ -217,8 +217,8 @@ module.exports = function(ctx) {
|
||||
{ name: 'prowlarrUrl', value: prowlarrUrl },
|
||||
{ name: 'baseUrl', value: config.url },
|
||||
{ name: 'apiKey', value: config.apiKey },
|
||||
{ name: 'syncCategories', value: syncCategories }
|
||||
]
|
||||
{ name: 'syncCategories', value: syncCategories },
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -226,10 +226,10 @@ module.exports = function(ctx) {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Api-Key': prowlarrApiKey
|
||||
'X-Api-Key': prowlarrApiKey,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
results[appName] = res.ok ? 'configured' : `failed: ${await res.text()}`;
|
||||
} catch (e) {
|
||||
@@ -262,7 +262,7 @@ module.exports = function(ctx) {
|
||||
const response = await ctx.fetchT(apiEndpoint, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
signal: AbortSignal.timeout(15000)
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
@@ -297,6 +297,6 @@ module.exports = function(ctx) {
|
||||
getOverseerrApiKey,
|
||||
connectPlexToOverseerr,
|
||||
configureProwlarrApps,
|
||||
testServiceConnection
|
||||
testServiceConnection,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ module.exports = function(ctx, helpers) {
|
||||
|
||||
if (!plexToken) {
|
||||
return ctx.errorResponse(res, 400, 'No Plex token available. Claim your Plex server first.', {
|
||||
hint: 'Deploy Plex with a claim token or manually configure it.'
|
||||
hint: 'Deploy Plex with a claim token or manually configure it.',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports = function(ctx, helpers) {
|
||||
// Fetch libraries
|
||||
const libRes = await ctx.fetchT(`${plexUrl}/library/sections`, {
|
||||
headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!libRes.ok) {
|
||||
@@ -45,7 +45,7 @@ module.exports = function(ctx, helpers) {
|
||||
title: dir.title,
|
||||
type: dir.type,
|
||||
count: parseInt(dir.count) || 0,
|
||||
scannedAt: dir.scannedAt
|
||||
scannedAt: dir.scannedAt,
|
||||
}));
|
||||
|
||||
// Get server name
|
||||
@@ -54,7 +54,7 @@ module.exports = function(ctx, helpers) {
|
||||
try {
|
||||
const identityRes = await ctx.fetchT(`${plexUrl}/identity`, {
|
||||
headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (identityRes.ok) {
|
||||
const identity = await identityRes.json();
|
||||
@@ -66,7 +66,7 @@ module.exports = function(ctx, helpers) {
|
||||
// Store token for future use
|
||||
await ctx.credentialManager.store('arr.plex.token', plexToken, {
|
||||
service: 'plex', source: 'local', url: plexUrl,
|
||||
lastVerified: new Date().toISOString()
|
||||
lastVerified: new Date().toISOString(),
|
||||
});
|
||||
|
||||
res.json({ success: true, serverName, version, libraries });
|
||||
|
||||
@@ -44,7 +44,7 @@ module.exports = function(ctx, helpers) {
|
||||
steps.push({
|
||||
step: `Test ${svc.charAt(0).toUpperCase() + svc.slice(1)} connection`,
|
||||
status: test.success ? 'success' : 'failed',
|
||||
details: test.success ? `v${test.version}` : test.error
|
||||
details: test.success ? `v${test.version}` : test.error,
|
||||
});
|
||||
|
||||
if (test.success) {
|
||||
@@ -55,12 +55,12 @@ module.exports = function(ctx, helpers) {
|
||||
const stored = await ctx.credentialManager.store(`arr.${svc}.apikey`, apiKey, {
|
||||
service: svc, source: 'external', url,
|
||||
lastVerified: new Date().toISOString(),
|
||||
version: test.version
|
||||
version: test.version,
|
||||
});
|
||||
steps.push({
|
||||
step: `Save ${svc} credentials`,
|
||||
status: stored ? 'success' : 'failed',
|
||||
details: stored ? 'Encrypted and saved' : 'Storage failed'
|
||||
details: stored ? 'Encrypted and saved' : 'Storage failed',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ module.exports = function(ctx, helpers) {
|
||||
steps.push({
|
||||
step: 'Get Overseerr API key',
|
||||
status: 'failed',
|
||||
details: 'Could not authenticate with Overseerr (Plex not running or not linked)'
|
||||
details: 'Could not authenticate with Overseerr (Plex not running or not linked)',
|
||||
});
|
||||
} else {
|
||||
steps.push({ step: 'Get Overseerr API key', status: 'success', details: 'Extracted from container' });
|
||||
@@ -110,7 +110,7 @@ module.exports = function(ctx, helpers) {
|
||||
// Fetch quality profiles
|
||||
const profilesRes = await ctx.fetchT(`${radarrUrl}/api/v3/qualityprofile`, {
|
||||
headers: { 'X-Api-Key': connectedServices.radarr.apiKey },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
const profiles = profilesRes.ok ? await profilesRes.json() : [];
|
||||
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
|
||||
@@ -118,7 +118,7 @@ module.exports = function(ctx, helpers) {
|
||||
// Fetch root folders
|
||||
const rootFoldersRes = await ctx.fetchT(`${radarrUrl}/api/v3/rootfolder`, {
|
||||
headers: { 'X-Api-Key': connectedServices.radarr.apiKey },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
|
||||
const defaultRootFolder = rootFolders[0]?.path || '/movies';
|
||||
@@ -141,20 +141,20 @@ module.exports = function(ctx, helpers) {
|
||||
minimumAvailability: 'released',
|
||||
isDefault: true,
|
||||
externalUrl: connectedServices.radarr.url,
|
||||
tags: []
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const radarrRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/radarr`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Cookie': overseerrCookie },
|
||||
body: JSON.stringify(radarrConfig),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
steps.push({
|
||||
step: 'Configure Radarr in Overseerr',
|
||||
status: radarrRes.ok ? 'success' : 'failed',
|
||||
details: radarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await radarrRes.text()
|
||||
details: radarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await radarrRes.text(),
|
||||
});
|
||||
} catch (e) {
|
||||
steps.push({ step: 'Configure Radarr in Overseerr', status: 'failed', details: e.message });
|
||||
@@ -170,14 +170,14 @@ module.exports = function(ctx, helpers) {
|
||||
|
||||
const profilesRes = await ctx.fetchT(`${sonarrUrl}/api/v3/qualityprofile`, {
|
||||
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
const profiles = profilesRes.ok ? await profilesRes.json() : [];
|
||||
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
|
||||
|
||||
const rootFoldersRes = await ctx.fetchT(`${sonarrUrl}/api/v3/rootfolder`, {
|
||||
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
|
||||
const defaultRootFolder = rootFolders[0]?.path || '/tv';
|
||||
@@ -186,7 +186,7 @@ module.exports = function(ctx, helpers) {
|
||||
try {
|
||||
const langRes = await ctx.fetchT(`${sonarrUrl}/api/v3/languageprofile`, {
|
||||
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (langRes.ok) {
|
||||
const langProfiles = await langRes.json();
|
||||
@@ -212,20 +212,20 @@ module.exports = function(ctx, helpers) {
|
||||
isDefault: true,
|
||||
enableSeasonFolders: true,
|
||||
externalUrl: connectedServices.sonarr.url,
|
||||
tags: []
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const sonarrRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/sonarr`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Cookie': overseerrCookie },
|
||||
body: JSON.stringify(sonarrConfig),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
steps.push({
|
||||
step: 'Configure Sonarr in Overseerr',
|
||||
status: sonarrRes.ok ? 'success' : 'failed',
|
||||
details: sonarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await sonarrRes.text()
|
||||
details: sonarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await sonarrRes.text(),
|
||||
});
|
||||
} catch (e) {
|
||||
steps.push({ step: 'Configure Sonarr in Overseerr', status: 'failed', details: e.message });
|
||||
@@ -239,7 +239,7 @@ module.exports = function(ctx, helpers) {
|
||||
steps.push({
|
||||
step: 'Connect Plex to Overseerr',
|
||||
status: 'success',
|
||||
details: `${plexResult.serverName} - ${plexResult.libraries.length} libraries synced`
|
||||
details: `${plexResult.serverName} - ${plexResult.libraries.length} libraries synced`,
|
||||
});
|
||||
} catch (e) {
|
||||
steps.push({ step: 'Connect Plex to Overseerr', status: 'failed', details: e.message });
|
||||
@@ -259,13 +259,13 @@ module.exports = function(ctx, helpers) {
|
||||
const prowlarrResults = await helpers.configureProwlarrApps(
|
||||
connectedServices.prowlarr.url.replace(/\/+$/, ''),
|
||||
connectedServices.prowlarr.apiKey,
|
||||
appsToConnect
|
||||
appsToConnect,
|
||||
);
|
||||
for (const [app, status] of Object.entries(prowlarrResults)) {
|
||||
steps.push({
|
||||
step: `Add ${app.charAt(0).toUpperCase() + app.slice(1)} to Prowlarr`,
|
||||
status: status === 'configured' || status === 'already_configured' ? 'success' : 'failed',
|
||||
details: status
|
||||
details: status,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -283,14 +283,14 @@ module.exports = function(ctx, helpers) {
|
||||
'deploymentSuccess',
|
||||
'Smart Arr Connect Complete',
|
||||
`${succeeded}/${steps.length} steps completed successfully`,
|
||||
'success'
|
||||
'success',
|
||||
);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: succeeded > 0,
|
||||
steps,
|
||||
summary: { totalSteps: steps.length, succeeded, failed }
|
||||
summary: { totalSteps: steps.length, succeeded, failed },
|
||||
});
|
||||
}, 'smart-connect'));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user