forked from zelo72/mastodon-ios
Merge pull request #49 from tootsuite/feature/setup-avatar
Update avatar and display name after sign-up flow
This commit is contained in:
commit
00cf194f0b
|
@ -7,7 +7,7 @@
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>7</integer>
|
<integer>10</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
<key>Mastodon.xcscheme_^#shared#^_</key>
|
<key>Mastodon.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>8</integer>
|
<integer>7</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -111,7 +111,24 @@ extension MastodonConfirmEmailViewController {
|
||||||
case .failure(let error):
|
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)
|
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:
|
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
|
} 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)
|
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)
|
||||||
|
|
|
@ -12,20 +12,29 @@ import MastodonSDK
|
||||||
final class MastodonConfirmEmailViewModel {
|
final class MastodonConfirmEmailViewModel {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
var email: String
|
var email: String
|
||||||
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
|
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
|
||||||
let userToken: Mastodon.Entity.Token
|
let userToken: Mastodon.Entity.Token
|
||||||
|
let updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery
|
||||||
|
|
||||||
let timestampUpdatePublisher = Timer.publish(every: 4.0, on: .main, in: .common)
|
let timestampUpdatePublisher = Timer.publish(every: 4.0, on: .main, in: .common)
|
||||||
.autoconnect()
|
.autoconnect()
|
||||||
.share()
|
.share()
|
||||||
.eraseToAnyPublisher()
|
.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.context = context
|
||||||
self.email = email
|
self.email = email
|
||||||
self.authenticateInfo = authenticateInfo
|
self.authenticateInfo = authenticateInfo
|
||||||
self.userToken = userToken
|
self.userToken = userToken
|
||||||
|
self.updateCredentialQuery = updateCredentialQuery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
// Created by MainasuK Cirno on 2021-2-5.
|
// Created by MainasuK Cirno on 2021-2-5.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import AlamofireImage
|
||||||
import Combine
|
import Combine
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import os.log
|
import os.log
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
import UIKit
|
import UIKit
|
||||||
import UITextField_Shake
|
|
||||||
|
|
||||||
final class MastodonRegisterViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance {
|
final class MastodonRegisterViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
@ -623,10 +623,10 @@ extension MastodonRegisterViewController {
|
||||||
username: username,
|
username: username,
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
agreement: true, // TODO:
|
agreement: true, // user confirmed in the server rules scene
|
||||||
locale: "en" // TODO:
|
locale: Locale.current.languageCode ?? "en"
|
||||||
)
|
)
|
||||||
|
|
||||||
// register without show server rules
|
// register without show server rules
|
||||||
context.apiService.accountRegister(
|
context.apiService.accountRegister(
|
||||||
domain: viewModel.domain,
|
domain: viewModel.domain,
|
||||||
|
@ -646,7 +646,21 @@ extension MastodonRegisterViewController {
|
||||||
} receiveValue: { [weak self] response in
|
} receiveValue: { [weak self] response in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let userToken = response.value
|
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)
|
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -43,6 +43,39 @@ extension APIService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func accountUpdateCredentials(
|
||||||
|
domain: String,
|
||||||
|
query: Mastodon.API.Account.UpdateCredentialQuery,
|
||||||
|
authorization: Mastodon.API.OAuth.Authorization
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||||
|
return Mastodon.API.Account.updateCredentials(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
query: query,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, 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(
|
func accountRegister(
|
||||||
domain: String,
|
domain: String,
|
||||||
query: Mastodon.API.Account.RegisterQuery,
|
query: Mastodon.API.Account.RegisterQuery,
|
||||||
|
|
|
@ -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<Mastodon.Response.Content<Mastodon.Entity.Token>, 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<Mastodon.Response.Content<Mastodon.Entity.Account>, 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<Mastodon.Response.Content<Mastodon.Entity.Account>, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,119 +8,17 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
// MARK: - Retrieve information
|
||||||
extension Mastodon.API.Account {
|
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 {
|
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)
|
.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<Mastodon.Response.Content<Mastodon.Entity.Account>, 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<Mastodon.Response.Content<Mastodon.Entity.Token>, 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<Mastodon.Response.Content<Mastodon.Entity.Account>, 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.
|
/// View information about a profile.
|
||||||
///
|
///
|
||||||
/// - Since: 0.0.0
|
/// - Since: 0.0.0
|
||||||
|
@ -138,11 +36,11 @@ extension Mastodon.API.Account {
|
||||||
public static func accountInfo(
|
public static func accountInfo(
|
||||||
session: URLSession,
|
session: URLSession,
|
||||||
domain: String,
|
domain: String,
|
||||||
query: AccountInfoQuery,
|
userID: Mastodon.Entity.Account.ID,
|
||||||
authorization: Mastodon.API.OAuth.Authorization?
|
authorization: Mastodon.API.OAuth.Authorization?
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||||
let request = Mastodon.API.get(
|
let request = Mastodon.API.get(
|
||||||
url: accountsInfoEndpointURL(domain: domain, id: query.id),
|
url: accountsInfoEndpointURL(domain: domain, id: userID),
|
||||||
query: nil,
|
query: nil,
|
||||||
authorization: authorization
|
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -140,8 +140,13 @@ extension Mastodon.API {
|
||||||
timeoutInterval: Mastodon.API.timeoutInterval
|
timeoutInterval: Mastodon.API.timeoutInterval
|
||||||
)
|
)
|
||||||
request.httpMethod = method.rawValue
|
request.httpMethod = method.rawValue
|
||||||
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
|
if let contentType = query?.contentType {
|
||||||
request.httpBody = query?.body
|
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 {
|
if let authorization = authorization {
|
||||||
request.setValue(
|
request.setValue(
|
||||||
"Bearer \(authorization.accessToken)",
|
"Bearer \(authorization.accessToken)",
|
||||||
|
|
|
@ -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)!
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,4 +12,5 @@ public enum Mastodon {
|
||||||
public enum Response { }
|
public enum Response { }
|
||||||
public enum API { }
|
public enum API { }
|
||||||
public enum Entity { }
|
public enum Entity { }
|
||||||
|
public enum Query { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// Mastodon+Entity+MediaAttachment.swift
|
// MediaAttachment.swift
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Created by jk234ert on 2/9/21.
|
// Created by jk234ert on 2/9/21.
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension Mastodon.Entity {
|
extension Mastodon.Query {
|
||||||
public enum MediaAttachment {
|
public enum MediaAttachment {
|
||||||
/// JPEG (Joint Photographic Experts Group) image
|
/// JPEG (Joint Photographic Experts Group) image
|
||||||
case jpeg(Data?)
|
case jpeg(Data?)
|
||||||
|
@ -20,7 +20,7 @@ extension Mastodon.Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Mastodon.Entity.MediaAttachment {
|
extension Mastodon.Query.MediaAttachment {
|
||||||
var data: Data? {
|
var data: Data? {
|
||||||
switch self {
|
switch self {
|
||||||
case .jpeg(let data): return data
|
case .jpeg(let data): return data
|
||||||
|
@ -31,11 +31,12 @@ extension Mastodon.Entity.MediaAttachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileName: String {
|
var fileName: String {
|
||||||
|
let name = UUID().uuidString
|
||||||
switch self {
|
switch self {
|
||||||
case .jpeg: return "file.jpg"
|
case .jpeg: return "\(name).jpg"
|
||||||
case .gif: return "file.gif"
|
case .gif: return "\(name).gif"
|
||||||
case .png: return "file.png"
|
case .png: return "\(name).png"
|
||||||
case .other(_, let fileExtension, _): return "file.\(fileExtension)"
|
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 }
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
}
|
|
@ -14,12 +14,22 @@ enum RequestMethod: String {
|
||||||
protocol RequestQuery {
|
protocol RequestQuery {
|
||||||
// All kinds of queries could have queryItems and body
|
// All kinds of queries could have queryItems and body
|
||||||
var queryItems: [URLQueryItem]? { get }
|
var queryItems: [URLQueryItem]? { get }
|
||||||
|
var contentType: String? { get }
|
||||||
var body: Data? { 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
|
// An `Encodable` query provides its body by encoding itself
|
||||||
// A `Get` query only contains queryItems, it should not be `Encodable`
|
// A `Get` query only contains queryItems, it should not be `Encodable`
|
||||||
extension RequestQuery where Self: Encodable {
|
extension RequestQuery where Self: Encodable {
|
||||||
|
var contentType: String? {
|
||||||
|
return "application/json; charset=utf-8"
|
||||||
|
}
|
||||||
var body: Data? {
|
var body: Data? {
|
||||||
return try? Mastodon.API.encoder.encode(self)
|
return try? Mastodon.API.encoder.encode(self)
|
||||||
}
|
}
|
||||||
|
@ -30,18 +40,20 @@ protocol GetQuery: RequestQuery { }
|
||||||
extension GetQuery {
|
extension GetQuery {
|
||||||
// By default a `GetQuery` does not has data body
|
// By default a `GetQuery` does not has data body
|
||||||
var body: Data? { nil }
|
var body: Data? { nil }
|
||||||
|
var contentType: String? { nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol PostQuery: RequestQuery & Encodable { }
|
protocol PostQuery: RequestQuery { }
|
||||||
|
|
||||||
extension PostQuery {
|
extension PostQuery {
|
||||||
// By default a `GetQuery` does not has query items
|
// By default a `PostQuery` does not has query items
|
||||||
var queryItems: [URLQueryItem]? { nil }
|
var queryItems: [URLQueryItem]? { nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol PatchQuery: RequestQuery & Encodable { }
|
protocol PatchQuery: RequestQuery { }
|
||||||
|
|
||||||
extension PatchQuery {
|
extension PatchQuery {
|
||||||
// By default a `GetQuery` does not has query items
|
// By default a `PatchQuery` does not has query items
|
||||||
var queryItems: [URLQueryItem]? { nil }
|
var queryItems: [URLQueryItem]? { nil }
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,11 @@
|
||||||
import os.log
|
import os.log
|
||||||
import XCTest
|
import XCTest
|
||||||
import Combine
|
import Combine
|
||||||
|
import UIKit
|
||||||
@testable import MastodonSDK
|
@testable import MastodonSDK
|
||||||
|
|
||||||
extension MastodonSDKTests {
|
extension MastodonSDKTests {
|
||||||
|
|
||||||
func testVerifyCredentials() throws {
|
func testVerifyCredentials() throws {
|
||||||
let theExpectation = expectation(description: "Verify Account Credentials")
|
let theExpectation = expectation(description: "Verify Account Credentials")
|
||||||
|
|
||||||
|
@ -44,11 +46,14 @@ extension MastodonSDKTests {
|
||||||
.flatMap({ (result) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
.flatMap({ (result) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
||||||
|
|
||||||
// TODO: replace with test account acct
|
// TODO: replace with test account acct
|
||||||
XCTAssertEqual(result.value.acct, "")
|
XCTAssert(!result.value.acct.isEmpty)
|
||||||
theExpectation1.fulfill()
|
theExpectation1.fulfill()
|
||||||
|
|
||||||
var query = Mastodon.API.Account.UpdateCredentialQuery()
|
let query = Mastodon.API.Account.UpdateCredentialQuery(
|
||||||
query.note = dateString
|
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)
|
return Mastodon.API.Account.updateCredentials(session: self.session, domain: self.domain, query: query, authorization: authorization)
|
||||||
})
|
})
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
|
@ -73,8 +78,7 @@ extension MastodonSDKTests {
|
||||||
func testRetrieveAccountInfo() throws {
|
func testRetrieveAccountInfo() throws {
|
||||||
let theExpectation = expectation(description: "Verify Account Credentials")
|
let theExpectation = expectation(description: "Verify Account Credentials")
|
||||||
|
|
||||||
let query = Mastodon.API.Account.AccountInfoQuery(id: "1")
|
Mastodon.API.Account.accountInfo(session: session, domain: "mastodon.online", userID: "1", authorization: nil)
|
||||||
Mastodon.API.Account.accountInfo(session: session, domain: domain, query: query, authorization: nil)
|
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
switch completion {
|
switch completion {
|
||||||
|
@ -91,4 +95,5 @@ extension MastodonSDKTests {
|
||||||
|
|
||||||
wait(for: [theExpectation], timeout: 5.0)
|
wait(for: [theExpectation], timeout: 5.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue