diff --git a/README.md b/README.md index 4179cb5..4f0f0d9 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ missarr sends search requests for missing episodes to Sonarr missarr offers [pre-compiled binaries](https://github.com/l3uddz/missarr/releases/latest) for Linux, macOS and Windows for each official release. +Example install (installing to /opt/missarr on linux amd64): +```` +cd /opt +mkdir missarr && cd missarr +curl -fLvo missarr https://github.com/jolbol1/missarr/releases/download/v1.2.0/missarr_v1.2.0_linux_amd64 +chmod +x missarr +```` + Alternatively, you can build the Missarr binary yourself. To build missarr on your system, make sure: @@ -59,6 +67,9 @@ Search for 10 movies: `missarr radarr --limit 10` Search for 10 movies (without updating movies cache) `missarr radarr --limit 10 --skip-refresh` +Search for 10 movies with cutoff unmet: `missarr radarr --limit 10 --cutoff` + + ## Donate If you find this project helpful, feel free to make a small donation: @@ -69,4 +80,4 @@ If you find this project helpful, feel free to make a small donation: - [GitHub Sponsor](https://github.com/sponsors/l3uddz): GitHub matches contributions for first 12 months. -- BTC: 3CiHME1HZQsNNcDL6BArG7PbZLa8zUUgjL \ No newline at end of file +- BTC: 3CiHME1HZQsNNcDL6BArG7PbZLa8zUUgjL diff --git a/cmd/missarr/radarr.go b/cmd/missarr/radarr.go index 272b61a..0b15955 100644 --- a/cmd/missarr/radarr.go +++ b/cmd/missarr/radarr.go @@ -15,6 +15,7 @@ type RadarrCmd struct { LastReleaseDate time.Duration `default:"72h" help:"How long before an item can be considered missing based on release date"` SkipRefresh bool `default:"false" help:"Retrieve current missing from radarr"` Delay time.Duration `default:"0s" help:"Delay between search requests"` + Cutoff bool `default:"false" help:"Search Cutoff Unmet Items"` } func (r *RadarrCmd) Run(c *config, db *sql.DB, mg *migrate.Migrator) error { @@ -44,7 +45,7 @@ func (r *RadarrCmd) Run(c *config, db *sql.DB, mg *migrate.Migrator) error { Msg("Retrieved movies") // refresh datastore - us, rs, fm, err = sc.RefreshStore(rm, time.Now().Add(-r.LastReleaseDate)) + us, rs, fm, err = sc.RefreshStore(rm, time.Now().Add(-r.LastReleaseDate), r.Cutoff) if err != nil { return fmt.Errorf("missing to store: %w", err) } diff --git a/radarr/api.go b/radarr/api.go index 00b8654..7395417 100644 --- a/radarr/api.go +++ b/radarr/api.go @@ -34,6 +34,10 @@ func (c *Client) getSystemStatus() (*systemStatus, error) { return b, nil } +type MovieFile struct { + QualityCutoffNotMet bool +} + type MovieResponse struct { SizeOnDisk int `json:"sizeOnDisk"` Status string `json:"status"` @@ -45,6 +49,7 @@ type MovieResponse struct { IsAvailable bool `json:"isAvailable"` Added time.Time `json:"added"` Id int `json:"id"` + MovieFile MovieFile `json:"movieFile"` } func (c *Client) Movies() ([]MovieResponse, error) { diff --git a/radarr/datastore.go b/radarr/datastore.go index be56518..b68b77d 100644 --- a/radarr/datastore.go +++ b/radarr/datastore.go @@ -30,8 +30,8 @@ func newDatastore(db *sql.DB, mg *migrate.Migrator) (*datastore, error) { } const sqlUpsert = ` -INSERT INTO movies (movie, release_date, search_date) -VALUES (?, ?, ?) +INSERT INTO movies (movie, release_date, search_date, type) +VALUES (?, ?, ?, ?) ON CONFLICT (movie) DO UPDATE SET release_date = excluded.release_date , search_date = CASE @@ -40,8 +40,8 @@ ON CONFLICT (movie) DO UPDATE SET END ` -func (store *datastore) upsert(tx *sql.Tx, movie int, releaseDate time.Time, searchDate *time.Time) error { - _, err := tx.Exec(sqlUpsert, movie, releaseDate, searchDate) +func (store *datastore) upsert(tx *sql.Tx, movie int, releaseDate time.Time, searchDate *time.Time, missingType string) error { + _, err := tx.Exec(sqlUpsert, movie, releaseDate, searchDate, missingType) return err } @@ -49,6 +49,7 @@ type Movie struct { Id int ReleaseDate time.Time SearchDate *time.Time + Type string } func (store *datastore) Upsert(movies []Movie) error { @@ -58,7 +59,7 @@ func (store *datastore) Upsert(movies []Movie) error { } for _, m := range movies { - if err = store.upsert(tx, m.Id, m.ReleaseDate, m.SearchDate); err != nil { + if err = store.upsert(tx, m.Id, m.ReleaseDate, m.SearchDate, m.Type); err != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil { panic(rollbackErr) } @@ -71,7 +72,7 @@ func (store *datastore) Upsert(movies []Movie) error { } const sqlGetAll = ` -SELECT movie, release_date, search_date +SELECT movie, release_date, search_date, type FROM movies ORDER BY release_date DESC ` @@ -85,7 +86,7 @@ func (store *datastore) GetAll() (movies []Movie, err error) { defer rows.Close() for rows.Next() { movie := Movie{} - err = rows.Scan(&movie.Id, &movie.ReleaseDate, &movie.SearchDate) + err = rows.Scan(&movie.Id, &movie.ReleaseDate, &movie.SearchDate, &movie.Type) if err != nil { return movies, err } diff --git a/radarr/ds.go b/radarr/ds.go index 1c6078e..f8e4d16 100644 --- a/radarr/ds.go +++ b/radarr/ds.go @@ -13,10 +13,14 @@ func (c *Client) GetAll() ([]Movie, error) { return c.store.GetAll() } -func (c *Client) RefreshStore(data []MovieResponse, maxReleaseDate time.Time) (int, int, []Movie, error) { +func (c *Client) RefreshStore(data []MovieResponse, maxReleaseDate time.Time, cutoff bool) (int, int, []Movie, error) { // filter movies movies := make([]Movie, 0) sm := make(map[int]int) + missingType := "missing" + if cutoff { + missingType = "cutoff" + } for _, m := range data { // skip if movie matches conditions @@ -25,7 +29,11 @@ func (c *Client) RefreshStore(data []MovieResponse, maxReleaseDate time.Time) (i continue case !m.IsAvailable: continue - case m.HasFile, m.SizeOnDisk > 0: + case m.HasFile && !cutoff, m.SizeOnDisk > 0 && !cutoff: + continue + case cutoff && m.HasFile && !m.MovieFile.QualityCutoffNotMet: + continue + case cutoff && !m.HasFile: continue } @@ -49,6 +57,7 @@ func (c *Client) RefreshStore(data []MovieResponse, maxReleaseDate time.Time) (i Id: m.Id, ReleaseDate: releaseDate, SearchDate: nil, + Type: missingType, }) } @@ -69,6 +78,9 @@ func (c *Client) RefreshStore(data []MovieResponse, maxReleaseDate time.Time) (i moviesToRemove := make([]Movie, 0) finalMovies := make([]Movie, 0) for _, m := range em { + if m.Type != missingType { + continue + } if _, ok := sm[m.Id]; !ok { moviesToRemove = append(moviesToRemove, m) continue diff --git a/radarr/migrations/1_init.sql b/radarr/migrations/1_init.sql index d923f8d..62f60e3 100644 --- a/radarr/migrations/1_init.sql +++ b/radarr/migrations/1_init.sql @@ -2,5 +2,6 @@ CREATE TABLE IF NOT EXISTS movies ( "movie" INTEGER NOT NULL, "release_date" DATETIME NOT NULL, "search_date" DATETIME NULL, + "type" VARCHAR(10), PRIMARY KEY(movie) ) \ No newline at end of file diff --git a/radarr/migrations/2_init.sql b/radarr/migrations/2_init.sql new file mode 100644 index 0000000..b62524f --- /dev/null +++ b/radarr/migrations/2_init.sql @@ -0,0 +1 @@ +ALTER TABLE movies ADD [type] VARCHAR(10) NOT NULL DEFAULT 'missing' \ No newline at end of file