forked from shadowfacts/Tusker
Shadowfacts
8a45837918
Unlike Mastodon, Pleroma includes a URL query component in attachment URLs. The URL(lenient:) initializer did not count query parameters (e.g. '?') as valid URL characters and incorrectly escaped them resulting in image requests that 404'd.
127 lines
3.9 KiB
Swift
127 lines
3.9 KiB
Swift
//
|
|
// Attachment.swift
|
|
// Pachyderm
|
|
//
|
|
// Created by Shadowfacts on 9/9/18.
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
public class Attachment: Decodable {
|
|
public let id: String
|
|
public let kind: Kind
|
|
public let url: URL
|
|
public let remoteURL: URL?
|
|
public let previewURL: URL
|
|
public let textURL: URL?
|
|
public let meta: Metadata?
|
|
public let description: String?
|
|
|
|
public static func update(_ attachment: Attachment, focus: (Float, Float)?, description: String?) -> Request<Attachment> {
|
|
return Request<Attachment>(method: .put, path: "/api/v1/media/\(attachment.id)", body: .formData([
|
|
"description" => (description ?? attachment.description),
|
|
"focus" => focus
|
|
], nil))
|
|
}
|
|
|
|
required public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.id = try container.decode(String.self, forKey: .id)
|
|
self.kind = try container.decode(Kind.self, forKey: .kind)
|
|
self.url = URL(lenient: try container.decode(String.self, forKey: .url))!
|
|
if let remote = try? container.decode(String.self, forKey: .remoteURL) {
|
|
self.remoteURL = URL(lenient: remote.replacingOccurrences(of: " ", with: "%20"))
|
|
} else {
|
|
self.remoteURL = nil
|
|
}
|
|
self.previewURL = URL(lenient: try container.decode(String.self, forKey: .previewURL).replacingOccurrences(of: " ", with: "%20"))!
|
|
if let text = try? container.decode(String.self, forKey: .textURL) {
|
|
self.textURL = URL(lenient: text.replacingOccurrences(of: " ", with: "%20"))
|
|
} else {
|
|
self.textURL = nil
|
|
}
|
|
self.meta = try? container.decode(Metadata.self, forKey: .meta)
|
|
self.description = try? container.decode(String.self, forKey: .description)
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case kind = "type"
|
|
case url
|
|
case remoteURL = "remote_url"
|
|
case previewURL = "preview_url"
|
|
case textURL = "text_url"
|
|
case meta
|
|
case description
|
|
}
|
|
}
|
|
|
|
extension Attachment {
|
|
public enum Kind: String, Decodable {
|
|
case image
|
|
case video
|
|
case gifv
|
|
case audio
|
|
case unknown
|
|
}
|
|
}
|
|
|
|
extension Attachment {
|
|
public class Metadata: Decodable {
|
|
public let length: String?
|
|
public let duration: Float?
|
|
public let audioEncoding: String?
|
|
public let audioBitrate: String?
|
|
public let audioChannels: String?
|
|
public let fps: Float?
|
|
public let width: Int?
|
|
public let height: Int?
|
|
public let size: String?
|
|
public let aspect: Float?
|
|
|
|
public let small: ImageMetadata?
|
|
public let original: ImageMetadata?
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case length
|
|
case duration
|
|
case audioEncoding = "audio_encode"
|
|
case audioBitrate = "audio_bitrate"
|
|
case audioChannels = "audio_channels"
|
|
case fps
|
|
case width
|
|
case height
|
|
case size
|
|
case aspect
|
|
case small
|
|
case original
|
|
}
|
|
}
|
|
|
|
public class ImageMetadata: Decodable {
|
|
public let width: Int?
|
|
public let height: Int?
|
|
public let size: String?
|
|
public let aspect: Float?
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case width
|
|
case height
|
|
case size
|
|
case aspect
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate extension URL {
|
|
private static let allowedChars = CharacterSet.urlHostAllowed.union(.urlPathAllowed).union(.urlQueryAllowed)
|
|
|
|
init?(lenient string: String) {
|
|
guard let escaped = string.addingPercentEncoding(withAllowedCharacters: URL.allowedChars) else {
|
|
return nil
|
|
}
|
|
self.init(string: escaped)
|
|
}
|
|
}
|