Files
Flaro/Packages/FlixyKit/Sources/PlexKit/Auth/PlexPin.swift
T
GuidoandClaude Opus 5 9a69df4635 Phase 1: Anmeldung, Bibliothek, Detailseiten, Hybrid-Player
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>
2026-09-11 16:46:28 +02:00

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
}
}