From 03a8d1f899ee41d465068fac45b485eaab32eb4b Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sat, 12 Sep 2026 15:04:00 +0200 Subject: [PATCH] =?UTF-8?q?Ein=20Werkzeug=20f=C3=BCr=20die=20Fragen,=20die?= =?UTF-8?q?=20reines=20HTTP=20sind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Das Upload-Kontingent des Tages ist aufgebraucht — verbraucht von meinen eigenen Vermutungen über eine Schnittstelle, die keine Oberfläche braucht. Jede Runde ging als TestFlight-Build hinaus und kam als Screenshot zurück. `swift run FlixyProbe` macht dasselbe in Sekunden: anmelden, Server suchen, Senderliste und Programm lesen, das Einschalten durchprobieren — mit derselben Logik wie die App. Der Token kommt über die übliche Anmeldung und liegt im Schlüsselbund dieses Rechners; gedruckt werden Pfade, Kennungen und Antworten, keine vollständigen Adressen. Co-Authored-By: Claude Opus 5 --- Packages/FlixyKit/Package.swift | 5 + .../FlixyKit/Sources/FlixyProbe/main.swift | 106 ++++++++++++++++++ README.md | 19 ++++ 3 files changed, 130 insertions(+) create mode 100644 Packages/FlixyKit/Sources/FlixyProbe/main.swift diff --git a/Packages/FlixyKit/Package.swift b/Packages/FlixyKit/Package.swift index 50b64dc..9da016d 100644 --- a/Packages/FlixyKit/Package.swift +++ b/Packages/FlixyKit/Package.swift @@ -20,6 +20,11 @@ let package = Package( // Sprechen mit dem Server: Anmeldung, Endpunkte, Modelle. Kein SwiftUI, // damit die gesamte Protokoll-Schicht ohne Oberfläche testbar bleibt. .target(name: "PlexKit"), + + // Ein Werkzeug für die Fragen, die reines HTTP sind — ohne Oberfläche, + // ohne Gerät, ohne Upload. Eine Runde dauert damit Sekunden statt + // eines halben Tages, und das Upload-Kontingent bleibt für Builds. + .executableTarget(name: "FlixyProbe", dependencies: ["PlexKit"]), .testTarget( name: "PlexKitTests", dependencies: ["PlexKit"], diff --git a/Packages/FlixyKit/Sources/FlixyProbe/main.swift b/Packages/FlixyKit/Sources/FlixyProbe/main.swift new file mode 100644 index 0000000..a195819 --- /dev/null +++ b/Packages/FlixyKit/Sources/FlixyProbe/main.swift @@ -0,0 +1,106 @@ +import Foundation +import PlexKit + +// Ein Werkzeug für die Fragen, die reines HTTP sind. +// +// Der Grund, dass es das gibt: die Live-TV-Schnittstelle von Plex ist +// nirgends beschrieben, und jede Vermutung darüber ging bisher als +// TestFlight-Build zum Nutzer und kam einen halben Tag später als Screenshot +// zurück. Irgendwann war das Upload-Kontingent des Tages aufgebraucht — für +// eine Frage, die keine Oberfläche braucht. +// +// Der Token wird über die übliche Anmeldung geholt und liegt im Schlüsselbund +// dieses Rechners. Er steht nirgends in der Ausgabe: gedruckt werden Pfade, +// Kennungen und Antworten, keine vollständigen Adressen. + +let identity = PlexClientIdentity( + product: "Flixy-Probe", + version: "1.0", + clientIdentifier: storedClientIdentifier(), + platform: "macOS", + platformVersion: ProcessInfo.processInfo.operatingSystemVersionString, + device: "Mac", + deviceName: Host.current().localizedName ?? "Mac" +) + +let store = KeychainTokenStore(service: "com.scarriffleservices.flixy.probe") +let authenticator = PlexAuthenticator(identity: identity, tokenStore: store) + +let token: String +if let existing = authenticator.storedToken { + print("Angemeldet (Token aus dem Schlüsselbund).") + token = existing +} else { + let pin = try await authenticator.requestPin() + print(""" + + Anmelden: https://plex.tv/link + Code: \(pin.code) + + Warte … + """) + + var found: String? + for _ in 0..<150 where found == nil { + try await Task.sleep(for: .seconds(2)) + found = try await authenticator.token(for: pin) + } + guard let found else { fatalError("Anmeldung abgelaufen.") } + token = found + print("Angemeldet.\n") +} + +let directory = PlexServerDirectory(identity: identity, token: token) +let servers = try await directory.servers() +guard let resource = servers.first else { fatalError("Kein Server am Konto.") } + +let selector = PlexConnectionSelector(identity: identity) +guard let connection = await selector.selectConnection(for: resource), + let server = PlexServer(resource: resource, connection: connection, identity: identity) +else { fatalError("Kein Weg zu \(resource.name) erreichbar.") } + +print("Server: \(resource.name) über \(connection.local ? "lokal" : "entfernt")\n") + +// MARK: - Live-TV + +let availability = await server.liveTVAvailability() +guard case .available(let dvrs) = availability, let dvr = dvrs.first else { + print("Live-TV: \(availability)") + exit(0) +} +print("Empfänger: \(dvr.key), Programmführer: \(dvr.epgIdentifier ?? "–")") + +let guide = await server.liveTVGuide(dvr: dvr, hours: 2) +print("Sender: \(guide.channels.count) — \(guide.source)\n") + +guard let channel = guide.channels.first else { exit(0) } +let program = guide.programs[channel.id]?.first { $0.isRunning() } + +print("Einschalten: \(channel.title) (\(channel.channelIdentifier ?? "–"))") +print("Läuft: \(program?.title ?? "–")\n") + +let result = await server.tune( + channel: channel, + program: program, + dvr: dvr, + maximumBitrate: nil +) + +for probe in result.trace { + print(probe.path) + print(" → \(probe.result)") +} + +print("\n" + (result.url == nil ? "Kein Weg trug." : "Erfolg.")) + +/// Eine Kennung, die über Läufe hinweg gleich bleibt. +/// +/// Wechselt sie, gilt die Probe am Konto als neues Gerät, und in der +/// Geräteliste sammelt sich Müll an. +func storedClientIdentifier() -> String { + let key = "flixy.probe.clientIdentifier" + if let stored = UserDefaults.standard.string(forKey: key) { return stored } + let created = UUID().uuidString + UserDefaults.standard.set(created, forKey: key) + return created +} diff --git a/README.md b/README.md index ce3c157..414c9c2 100644 --- a/README.md +++ b/README.md @@ -127,3 +127,22 @@ Die Oberfläche wird von Hand im Simulator geprüft. - Xcode 26 oder neuer - iOS 26.0 auf dem Zielgerät - XcodeGen (`brew install xcodegen`) + +## Live-TV untersuchen, ohne zu bauen + +Die Live-TV-Schnittstelle von Plex ist nirgends beschrieben, und jede Vermutung +darüber ging bisher als TestFlight-Build hinaus und kam als Screenshot zurück. +Irgendwann war das Upload-Kontingent des Tages aufgebraucht — für eine Frage, +die keine Oberfläche braucht. + +```bash +cd Packages/FlixyKit && swift run FlixyProbe +``` + +Beim ersten Lauf zeigt das Werkzeug einen Code für plex.tv/link; danach liegt +der Token im Schlüsselbund dieses Rechners. Es sucht den Server, liest +Senderliste und Programm und probiert das Einschalten durch — mit derselben +Logik wie die App, nur in Sekunden statt in einem halben Tag. + +Ausgegeben werden Pfade, Kennungen und die Antworten des Servers. Keine +vollständigen Adressen, also auch kein Token.