Some checks failed
release / build-release (push) Has been cancelled
- 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>
126 lines
2.7 KiB
Swift
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 })
|
|
}
|
|
}
|