Die Datenschicht: - plex.tv-Anmeldung über das PIN-Verfahren, Token im Schlüsselbund - Serversuche und Verbindungsauswahl nach Rang (lokal vor direkt vor Vermittlung), mit Abgleich der Server-Kennung gegen veraltete plex.direct-Adressen - PlexServer als einzige Stelle, an der die App Endpunkte kennt: Mediatheken, Hubs, seitenweises Blättern mit Filtern, Metadaten, Staffeln/Folgen, Suche Die Oberfläche: - Startseite mit Aufmacher und den Reihen vom Server - Mediathek als Raster oder Liste, mit Filter- und Sortierblatt - Detailseiten für Filme, Folgen, Serien und Staffeln inkl. Besetzung, Fassungsauswahl und aufklappbaren technischen Angaben - Suche mit gemerkten Anfragen - Vollbildplayer mit eigenem Chrome Der Player: - AVPlayerEngine und VLCPlayerEngine hinter einem gemeinsamen Protokoll - VLCKit liegt in einem eigenen Package (FlixyVLC), damit `swift test` in FlixyKit nicht das 920-MB-Binärpaket mitzieht Am echten plex.tv geprüft: der Anmeldecode kommt an. Dabei fiel auf, dass `strong=true` einen dreißigstelligen Code liefert, der sich bei plex.tv/link nicht eintippen lässt — jetzt der kurze. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
2.3 KiB
Swift
54 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
/// Der Anmeldecode aus dem PIN-Verfahren.
|
|
///
|
|
/// Plex legt auf Anfrage einen vierstelligen Code an. Der Nutzer bestätigt ihn
|
|
/// im Browser bei plex.tv, danach trägt derselbe Datensatz den Token. Die App
|
|
/// selbst bekommt nie Benutzername oder Passwort zu sehen — deshalb ist dieser
|
|
/// Weg dem manuellen Eintippen eines Tokens vorzuziehen.
|
|
public struct PlexPin: Sendable, Equatable, Decodable {
|
|
public let id: Int
|
|
public let code: String
|
|
public let authToken: String?
|
|
public let expiresAt: Date?
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case id, code, authToken, expiresAt
|
|
}
|
|
|
|
public init(from decoder: any Decoder) throws {
|
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.id = try values.decode(Int.self, forKey: .id)
|
|
self.code = try values.decode(String.self, forKey: .code)
|
|
self.authToken = try values.decodeIfPresent(String.self, forKey: .authToken)
|
|
|
|
// Plex liefert ISO-8601 mit Zeitzone. Ein eigener Formatierer statt
|
|
// einer Decoder-Strategie, weil sonst alle anderen Datumsfelder in
|
|
// dieser Antwort mitbetroffen wären.
|
|
if let text = try values.decodeIfPresent(String.self, forKey: .expiresAt) {
|
|
self.expiresAt = ISO8601DateFormatter().date(from: text)
|
|
} else {
|
|
self.expiresAt = nil
|
|
}
|
|
}
|
|
|
|
public init(id: Int, code: String, authToken: String? = nil, expiresAt: Date? = nil) {
|
|
self.id = id
|
|
self.code = code
|
|
self.authToken = authToken
|
|
self.expiresAt = expiresAt
|
|
}
|
|
|
|
/// Die Adresse, unter der der Nutzer den Code bestätigt.
|
|
///
|
|
/// `forwardUrl` fehlt bewusst: der Rücksprung in die App wäre bequemer,
|
|
/// verlangt aber ein eigenes URL-Schema. Solange die App den Code ohnehin
|
|
/// im Hintergrund abfragt, merkt niemand einen Unterschied.
|
|
public func authorizationURL(clientIdentifier: String, product: String) -> URL? {
|
|
var components = URLComponents(string: "https://app.plex.tv/auth")
|
|
components?.fragment = "?clientID=\(clientIdentifier)&code=\(code)"
|
|
+ "&context%5Bdevice%5D%5Bproduct%5D=\(product.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? product)"
|
|
return components?.url
|
|
}
|
|
}
|