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
98
internal/mediaserver/detect_test.go
Normal file
98
internal/mediaserver/detect_test.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package mediaserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParsePlexSections(t *testing.T) {
|
||||
body := `{
|
||||
"MediaContainer": {
|
||||
"Directory": [
|
||||
{
|
||||
"title": "Movies",
|
||||
"Location": [{"path": "/data/media/movies"}]
|
||||
},
|
||||
{
|
||||
"title": "TV Shows",
|
||||
"Location": [{"path": "/data/media/tv"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
paths := parsePlexSections([]byte(body))
|
||||
if len(paths) != 2 {
|
||||
t.Fatalf("parsePlexSections = %d paths, want 2", len(paths))
|
||||
}
|
||||
if paths[0] != "/data/media/movies" {
|
||||
t.Errorf("paths[0] = %q, want /data/media/movies", paths[0])
|
||||
}
|
||||
if paths[1] != "/data/media/tv" {
|
||||
t.Errorf("paths[1] = %q, want /data/media/tv", paths[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePlexSections_Empty(t *testing.T) {
|
||||
paths := parsePlexSections([]byte(`{}`))
|
||||
if len(paths) != 0 {
|
||||
t.Errorf("parsePlexSections empty = %d paths, want 0", len(paths))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePlexSections_InvalidJSON(t *testing.T) {
|
||||
paths := parsePlexSections([]byte(`not json`))
|
||||
if paths != nil {
|
||||
t.Errorf("parsePlexSections invalid = %v, want nil", paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJellyfinParsing(t *testing.T) {
|
||||
body := `[
|
||||
{"Locations": ["/media/movies"]},
|
||||
{"Locations": ["/media/tv", "/media/anime"]}
|
||||
]`
|
||||
|
||||
var folders []struct {
|
||||
Locations []string `json:"Locations"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(body), &folders); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var paths []string
|
||||
for _, f := range folders {
|
||||
paths = append(paths, f.Locations...)
|
||||
}
|
||||
if len(paths) != 3 {
|
||||
t.Fatalf("got %d paths, want 3", len(paths))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParentDir(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
paths []string
|
||||
expect string
|
||||
}{
|
||||
{"empty", nil, ""},
|
||||
{"single", []string{"/data/media/movies"}, "/data/media"},
|
||||
{"siblings", []string{"/data/media/movies", "/data/media/tv"}, "/data/media"},
|
||||
{"different roots", []string{"/data/movies", "/srv/tv"}, "/"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParentDir(tt.paths)
|
||||
// "/" is filtered out (returns "")
|
||||
if tt.expect == "/" {
|
||||
if got != "" {
|
||||
t.Errorf("ParentDir = %q, want empty (root filtered)", got)
|
||||
}
|
||||
return
|
||||
}
|
||||
if got != tt.expect {
|
||||
t.Errorf("ParentDir = %q, want %q", got, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue