Fix not posting status special characters or dropping part of it (Post in JSON now)

This commit is contained in:
Thomas Ricouard 2023-01-15 14:51:09 +01:00
parent 9e569df417
commit d05d9fbfff
4 changed files with 92 additions and 72 deletions

View File

@ -66,18 +66,29 @@ public class Client: ObservableObject, Equatable {
return components.url! return components.url!
} }
private func makeURLRequest(url: URL, httpMethod: String) -> URLRequest { private func makeURLRequest(url: URL, endpoint: Endpoint, httpMethod: String) -> URLRequest {
var request = URLRequest(url: url) var request = URLRequest(url: url)
request.httpMethod = httpMethod request.httpMethod = httpMethod
if let oauthToken { if let oauthToken {
request.setValue("Bearer \(oauthToken.accessToken)", forHTTPHeaderField: "Authorization") request.setValue("Bearer \(oauthToken.accessToken)", forHTTPHeaderField: "Authorization")
} }
if let json = endpoint.jsonValue {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
do {
let jsonData = try encoder.encode(json)
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
} catch {
print("Client Error encoding JSON: \(error.localizedDescription)")
}
}
return request return request
} }
private func makeGet(endpoint: Endpoint) -> URLRequest { private func makeGet(endpoint: Endpoint) -> URLRequest {
let url = makeURL(endpoint: endpoint) let url = makeURL(endpoint: endpoint)
return makeURLRequest(url: url, httpMethod: "GET") return makeURLRequest(url: url, endpoint: endpoint, httpMethod: "GET")
} }
public func get<Entity: Decodable>(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> Entity { public func get<Entity: Decodable>(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> Entity {
@ -101,14 +112,14 @@ public class Client: ObservableObject, Equatable {
public func post(endpoint: Endpoint) async throws -> HTTPURLResponse? { public func post(endpoint: Endpoint) async throws -> HTTPURLResponse? {
let url = makeURL(endpoint: endpoint) let url = makeURL(endpoint: endpoint)
let request = makeURLRequest(url: url, httpMethod: "POST") let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "POST")
let (_, httpResponse) = try await urlSession.data(for: request) let (_, httpResponse) = try await urlSession.data(for: request)
return httpResponse as? HTTPURLResponse return httpResponse as? HTTPURLResponse
} }
public func patch(endpoint: Endpoint) async throws -> HTTPURLResponse? { public func patch(endpoint: Endpoint) async throws -> HTTPURLResponse? {
let url = makeURL(endpoint: endpoint) let url = makeURL(endpoint: endpoint)
let request = makeURLRequest(url: url, httpMethod: "PATCH") let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "PATCH")
let (_, httpResponse) = try await urlSession.data(for: request) let (_, httpResponse) = try await urlSession.data(for: request)
return httpResponse as? HTTPURLResponse return httpResponse as? HTTPURLResponse
} }
@ -119,7 +130,7 @@ public class Client: ObservableObject, Equatable {
public func delete(endpoint: Endpoint) async throws -> HTTPURLResponse? { public func delete(endpoint: Endpoint) async throws -> HTTPURLResponse? {
let url = makeURL(endpoint: endpoint) let url = makeURL(endpoint: endpoint)
let request = makeURLRequest(url: url, httpMethod: "DELETE") let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "DELETE")
let (_, httpResponse) = try await urlSession.data(for: request) let (_, httpResponse) = try await urlSession.data(for: request)
return httpResponse as? HTTPURLResponse return httpResponse as? HTTPURLResponse
} }
@ -128,7 +139,7 @@ public class Client: ObservableObject, Equatable {
method: String, method: String,
forceVersion: Version? = nil) async throws -> Entity { forceVersion: Version? = nil) async throws -> Entity {
let url = makeURL(endpoint: endpoint, forceVersion: forceVersion) let url = makeURL(endpoint: endpoint, forceVersion: forceVersion)
let request = makeURLRequest(url: url, httpMethod: method) let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
let (data, httpResponse) = try await urlSession.data(for: request) let (data, httpResponse) = try await urlSession.data(for: request)
logResponseOnError(httpResponse: httpResponse, data: data) logResponseOnError(httpResponse: httpResponse, data: data)
return try decoder.decode(Entity.self, from: data) return try decoder.decode(Entity.self, from: data)
@ -157,7 +168,7 @@ public class Client: ObservableObject, Equatable {
public func makeWebSocketTask(endpoint: Endpoint) -> URLSessionWebSocketTask { public func makeWebSocketTask(endpoint: Endpoint) -> URLSessionWebSocketTask {
let url = makeURL(scheme: "wss", endpoint: endpoint) let url = makeURL(scheme: "wss", endpoint: endpoint)
let request = makeURLRequest(url: url, httpMethod: "GET") let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "GET")
return urlSession.webSocketTask(with: request) return urlSession.webSocketTask(with: request)
} }
@ -168,7 +179,7 @@ public class Client: ObservableObject, Equatable {
filename: String, filename: String,
data: Data) async throws -> Entity { data: Data) async throws -> Entity {
let url = makeURL(endpoint: endpoint, forceVersion: version) let url = makeURL(endpoint: endpoint, forceVersion: version)
var request = makeURLRequest(url: url, httpMethod: method) var request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method)
let boundary = UUID().uuidString let boundary = UUID().uuidString
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let httpBody = NSMutableData() let httpBody = NSMutableData()

View File

@ -3,6 +3,13 @@ import Foundation
public protocol Endpoint { public protocol Endpoint {
func path() -> String func path() -> String
func queryItems() -> [URLQueryItem]? func queryItems() -> [URLQueryItem]?
var jsonValue: Encodable? { get }
}
extension Endpoint {
public var jsonValue: Encodable? {
nil
}
} }
extension Endpoint { extension Endpoint {

View File

@ -2,19 +2,8 @@ import Foundation
import Models import Models
public enum Statuses: Endpoint { public enum Statuses: Endpoint {
case postStatus(status: String, case postStatus(json: StatusData)
inReplyTo: String?, case editStatus(id: String, json: StatusData)
mediaIds: [String]?,
spoilerText: String?,
visibility: Visibility,
pollOptions: [String],
pollVotingFrequency: Bool?,
pollDuration: Int?)
case editStatus(id: String,
status: String,
mediaIds: [String]?,
spoilerText: String?,
visibility: Visibility)
case status(id: String) case status(id: String)
case context(id: String) case context(id: String)
case favourite(id: String) case favourite(id: String)
@ -34,7 +23,7 @@ public enum Statuses: Endpoint {
return "statuses" return "statuses"
case .status(let id): case .status(let id):
return "statuses/\(id)" return "statuses/\(id)"
case .editStatus(let id, _, _, _, _): case .editStatus(let id, _):
return "statuses/\(id)" return "statuses/\(id)"
case .context(let id): case .context(let id):
return "statuses/\(id)/context" return "statuses/\(id)/context"
@ -63,41 +52,6 @@ public enum Statuses: Endpoint {
public func queryItems() -> [URLQueryItem]? { public func queryItems() -> [URLQueryItem]? {
switch self { switch self {
case let .postStatus(status, inReplyTo, mediaIds, spoilerText, visibility, pollOptions, pollVotingFrequency, pollDuration):
var params: [URLQueryItem] = [.init(name: "status", value: status),
.init(name: "visibility", value: visibility.rawValue)]
if let inReplyTo {
params.append(.init(name: "in_reply_to_id", value: inReplyTo))
}
if let mediaIds {
for mediaId in mediaIds {
params.append(.init(name: "media_ids[]", value: mediaId))
}
}
if let spoilerText {
params.append(.init(name: "spoiler_text", value: spoilerText))
}
if !pollOptions.isEmpty, let pollVotingFrequency, let pollDuration {
for option in pollOptions {
params.append(.init(name: "poll[options][]", value: option))
}
params.append(.init(name: "poll[multiple]", value: pollVotingFrequency ? "true" : "false"))
params.append(.init(name: "poll[expires_in]", value: "\(pollDuration)"))
}
return params
case let .editStatus(_, status, mediaIds, spoilerText, visibility):
var params: [URLQueryItem] = [.init(name: "status", value: status),
.init(name: "visibility", value: visibility.rawValue)]
if let mediaIds {
for mediaId in mediaIds {
params.append(.init(name: "media_ids[]", value: mediaId))
}
}
if let spoilerText {
params.append(.init(name: "spoiler_text", value: spoilerText))
}
return params
case let .rebloggedBy(_, maxId): case let .rebloggedBy(_, maxId):
return makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil) return makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil)
case let .favouritedBy(_, maxId): case let .favouritedBy(_, maxId):
@ -106,4 +60,50 @@ public enum Statuses: Endpoint {
return nil return nil
} }
} }
public var jsonValue: Encodable? {
switch self {
case let .postStatus(json):
return json
case let .editStatus(_, json):
return json
default:
return nil
}
}
}
public struct StatusData: Encodable {
public let status: String
public let visibility: Visibility
public let inReplyToId: String?
public let spoilerText: String?
public let mediaIds: [String]?
public let poll: PollData?
public struct PollData: Encodable {
public let options: [String]
public let multiple: Bool
public let expires_in: Int
public init(options: [String], multiple: Bool, expires_in: Int) {
self.options = options
self.multiple = multiple
self.expires_in = expires_in
}
}
public init(status: String,
visibility: Visibility,
inReplyToId: String? = nil,
spoilerText: String? = nil,
mediaIds: [String]? = nil,
poll: PollData? = nil) {
self.status = status
self.visibility = visibility
self.inReplyToId = inReplyToId
self.spoilerText = spoilerText
self.mediaIds = mediaIds
self.poll = poll
}
} }

View File

@ -95,8 +95,9 @@ public class StatusEditorViewModel: ObservableObject {
selectedRange = .init(location: text.utf16.count, length: 0) selectedRange = .init(location: text.utf16.count, length: 0)
} }
private func getPollOptionsForAPI() -> [String] { private func getPollOptionsForAPI() -> [String]? {
pollOptions.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty } let options = pollOptions.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
return options.isEmpty ? nil : options
} }
func postStatus() async -> Status? { func postStatus() async -> Status? {
@ -104,22 +105,23 @@ public class StatusEditorViewModel: ObservableObject {
do { do {
isPosting = true isPosting = true
let postStatus: Status? let postStatus: Status?
var pollData: StatusData.PollData?
if let pollOptions = getPollOptionsForAPI() {
pollData = .init(options: pollOptions,
multiple: pollVotingFrequency.canVoteMultipleTimes,
expires_in: pollDuration.rawValue)
}
let data = StatusData(status: statusText.string,
visibility: visibility,
inReplyToId: mode.replyToStatus?.id,
spoilerText: spoilerOn ? spoilerText : nil,
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
poll: pollData)
switch mode { switch mode {
case .new, .replyTo, .quote, .mention: case .new, .replyTo, .quote, .mention:
postStatus = try await client.post(endpoint: Statuses.postStatus(status: statusText.string, postStatus = try await client.post(endpoint: Statuses.postStatus(json: data))
inReplyTo: mode.replyToStatus?.id,
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
spoilerText: spoilerOn ? spoilerText : nil,
visibility: visibility,
pollOptions: getPollOptionsForAPI(),
pollVotingFrequency: pollVotingFrequency.canVoteMultipleTimes,
pollDuration: pollDuration.rawValue))
case let .edit(status): case let .edit(status):
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data))
status: statusText.string,
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
spoilerText: spoilerOn ? spoilerText : nil,
visibility: visibility))
} }
generator.notificationOccurred(.success) generator.notificationOccurred(.success)
isPosting = false isPosting = false