feat: add migrate command, media server detection, and debrid auto-config
- Migration wizard from Sonarr/Radarr/Prowlarr (unarr migrate) [pre-beta] - Auto-detect instances via Docker, config files, port scan, Prowlarr - Import wanted list (monitored+missing movies/series) - Import download history and blocklist to avoid re-downloading - Extract debrid tokens from *arr download clients - Quality profile mapping to preferred_quality config - DISTINCT ON PostgreSQL query for optimal torrent selection - JSON export with --dry-run --json (text to stderr, JSON to stdout) - Media server detection (Plex/Jellyfin/Emby) in unarr init - Detects library paths and offers them as download directory options - Debrid auto-configuration in unarr init - Scans *arr instances for debrid tokens - Validates and saves via API if user confirms - New preferred_quality setting in config (2160p/1080p/720p) - Library scan command (unarr scan) with ffprobe metadata extraction
This commit is contained in:
parent
0b6c6849b1
commit
677a8fe083
34 changed files with 4766 additions and 22 deletions
|
|
@ -153,6 +153,33 @@ func (c *Client) GetUsenetUsage(ctx context.Context) (*UsenetUsageResponse, erro
|
|||
return &resp, nil
|
||||
}
|
||||
|
||||
// ConfigureDebrid saves a debrid provider token for the user (used by unarr init/migrate).
|
||||
func (c *Client) ConfigureDebrid(ctx context.Context, req ConfigureDebridRequest) (*ConfigureDebridResponse, error) {
|
||||
var resp ConfigureDebridResponse
|
||||
if err := c.doPost(ctx, "/api/internal/agent/debrid-config", req, &resp); err != nil {
|
||||
return nil, fmt.Errorf("configure debrid: %w", err)
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// BatchDownload queues multiple items for download (used by unarr migrate).
|
||||
func (c *Client) BatchDownload(ctx context.Context, req BatchDownloadRequest) (*BatchDownloadResponse, error) {
|
||||
var resp BatchDownloadResponse
|
||||
if err := c.doPost(ctx, "/api/internal/agent/batch-download", req, &resp); err != nil {
|
||||
return nil, fmt.Errorf("batch download: %w", err)
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// SyncLibrary sends scanned library items to the server for matching and upgrade discovery.
|
||||
func (c *Client) SyncLibrary(ctx context.Context, req LibrarySyncRequest) (*LibrarySyncResponse, error) {
|
||||
var resp LibrarySyncResponse
|
||||
if err := c.doPost(ctx, "/api/internal/agent/library-sync", req, &resp); err != nil {
|
||||
return nil, fmt.Errorf("library sync: %w", err)
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// doPost sends a JSON POST request and decodes the response.
|
||||
func (c *Client) doPost(ctx context.Context, path string, body any, dst any) error {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ type Task struct {
|
|||
DirectFileName string `json:"directFileName,omitempty"` // Original filename from direct URL
|
||||
NzbID string `json:"nzbId,omitempty"` // Pre-resolved NZB ID from server
|
||||
NzbPassword string `json:"nzbPassword,omitempty"` // Password for encrypted NZB archives
|
||||
ReplacePath string `json:"replacePath,omitempty"` // File to replace after download (upgrade mode)
|
||||
LibraryItemID int `json:"libraryItemId,omitempty"` // Library item being upgraded
|
||||
}
|
||||
|
||||
// TasksResponse wraps the array of tasks returned by the server.
|
||||
|
|
@ -197,3 +199,102 @@ type UsenetUsageResponse struct {
|
|||
RemainingBytes int64 `json:"remainingBytes"`
|
||||
QuotaResetDate string `json:"quotaResetDate"`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Batch download types (used by unarr migrate)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// BatchDownloadRequest sends a list of wanted items to queue for download.
|
||||
type BatchDownloadRequest struct {
|
||||
Items []WantedItem `json:"items"`
|
||||
ExcludeHashes []string `json:"excludeHashes,omitempty"` // blocklisted + already-downloaded hashes
|
||||
}
|
||||
|
||||
// WantedItem represents a movie or series the user wants.
|
||||
type WantedItem struct {
|
||||
TmdbID int `json:"tmdbId,omitempty"`
|
||||
ImdbID string `json:"imdbId,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Year int `json:"year,omitempty"`
|
||||
Type string `json:"type"` // "movie" or "show"
|
||||
}
|
||||
|
||||
// BatchDownloadResponse reports the outcome of a batch download request.
|
||||
type BatchDownloadResponse struct {
|
||||
Queued int `json:"queued"`
|
||||
NotFound int `json:"notFound"`
|
||||
AlreadyActive int `json:"alreadyActive"`
|
||||
Items []BatchItem `json:"items"`
|
||||
}
|
||||
|
||||
// BatchItem is the per-item result of a batch download.
|
||||
type BatchItem struct {
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"` // "queued", "not_found", "already_active"
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Debrid config types (used by unarr init/migrate)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ConfigureDebridRequest configures a debrid provider.
|
||||
type ConfigureDebridRequest struct {
|
||||
Provider string `json:"provider"` // "real-debrid", "alldebrid", "torbox", "premiumize"
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ConfigureDebridResponse is returned after configuring a debrid provider.
|
||||
type ConfigureDebridResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Account DebridAccount `json:"account"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// DebridAccount holds verified debrid account info.
|
||||
type DebridAccount struct {
|
||||
Valid bool `json:"valid"`
|
||||
Premium bool `json:"premium"`
|
||||
Username string `json:"username"`
|
||||
ExpiresAt string `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Library sync types (used by unarr scan)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// LibrarySyncRequest sends scanned media items to the server.
|
||||
type LibrarySyncRequest struct {
|
||||
Items []LibrarySyncItem `json:"items"`
|
||||
ScanPath string `json:"scanPath"`
|
||||
IsLastBatch bool `json:"isLastBatch"`
|
||||
}
|
||||
|
||||
// LibrarySyncItem is a single scanned media file with ffprobe metadata.
|
||||
type LibrarySyncItem struct {
|
||||
FilePath string `json:"filePath"`
|
||||
FileName string `json:"fileName"`
|
||||
FileSize int64 `json:"fileSize,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Year string `json:"year,omitempty"`
|
||||
Season int `json:"season,omitempty"`
|
||||
Episode int `json:"episode,omitempty"`
|
||||
ContentType string `json:"contentType"`
|
||||
Resolution string `json:"resolution,omitempty"`
|
||||
VideoCodec string `json:"videoCodec,omitempty"`
|
||||
HDR string `json:"hdr,omitempty"`
|
||||
BitDepth int `json:"bitDepth,omitempty"`
|
||||
AudioCodec string `json:"audioCodec,omitempty"`
|
||||
AudioChannels int `json:"audioChannels,omitempty"`
|
||||
AudioLanguages []string `json:"audioLanguages,omitempty"`
|
||||
SubtitleLanguages []string `json:"subtitleLanguages,omitempty"`
|
||||
AudioTracks any `json:"audioTracks,omitempty"`
|
||||
SubtitleTracks any `json:"subtitleTracks,omitempty"`
|
||||
VideoInfo any `json:"videoInfo,omitempty"`
|
||||
}
|
||||
|
||||
// LibrarySyncResponse is returned after syncing library items.
|
||||
type LibrarySyncResponse struct {
|
||||
Synced int `json:"synced"`
|
||||
Matched int `json:"matched"`
|
||||
Removed int `json:"removed"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue