Files
jellyfin-plugin-floatplane/doc/research/reference-sources/wf_FloatplaneAPIClient+Extensions.swift
claude 9e33a343ab
Some checks failed
release / build-release (push) Has been cancelled
Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
- IChannel: creators -> sub-channels -> video posts (public listing API, search mapped)
- IRequiresMediaInfoCallback -> signed proxy URLs; stream controller rewrites HLS so the
  AES-128 key is fetched server-side with the session cookie, segments stay on the CDN
- delivery/info cached per token lifetime and throttled (~2/min)
- config page: paste sails.sid + validate, extra creators, quality, search test
- scripts/package.sh -> plugin folder + zip + md5 + manifest.json; Gitea release workflow
- research docs, HAR analysis (HAR itself gitignored), install-pi5.sh

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 09:42:27 +01:00

126 lines
2.7 KiB
Swift

import Foundation
import CoreGraphics
import SwiftUI
import FloatplaneAPIClient
extension ErrorModel: LocalizedError {
public var errorDescription: String? {
let message = self.message ?? self.errors.first?.message ?? self.errors.first?.name ?? "Error message unretrievable."
let separator: String
if message.last != "." {
separator = ". "
} else {
separator = " "
}
return message + separator + "Error ID: " + self.id
}
public var failureReason: String? {
nil
}
public var recoverySuggestion: String? {
nil
}
public var helpAnchor: String? {
nil
}
}
extension VideoAttachmentModel: Identifiable { }
extension PictureAttachmentModel: Identifiable { }
extension AudioAttachmentModel: Identifiable { }
extension BlogPostModelV3: Identifiable { }
extension UserSubscriptionModel: Identifiable {
public var id: String {
self.creator
}
}
protocol ImageModelShared {
var width: Int { get set }
var height: Int { get set }
var path: String { get set }
var childImages: [ChildImageModel]? { get set }
}
extension ImageModelShared {
var aspectRatio: CGFloat {
return CGFloat(self.width) / CGFloat(self.height)
}
func bestImage(for geometrySize: CGSize?) -> URL? {
guard let geometrySize else {
return nil
}
let geometryMagnitude = Int(geometrySize.width * geometrySize.height)
var difference = (self.width * self.height) - geometryMagnitude
var path = self.path
for childImage in self.childImages ?? [] {
let childDifference = (childImage.width * childImage.height) - geometryMagnitude
if childDifference >= 0 && childDifference < difference {
difference = childDifference
path = childImage.path
}
}
return URL(string: path)
}
}
extension ImageModel: ImageModelShared {
}
extension Optional where Wrapped == ImageModelShared {
var pathUrlOrNil: URL? {
if let thumbnail = self {
return URL(string: thumbnail.path)
} else {
return nil
}
}
func bestImage(for geometrySize: CGSize?) -> URL? {
if let thumbnail = self {
return thumbnail.bestImage(for: geometrySize)
} else {
return nil
}
}
}
extension BlogPostModelV3Channel {
var asChannelModel: ChannelModel? {
switch self {
case let .typeChannelModel(channelModel):
return channelModel
default:
return nil
}
}
}
extension BlogPostModelV3 {
var firstVideoAttachmentId: String? {
return self.attachmentOrder.filter({ self.videoAttachments?.contains($0) == true }).first
}
}
extension ContentPostV3Response {
var firstVideoAttachmentId: String? {
return self.attachmentOrder.filter({ self.videoAttachments?.lazy.map(\.id).contains($0) == true }).first
}
var firstVideoAttachment: VideoAttachmentModel? {
return self.videoAttachments?.first(where: { $0.id == firstVideoAttachmentId })
}
}