Ein Werkzeug für die Fragen, die reines HTTP sind

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 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-12 15:04:00 +02:00
co-authored by Claude Opus 5
parent 024f396641
commit 03a8d1f899
3 changed files with 130 additions and 0 deletions
@@ -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
}