Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support Sonarr multi-season packs
- Improved x265, H.266, VC1 and VVC codec replacements
- Open the exact movie on Letterboxd instead of a search
- Open and search movies and series from Shortcuts and Siri on iOS 27
- Ask Siri about the movie or series on screen

## 2.1.2 - 2026-11-02
### Fixed
Expand Down
1 change: 1 addition & 0 deletions Ruddarr.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
Utilities/Extensions.swift,
Utilities/Formatters.swift,
Utilities/Helpers.swift,
Utilities/Identifiers.swift,
Utilities/Networking.swift,
Utilities/Placeholder.swift,
Utilities/Privacy.swift,
Expand Down
18 changes: 18 additions & 0 deletions Ruddarr/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
}
}
}
},
"%@" : {

},
"%@ at %@" : {
"comment" : "(Today/Tomorrow/Yesterday) at (time)",
Expand Down Expand Up @@ -1652,6 +1655,9 @@
}
}
}
},
"Downloaded Episodes" : {

},
"Downloading" : {
"comment" : "(Short) State of task in queue\nState of task in queue\nStatus of task in queue",
Expand Down Expand Up @@ -2239,6 +2245,9 @@
}
}
}
},
"IMDb ID" : {

},
"Import" : {
"comment" : "(Short) Importing a queue task",
Expand Down Expand Up @@ -5037,6 +5046,9 @@
}
}
}
},
"Size on Disk" : {

},
"Slow Instance" : {
"localizations" : {
Expand Down Expand Up @@ -5433,6 +5445,9 @@
}
}
}
},
"TMDB ID" : {

},
"To monitor a season or episode, the series itself must be monitored." : {
"localizations" : {
Expand Down Expand Up @@ -5525,6 +5540,9 @@
}
}
}
},
"TVDB ID" : {

},
"Type" : {
"comment" : "Short version of Series Type",
Expand Down
2 changes: 1 addition & 1 deletion Ruddarr/Models/Media.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ protocol Media: Identifiable, Sendable where ID: Sendable {
var remotePoster: String? { get }

var searchableHash: String { get }
func searchableItem(poster: URL?) -> CSSearchableItem
func searchableItem(poster: URL?, instance: Instance) -> CSSearchableItem
}

struct Tag: Identifiable, Equatable, Codable {
Expand Down
7 changes: 5 additions & 2 deletions Ruddarr/Models/Movies/Movie.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import AppIntents
import CoreSpotlight

struct Movie: Media, Identifiable, Equatable, Codable {
Expand Down Expand Up @@ -238,7 +239,7 @@ struct Movie: Media, Identifiable, Equatable, Codable {
}

extension Movie {
func searchableItem(poster: URL?) -> CSSearchableItem {
func searchableItem(poster: URL?, instance: Instance) -> CSSearchableItem {
let attributes = CSSearchableItemAttributeSet(contentType: UTType.movie)
attributes.title = title
attributes.genre = genres.first
Expand All @@ -257,8 +258,10 @@ extension Movie {
.filter { $0.title == title }
.map { $0.title }

attributes.associateAppEntity(MovieEntity(self, instance))

return CSSearchableItem(
uniqueIdentifier: "movie:\(id):\(instanceId?.uuidString ?? "")",
uniqueIdentifier: SpotlightItemID(.movie, id, instanceId?.uuidString).rawValue,
domainIdentifier: instanceId?.uuidString,
attributeSet: attributes
)
Expand Down
2 changes: 1 addition & 1 deletion Ruddarr/Models/Movies/Movies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class Movies {
items = try await dependencies.api.radarr.fetch(instance)
itemsCount = items.count
computeAlternateTitles()
await Spotlight(instance.id).index(items, delay: .seconds(5))
await Spotlight(instance.id).index(items, from: instance, delay: .seconds(5))

leaveBreadcrumb(.info, category: "movies", message: "Fetched movies", data: ["count": items.count])

Expand Down
7 changes: 5 additions & 2 deletions Ruddarr/Models/Series/Series.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import AppIntents
import CoreSpotlight

struct Series: Media, Identifiable, Equatable, Codable {
Expand Down Expand Up @@ -227,7 +228,7 @@ struct Series: Media, Identifiable, Equatable, Codable {
}

extension Series {
func searchableItem(poster: URL?) -> CSSearchableItem {
func searchableItem(poster: URL?, instance: Instance) -> CSSearchableItem {
let attributes = CSSearchableItemAttributeSet(contentType: UTType.movie)
attributes.title = title
attributes.genre = genres.first
Expand All @@ -247,8 +248,10 @@ extension Series {
.map { $0.title }
}

attributes.associateAppEntity(SeriesEntity(self, instance))

return CSSearchableItem(
uniqueIdentifier: "series:\(id):\(instanceId?.uuidString ?? "")",
uniqueIdentifier: SpotlightItemID(.series, id, instanceId?.uuidString).rawValue,
domainIdentifier: instanceId?.uuidString,
attributeSet: attributes
)
Expand Down
2 changes: 1 addition & 1 deletion Ruddarr/Models/Series/SeriesModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class SeriesModel {
items = try await dependencies.api.sonarr.fetch(instance)
itemsCount = items.count
computeAlternateTitles()
await Spotlight(instance.id).index(items, delay: .seconds(5))
await Spotlight(instance.id).index(items, from: instance, delay: .seconds(5))

leaveBreadcrumb(.info, category: "series", message: "Fetched series", data: ["count": items.count])

Expand Down
13 changes: 5 additions & 8 deletions Ruddarr/Ruddarr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,13 @@ struct Ruddarr: App {
func openSearchableItem(_ userActivity: NSUserActivity) {
guard let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else { return }

let parts = identifier.split(separator: ":").map(String.init) // `<type>:<id>:<instance>`

let deeplink: QuickActions.Deeplink? = switch parts[0] {
case "movie": Movie.ID(parts[1]).map { .openMovie($0, parts[2]) }
case "series": Series.ID(parts[1]).map { .openSeries($0, parts[2]) }
default: nil
guard let item = SpotlightItemID(rawValue: identifier) else {
return leaveBreadcrumb(.error, category: "spotlight", message: "Invalid identifier", data: ["openSearchableItem": identifier])
}

guard let deeplink else {
return leaveBreadcrumb(.error, category: "spotlight", message: "Invalid identifier", data: ["openSearchableItem": identifier])
let deeplink: QuickActions.Deeplink = switch item.kind {
case .movie: .openMovie(item.mediaId, item.instance)
case .series: .openSeries(item.mediaId, item.instance)
}

deeplink()
Expand Down
Loading
Loading