diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
index 60ccd3d87..6ec23cf5d 100644
--- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
CoreDataStack.xcscheme_^#shared#^_
orderHint
- 7
+ 10
Mastodon - RTL.xcscheme_^#shared#^_
@@ -22,7 +22,7 @@
Mastodon.xcscheme_^#shared#^_
orderHint
- 8
+ 7
SuppressBuildableAutocreation
diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
index 2d69f0dd3..dee1510cd 100644
--- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
+++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift
@@ -111,7 +111,24 @@ extension MastodonConfirmEmailViewController {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: swap user access token swap fail: %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
case .finished:
- break
+ // upload avatar and set display name in the background
+ self.context.apiService.accountUpdateCredentials(
+ domain: self.viewModel.authenticateInfo.domain,
+ query: self.viewModel.updateCredentialQuery,
+ authorization: Mastodon.API.OAuth.Authorization(accessToken: self.viewModel.userToken.accessToken)
+ )
+ .retry(3)
+ .sink { completion in
+ switch completion {
+ case .failure(let error):
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: setup avatar & display name fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
+ case .finished:
+ os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: setup avatar & display name success", ((#file as NSString).lastPathComponent), #line, #function)
+ }
+ } receiveValue: { _ in
+ // do nothing
+ }
+ .store(in: &self.context.disposeBag) // execute in the background
}
} receiveValue: { response in
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: user %s's email confirmed", ((#file as NSString).lastPathComponent), #line, #function, response.value.username)
diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift
index aff254741..9fbd24eda 100644
--- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift
+++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewModel.swift
@@ -12,20 +12,29 @@ import MastodonSDK
final class MastodonConfirmEmailViewModel {
var disposeBag = Set()
+ // input
let context: AppContext
var email: String
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
let userToken: Mastodon.Entity.Token
+ let updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery
let timestampUpdatePublisher = Timer.publish(every: 4.0, on: .main, in: .common)
.autoconnect()
.share()
.eraseToAnyPublisher()
- init(context: AppContext, email: String, authenticateInfo: AuthenticationViewModel.AuthenticateInfo, userToken: Mastodon.Entity.Token) {
+ init(
+ context: AppContext,
+ email: String,
+ authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
+ userToken: Mastodon.Entity.Token,
+ updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery
+ ) {
self.context = context
self.email = email
self.authenticateInfo = authenticateInfo
self.userToken = userToken
+ self.updateCredentialQuery = updateCredentialQuery
}
}
diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
index d1ef11c67..f078e9b8d 100644
--- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
+++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift
@@ -5,12 +5,12 @@
// Created by MainasuK Cirno on 2021-2-5.
//
+import AlamofireImage
import Combine
import MastodonSDK
import os.log
import PhotosUI
import UIKit
-import UITextField_Shake
final class MastodonRegisterViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance {
var disposeBag = Set()
@@ -623,10 +623,10 @@ extension MastodonRegisterViewController {
username: username,
email: email,
password: password,
- agreement: true, // TODO:
- locale: "en" // TODO:
+ agreement: true, // user confirmed in the server rules scene
+ locale: Locale.current.languageCode ?? "en"
)
-
+
// register without show server rules
context.apiService.accountRegister(
domain: viewModel.domain,
@@ -646,7 +646,21 @@ extension MastodonRegisterViewController {
} receiveValue: { [weak self] response in
guard let self = self else { return }
let userToken = response.value
- let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
+ let updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery = {
+ let displayName: String? = self.viewModel.displayName.value.isEmpty ? nil : self.viewModel.displayName.value
+ let avatar: Mastodon.Query.MediaAttachment? = {
+ guard let avatarImage = self.viewModel.avatarImage.value else { return nil }
+ guard avatarImage.size.width <= 400 else {
+ return .jpeg(avatarImage.af.imageScaled(to: CGSize(width: 400, height: 400)).jpegData(compressionQuality: 0.8))
+ }
+ return .jpeg(avatarImage.jpegData(compressionQuality: 0.8))
+ }()
+ return Mastodon.API.Account.UpdateCredentialQuery(
+ displayName: displayName,
+ avatar: avatar
+ )
+ }()
+ let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken, updateCredentialQuery: updateCredentialQuery)
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
}
.store(in: &disposeBag)
diff --git a/Mastodon/Service/APIService/APIService+Account.swift b/Mastodon/Service/APIService/APIService+Account.swift
index 6e26dbf83..2218bfa50 100644
--- a/Mastodon/Service/APIService/APIService+Account.swift
+++ b/Mastodon/Service/APIService/APIService+Account.swift
@@ -43,6 +43,39 @@ extension APIService {
.eraseToAnyPublisher()
}
+ func accountUpdateCredentials(
+ domain: String,
+ query: Mastodon.API.Account.UpdateCredentialQuery,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) -> AnyPublisher, Error> {
+ return Mastodon.API.Account.updateCredentials(
+ session: session,
+ domain: domain,
+ query: query,
+ authorization: authorization
+ )
+ .flatMap { response -> AnyPublisher, Error> in
+ let log = OSLog.api
+ let account = response.value
+
+ return self.backgroundManagedObjectContext.performChanges {
+ let (mastodonUser, isCreated) = APIService.CoreData.createOrMergeMastodonUser(
+ into: self.backgroundManagedObjectContext,
+ for: nil,
+ in: domain,
+ entity: account,
+ networkDate: response.networkDate,
+ log: log)
+ let flag = isCreated ? "+" : "-"
+ os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: mastodon user [%s](%s)%s verifed", ((#file as NSString).lastPathComponent), #line, #function, flag, mastodonUser.id, mastodonUser.username)
+ }
+ .setFailureType(to: Error.self)
+ .map { _ in return response }
+ .eraseToAnyPublisher()
+ }
+ .eraseToAnyPublisher()
+ }
+
func accountRegister(
domain: String,
query: Mastodon.API.Account.RegisterQuery,
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Credentials.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Credentials.swift
new file mode 100644
index 000000000..04273188b
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+Credentials.swift
@@ -0,0 +1,227 @@
+//
+// Mastodon+API+Account+Credentials.swift
+//
+//
+// Created by MainasuK Cirno on 2021-3-8.
+//
+
+import Foundation
+import Combine
+
+// MARK: - Account credentials
+extension Mastodon.API.Account {
+
+ static func accountsEndpointURL(domain: String) -> URL {
+ return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts")
+ }
+
+ /// Register an account
+ ///
+ /// Creates a user and account records.
+ ///
+ /// - Since: 2.7.0
+ /// - Version: 3.3.0
+ /// # Last Update
+ /// 2021/2/9
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/accounts/)
+ /// - Parameters:
+ /// - session: `URLSession`
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - query: `RegisterQuery` with account registration information
+ /// - authorization: App token
+ /// - Returns: `AnyPublisher` contains `Token` nested in the response
+ public static func register(
+ session: URLSession,
+ domain: String,
+ query: RegisterQuery,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) -> AnyPublisher, Error> {
+ let request = Mastodon.API.post(
+ url: accountsEndpointURL(domain: domain),
+ query: query,
+ authorization: authorization
+ )
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value = try Mastodon.API.decode(type: Mastodon.Entity.Token.self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+
+ public struct RegisterQuery: Codable, PostQuery {
+ public let reason: String?
+ public let username: String
+ public let email: String
+ public let password: String
+ public let agreement: Bool
+ public let locale: String
+
+ public init(reason: String? = nil, username: String, email: String, password: String, agreement: Bool, locale: String) {
+ self.reason = reason
+ self.username = username
+ self.email = email
+ self.password = password
+ self.agreement = agreement
+ self.locale = locale
+ }
+ }
+
+}
+
+extension Mastodon.API.Account {
+
+ static func verifyCredentialsEndpointURL(domain: String) -> URL {
+ return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/verify_credentials")
+ }
+
+ /// Verify account credentials
+ ///
+ /// Test to make sure that the user token works.
+ ///
+ /// - Since: 0.0.0
+ /// - Version: 3.3.0
+ /// # Last Update
+ /// 2021/2/9
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/accounts/)
+ /// - Parameters:
+ /// - session: `URLSession`
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - authorization: App token
+ /// - Returns: `AnyPublisher` contains `Account` nested in the response
+ public static func verifyCredentials(
+ session: URLSession,
+ domain: String,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) -> AnyPublisher, Error> {
+ let request = Mastodon.API.get(
+ url: verifyCredentialsEndpointURL(domain: domain),
+ query: nil,
+ authorization: authorization
+ )
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+
+ static func updateCredentialsEndpointURL(domain: String) -> URL {
+ return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/update_credentials")
+ }
+
+ /// Update account credentials
+ ///
+ /// Update the user's display and preferences.
+ ///
+ /// - Since: 1.1.1
+ /// - Version: 3.3.0
+ /// # Last Update
+ /// 2021/2/9
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/accounts/)
+ /// - Parameters:
+ /// - session: `URLSession`
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - query: `CredentialQuery` with update credential information
+ /// - authorization: user token
+ /// - Returns: `AnyPublisher` contains updated `Account` nested in the response
+ public static func updateCredentials(
+ session: URLSession,
+ domain: String,
+ query: UpdateCredentialQuery,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) -> AnyPublisher, Error> {
+ let request = Mastodon.API.patch(
+ url: updateCredentialsEndpointURL(domain: domain),
+ query: query,
+ authorization: authorization
+ )
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+
+ public struct UpdateCredentialQuery: PatchQuery {
+ public let discoverable: Bool?
+ public let bot: Bool?
+ public let displayName: String?
+ public let note: String?
+ public let avatar: Mastodon.Query.MediaAttachment?
+ public let header: Mastodon.Query.MediaAttachment?
+ public let locked: Bool?
+ public let source: Mastodon.Entity.Source?
+ public let fieldsAttributes: [Mastodon.Entity.Field]?
+
+ enum CodingKeys: String, CodingKey {
+ case discoverable
+ case bot
+ case displayName = "display_name"
+ case note
+
+ case avatar
+ case header
+ case locked
+ case source
+ case fieldsAttributes = "fields_attributes"
+ }
+
+ public init(
+ discoverable: Bool? = nil,
+ bot: Bool? = nil,
+ displayName: String? = nil,
+ note: String? = nil,
+ avatar: Mastodon.Query.MediaAttachment? = nil,
+ header: Mastodon.Query.MediaAttachment? = nil,
+ locked: Bool? = nil,
+ source: Mastodon.Entity.Source? = nil,
+ fieldsAttributes: [Mastodon.Entity.Field]? = nil
+ ) {
+ self.discoverable = discoverable
+ self.bot = bot
+ self.displayName = displayName
+ self.note = note
+ self.avatar = avatar
+ self.header = header
+ self.locked = locked
+ self.source = source
+ self.fieldsAttributes = fieldsAttributes
+ }
+
+ var contentType: String? {
+ return Self.multipartContentType()
+ }
+
+ var body: Data? {
+ var data = Data()
+
+ discoverable.flatMap { data.append(Data.multipart(key: "discoverable", value: $0)) }
+ bot.flatMap { data.append(Data.multipart(key: "bot", value: $0)) }
+ displayName.flatMap { data.append(Data.multipart(key: "display_name", value: $0)) }
+ note.flatMap { data.append(Data.multipart(key: "note", value: $0)) }
+ avatar.flatMap { data.append(Data.multipart(key: "avatar", value: $0)) }
+ header.flatMap { data.append(Data.multipart(key: "header", value: $0)) }
+ locked.flatMap { data.append(Data.multipart(key: "locked", value: $0)) }
+ if let source = source {
+ source.privacy.flatMap { data.append(Data.multipart(key: "source[privacy]", value: $0.rawValue)) }
+ source.sensitive.flatMap { data.append(Data.multipart(key: "source[privacy]", value: $0)) }
+ source.language.flatMap { data.append(Data.multipart(key: "source[privacy]", value: $0)) }
+ }
+ fieldsAttributes.flatMap { fieldsAttributes in
+ for fieldsAttribute in fieldsAttributes {
+ data.append(Data.multipart(key: "fields_attributes[name][]", value: fieldsAttribute.name))
+ data.append(Data.multipart(key: "fields_attributes[value][]", value: fieldsAttribute.value))
+ }
+ }
+ data.append(Data.multipartEnd())
+ return data
+ }
+ }
+
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift
index d9b2a4448..3bb81dc6b 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift
@@ -8,119 +8,17 @@
import Foundation
import Combine
+// MARK: - Retrieve information
extension Mastodon.API.Account {
-
- static func verifyCredentialsEndpointURL(domain: String) -> URL {
- return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/verify_credentials")
- }
- static func accountsEndpointURL(domain: String) -> URL {
- return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts")
- }
+
static func accountsInfoEndpointURL(domain: String, id: String) -> URL {
- return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts")
+ return Mastodon.API.endpointURL(domain: domain)
+ .appendingPathComponent("accounts")
.appendingPathComponent(id)
}
- static func updateCredentialsEndpointURL(domain: String) -> URL {
- return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/update_credentials")
- }
- /// Test to make sure that the user token works.
+ /// Retrieve information
///
- /// - Since: 0.0.0
- /// - Version: 3.3.0
- /// # Last Update
- /// 2021/2/9
- /// # Reference
- /// [Document](https://docs.joinmastodon.org/methods/accounts/)
- /// - Parameters:
- /// - session: `URLSession`
- /// - domain: Mastodon instance domain. e.g. "example.com"
- /// - authorization: App token
- /// - Returns: `AnyPublisher` contains `Account` nested in the response
- public static func verifyCredentials(
- session: URLSession,
- domain: String,
- authorization: Mastodon.API.OAuth.Authorization
- ) -> AnyPublisher, Error> {
- let request = Mastodon.API.get(
- url: verifyCredentialsEndpointURL(domain: domain),
- query: nil,
- authorization: authorization
- )
- return session.dataTaskPublisher(for: request)
- .tryMap { data, response in
- let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
- return Mastodon.Response.Content(value: value, response: response)
- }
- .eraseToAnyPublisher()
- }
-
- /// Creates a user and account records.
- ///
- /// - Since: 2.7.0
- /// - Version: 3.3.0
- /// # Last Update
- /// 2021/2/9
- /// # Reference
- /// [Document](https://docs.joinmastodon.org/methods/accounts/)
- /// - Parameters:
- /// - session: `URLSession`
- /// - domain: Mastodon instance domain. e.g. "example.com"
- /// - query: `RegisterQuery` with account registration information
- /// - authorization: App token
- /// - Returns: `AnyPublisher` contains `Token` nested in the response
- public static func register(
- session: URLSession,
- domain: String,
- query: RegisterQuery,
- authorization: Mastodon.API.OAuth.Authorization
- ) -> AnyPublisher, Error> {
- let request = Mastodon.API.post(
- url: accountsEndpointURL(domain: domain),
- query: query,
- authorization: authorization
- )
- return session.dataTaskPublisher(for: request)
- .tryMap { data, response in
- let value = try Mastodon.API.decode(type: Mastodon.Entity.Token.self, from: data, response: response)
- return Mastodon.Response.Content(value: value, response: response)
- }
- .eraseToAnyPublisher()
- }
-
- /// Update the user's display and preferences.
- ///
- /// - Since: 1.1.1
- /// - Version: 3.3.0
- /// # Last Update
- /// 2021/2/9
- /// # Reference
- /// [Document](https://docs.joinmastodon.org/methods/accounts/)
- /// - Parameters:
- /// - session: `URLSession`
- /// - domain: Mastodon instance domain. e.g. "example.com"
- /// - query: `CredentialQuery` with update credential information
- /// - authorization: user token
- /// - Returns: `AnyPublisher` contains updated `Account` nested in the response
- public static func updateCredentials(
- session: URLSession,
- domain: String,
- query: UpdateCredentialQuery,
- authorization: Mastodon.API.OAuth.Authorization
- ) -> AnyPublisher, Error> {
- let request = Mastodon.API.patch(
- url: updateCredentialsEndpointURL(domain: domain),
- query: query,
- authorization: authorization
- )
- return session.dataTaskPublisher(for: request)
- .tryMap { data, response in
- let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
- return Mastodon.Response.Content(value: value, response: response)
- }
- .eraseToAnyPublisher()
- }
-
/// View information about a profile.
///
/// - Since: 0.0.0
@@ -138,11 +36,11 @@ extension Mastodon.API.Account {
public static func accountInfo(
session: URLSession,
domain: String,
- query: AccountInfoQuery,
+ userID: Mastodon.Entity.Account.ID,
authorization: Mastodon.API.OAuth.Authorization?
) -> AnyPublisher, Error> {
let request = Mastodon.API.get(
- url: accountsInfoEndpointURL(domain: domain, id: query.id),
+ url: accountsInfoEndpointURL(domain: domain, id: userID),
query: nil,
authorization: authorization
)
@@ -155,79 +53,3 @@ extension Mastodon.API.Account {
}
}
-
-extension Mastodon.API.Account {
-
- public struct RegisterQuery: Codable, PostQuery {
- public let reason: String?
- public let username: String
- public let email: String
- public let password: String
- public let agreement: Bool
- public let locale: String
-
- public init(reason: String? = nil, username: String, email: String, password: String, agreement: Bool, locale: String) {
- self.reason = reason
- self.username = username
- self.email = email
- self.password = password
- self.agreement = agreement
- self.locale = locale
- }
- }
-
- public struct UpdateCredentialQuery: Codable, PatchQuery {
-
- public var discoverable: Bool?
- public var bot: Bool?
- public var displayName: String?
- public var note: String?
- public var avatar: String?
- public var header: String?
- public var locked: Bool?
- public var source: Mastodon.Entity.Source?
- public var fieldsAttributes: [Mastodon.Entity.Field]?
-
- enum CodingKeys: String, CodingKey {
- case discoverable
- case bot
- case displayName = "display_name"
- case note
-
- case avatar
- case header
- case locked
- case source
- case fieldsAttributes = "fields_attributes"
- }
-
- public init(
- discoverable: Bool? = nil,
- bot: Bool? = nil,
- displayName: String? = nil,
- note: String? = nil,
- avatar: Mastodon.Entity.MediaAttachment? = nil,
- header: Mastodon.Entity.MediaAttachment? = nil,
- locked: Bool? = nil,
- source: Mastodon.Entity.Source? = nil,
- fieldsAttributes: [Mastodon.Entity.Field]? = nil
- ) {
- self.discoverable = discoverable
- self.bot = bot
- self.displayName = displayName
- self.note = note
- self.avatar = avatar?.base64EncondedString
- self.header = header?.base64EncondedString
- self.locked = locked
- self.source = source
- self.fieldsAttributes = fieldsAttributes
- }
- }
-
- public struct AccountInfoQuery: GetQuery {
-
- public let id: String
-
- var queryItems: [URLQueryItem]? { nil }
- }
-}
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
index 5a55ee103..073d926e9 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
@@ -140,8 +140,13 @@ extension Mastodon.API {
timeoutInterval: Mastodon.API.timeoutInterval
)
request.httpMethod = method.rawValue
- request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
- request.httpBody = query?.body
+ if let contentType = query?.contentType {
+ request.setValue(contentType, forHTTPHeaderField: "Content-Type")
+ }
+ if let body = query?.body {
+ request.httpBody = body
+ request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
+ }
if let authorization = authorization {
request.setValue(
"Bearer \(authorization.accessToken)",
diff --git a/MastodonSDK/Sources/MastodonSDK/Extension/Data.swift b/MastodonSDK/Sources/MastodonSDK/Extension/Data.swift
new file mode 100644
index 000000000..43354394d
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/Extension/Data.swift
@@ -0,0 +1,37 @@
+//
+// Data.swift
+//
+//
+// Created by MainasuK Cirno on 2021-3-8.
+//
+
+import Foundation
+
+extension Data {
+
+ static func multipart(
+ boundary: String = Multipart.boundary,
+ key: String,
+ value: MultipartFormValue
+ ) -> Data {
+ var data = Data()
+ data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
+ data.append("Content-Disposition: form-data; name=\"\(key)\"".data(using: .utf8)!)
+ if let filename = value.multipartFilename {
+ data.append("; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
+ } else {
+ data.append("\r\n".data(using: .utf8)!)
+ }
+ if let contentType = value.multipartContentType {
+ data.append("Content-Type: \(contentType)\r\n".data(using: .utf8)!)
+ }
+ data.append("\r\n".data(using: .utf8)!)
+ data.append(value.multipartValue)
+ return data
+ }
+
+ static func multipartEnd(boundary: String = Multipart.boundary) -> Data {
+ return "\r\n--\(boundary)--\r\n".data(using: .utf8)!
+ }
+
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/Mastodon.swift b/MastodonSDK/Sources/MastodonSDK/Mastodon.swift
index 0c5e90609..b64d3726e 100644
--- a/MastodonSDK/Sources/MastodonSDK/Mastodon.swift
+++ b/MastodonSDK/Sources/MastodonSDK/Mastodon.swift
@@ -12,4 +12,5 @@ public enum Mastodon {
public enum Response { }
public enum API { }
public enum Entity { }
+ public enum Query { }
}
diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+MediaAttachment.swift b/MastodonSDK/Sources/MastodonSDK/Query/MediaAttachment.swift
similarity index 66%
rename from MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+MediaAttachment.swift
rename to MastodonSDK/Sources/MastodonSDK/Query/MediaAttachment.swift
index 39ffe23d7..f3bd88832 100644
--- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+MediaAttachment.swift
+++ b/MastodonSDK/Sources/MastodonSDK/Query/MediaAttachment.swift
@@ -1,5 +1,5 @@
//
-// Mastodon+Entity+MediaAttachment.swift
+// MediaAttachment.swift
//
//
// Created by jk234ert on 2/9/21.
@@ -7,7 +7,7 @@
import Foundation
-extension Mastodon.Entity {
+extension Mastodon.Query {
public enum MediaAttachment {
/// JPEG (Joint Photographic Experts Group) image
case jpeg(Data?)
@@ -20,7 +20,7 @@ extension Mastodon.Entity {
}
}
-extension Mastodon.Entity.MediaAttachment {
+extension Mastodon.Query.MediaAttachment {
var data: Data? {
switch self {
case .jpeg(let data): return data
@@ -31,11 +31,12 @@ extension Mastodon.Entity.MediaAttachment {
}
var fileName: String {
+ let name = UUID().uuidString
switch self {
- case .jpeg: return "file.jpg"
- case .gif: return "file.gif"
- case .png: return "file.png"
- case .other(_, let fileExtension, _): return "file.\(fileExtension)"
+ case .jpeg: return "\(name).jpg"
+ case .gif: return "\(name).gif"
+ case .png: return "\(name).png"
+ case .other(_, let fileExtension, _): return "\(name).\(fileExtension)"
}
}
@@ -53,3 +54,8 @@ extension Mastodon.Entity.MediaAttachment {
}
}
+extension Mastodon.Query.MediaAttachment: MultipartFormValue {
+ var multipartValue: Data { return data ?? Data() }
+ var multipartContentType: String? { return mimeType }
+ var multipartFilename: String? { return fileName }
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/Query/MultipartFormValue.swift b/MastodonSDK/Sources/MastodonSDK/Query/MultipartFormValue.swift
new file mode 100644
index 000000000..fd9a9c8f4
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/Query/MultipartFormValue.swift
@@ -0,0 +1,37 @@
+//
+// MultipartFormValue.swift
+//
+//
+// Created by MainasuK Cirno on 2021-3-8.
+//
+
+import Foundation
+
+enum Multipart {
+ static let boundary = "__boundary__"
+}
+
+protocol MultipartFormValue {
+ var multipartValue: Data { get }
+ var multipartContentType: String? { get }
+ var multipartFilename: String? { get }
+}
+
+extension Bool: MultipartFormValue {
+ var multipartValue: Data {
+ switch self {
+ case true: return "true".data(using: .utf8)!
+ case false: return "false".data(using: .utf8)!
+ }
+ }
+ var multipartContentType: String? { return nil }
+ var multipartFilename: String? { return nil }
+}
+
+extension String: MultipartFormValue {
+ var multipartValue: Data {
+ return self.data(using: .utf8)!
+ }
+ var multipartContentType: String? { return nil }
+ var multipartFilename: String? { return nil }
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/Protocol/Query.swift b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift
similarity index 61%
rename from MastodonSDK/Sources/MastodonSDK/Protocol/Query.swift
rename to MastodonSDK/Sources/MastodonSDK/Query/Query.swift
index 7a6608d66..a0a5e4eae 100644
--- a/MastodonSDK/Sources/MastodonSDK/Protocol/Query.swift
+++ b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift
@@ -14,12 +14,22 @@ enum RequestMethod: String {
protocol RequestQuery {
// All kinds of queries could have queryItems and body
var queryItems: [URLQueryItem]? { get }
+ var contentType: String? { get }
var body: Data? { get }
}
+extension RequestQuery {
+ static func multipartContentType(boundary: String = Multipart.boundary) -> String {
+ return "multipart/form-data; charset=utf-8; boundary=\"\(boundary)\""
+ }
+}
+
// An `Encodable` query provides its body by encoding itself
// A `Get` query only contains queryItems, it should not be `Encodable`
extension RequestQuery where Self: Encodable {
+ var contentType: String? {
+ return "application/json; charset=utf-8"
+ }
var body: Data? {
return try? Mastodon.API.encoder.encode(self)
}
@@ -30,18 +40,20 @@ protocol GetQuery: RequestQuery { }
extension GetQuery {
// By default a `GetQuery` does not has data body
var body: Data? { nil }
+ var contentType: String? { nil }
}
-protocol PostQuery: RequestQuery & Encodable { }
+protocol PostQuery: RequestQuery { }
extension PostQuery {
- // By default a `GetQuery` does not has query items
+ // By default a `PostQuery` does not has query items
var queryItems: [URLQueryItem]? { nil }
}
-protocol PatchQuery: RequestQuery & Encodable { }
+protocol PatchQuery: RequestQuery { }
extension PatchQuery {
- // By default a `GetQuery` does not has query items
+ // By default a `PatchQuery` does not has query items
var queryItems: [URLQueryItem]? { nil }
}
+
diff --git a/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+AccountTests.swift b/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+AccountTests.swift
index 08607aed8..b113672ec 100644
--- a/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+AccountTests.swift
+++ b/MastodonSDK/Tests/MastodonSDKTests/API/MastodonSDK+API+AccountTests.swift
@@ -8,9 +8,11 @@
import os.log
import XCTest
import Combine
+import UIKit
@testable import MastodonSDK
extension MastodonSDKTests {
+
func testVerifyCredentials() throws {
let theExpectation = expectation(description: "Verify Account Credentials")
@@ -44,11 +46,14 @@ extension MastodonSDKTests {
.flatMap({ (result) -> AnyPublisher, Error> in
// TODO: replace with test account acct
- XCTAssertEqual(result.value.acct, "")
+ XCTAssert(!result.value.acct.isEmpty)
theExpectation1.fulfill()
-
- var query = Mastodon.API.Account.UpdateCredentialQuery()
- query.note = dateString
+
+ let query = Mastodon.API.Account.UpdateCredentialQuery(
+ bot: !(result.value.bot ?? false),
+ note: dateString,
+ header: Mastodon.Query.MediaAttachment.jpeg(UIImage(systemName: "house")!.jpegData(compressionQuality: 0.8))
+ )
return Mastodon.API.Account.updateCredentials(session: self.session, domain: self.domain, query: query, authorization: authorization)
})
.sink { completion in
@@ -73,8 +78,7 @@ extension MastodonSDKTests {
func testRetrieveAccountInfo() throws {
let theExpectation = expectation(description: "Verify Account Credentials")
- let query = Mastodon.API.Account.AccountInfoQuery(id: "1")
- Mastodon.API.Account.accountInfo(session: session, domain: domain, query: query, authorization: nil)
+ Mastodon.API.Account.accountInfo(session: session, domain: "mastodon.online", userID: "1", authorization: nil)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
@@ -91,4 +95,5 @@ extension MastodonSDKTests {
wait(for: [theExpectation], timeout: 5.0)
}
+
}