iOS: stem playlist refresh and covers, download queue page, reorderable Library, whole-library radio

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Sonnet 5.5 committed 2026-10-28 15:49:09 -07:00
1 parent 7c8dfb54ca
commit a4c870251c
18 files changed
+514 -30

No files matched your search

+40
View File
@@ -325,6 +325,46 @@ final class PlexServer: MediaSource, @unchecked Sendable {
return try JSONDecoder().decode(PlexPin.self, from: try await http.data(req))
}
/// A strong PIN, completed in the browser at [authUrl] (same flow as the web UI).
func createStrongPin() async throws -> PlexPin {
var req = request(URL(string: "https://plex.tv/api/v2/pins?strong=true")!, withToken: false, method: "POST")
req.httpBody = Data()
return try JSONDecoder().decode(PlexPin.self, from: try await http.data(req))
}
func authUrl(code: String) -> URL? {
var c = URLComponents(string: "https://app.plex.tv/auth")
c?.percentEncodedFragment = "?clientID=\(urlEncode(settings.clientId))&code=\(urlEncode(code))&context%5Bdevice%5D%5Bproduct%5D=\(urlEncode(Self.product))"
return c?.url
}
/// Every server this plex.tv account can reach, owned or shared.
func resources(token: String) async throws -> [PlexResource] {
var req = request(URL(string: "https://plex.tv/api/v2/resources?includeHttps=1&includeRelay=1")!, withToken: false)
req.setValue(token, forHTTPHeaderField: "X-Plex-Token")
let list = try JSONDecoder().decode([Lenient<PlexResource>].self, from: try await http.data(req))
return list.compactMap(\.value).filter(\.isServer)
}
/// The first address that answers /identity as the expected server, local ones first, relay last.
func reach(_ r: PlexResource, userToken: String) async -> String? {
let tok = r.accessToken.isEmpty ? userToken : r.accessToken
let ordered = r.connections.filter { !$0.uri.isEmpty }.sorted { rank($0) < rank($1) }
for c in ordered {
let base = c.uri.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
guard let u = URL(string: base + "/identity") else { continue }
var req = request(u, withToken: false)
req.setValue(tok, forHTTPHeaderField: "X-Plex-Token")
req.timeoutInterval = 3.5
guard let body = try? await http.data(req),
let id = try? decode(body).machineIdentifier, id == r.clientIdentifier || r.clientIdentifier.isEmpty else { continue }
return base
}
return nil
}
private func rank(_ c: PlexResource.Connection) -> Int { c.relay ? 2 : (c.local ? 0 : 1) }
func checkPin(_ id: Int64) async throws -> PlexPin {
let req = request(URL(string: "https://plex.tv/api/v2/pins/\(id)")!, withToken: false)
return try JSONDecoder().decode(PlexPin.self, from: try await http.data(req))