diff --git a/Pachyderm/Client.swift b/Pachyderm/Client.swift index 21668cb0..efbf5830 100644 --- a/Pachyderm/Client.swift +++ b/Pachyderm/Client.swift @@ -109,7 +109,9 @@ public class Client { var urlRequest = URLRequest(url: url, timeoutInterval: timeoutInterval) urlRequest.httpMethod = request.method.name urlRequest.httpBody = request.body.data - urlRequest.setValue(request.body.mimeType, forHTTPHeaderField: "Content-Type") + if let mimeType = request.body.mimeType { + urlRequest.setValue(mimeType, forHTTPHeaderField: "Content-Type") + } if let accessToken = accessToken { urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") } @@ -150,6 +152,24 @@ public class Client { } } + public func nodeInfo(completion: @escaping Callback) { + let wellKnown = Request(method: .get, path: "/.well-known/nodeinfo") + run(wellKnown) { result in + switch result { + case let .failure(error): + completion(.failure(error)) + + case let .success(wellKnown, _): + if let url = wellKnown.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }), + let components = URLComponents(string: url.href), + components.host == self.baseURL.host { + let nodeInfo = Request(method: .get, path: components.path) + self.run(nodeInfo, completion: completion) + } + } + } + } + // MARK: - Self public static func getSelfAccount() -> Request { return Request(method: .get, path: "/api/v1/accounts/verify_credentials") diff --git a/Pachyderm/Model/NodeInfo.swift b/Pachyderm/Model/NodeInfo.swift new file mode 100644 index 00000000..5148eef8 --- /dev/null +++ b/Pachyderm/Model/NodeInfo.swift @@ -0,0 +1,19 @@ +// +// NodeInfo.swift +// Pachyderm +// +// Created by Shadowfacts on 1/22/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import Foundation + +public struct NodeInfo: Decodable { + public let version: String + public let software: Software + + public struct Software: Decodable { + public let name: String + public let version: String + } +} diff --git a/Pachyderm/Model/WellKnown.swift b/Pachyderm/Model/WellKnown.swift new file mode 100644 index 00000000..a3cd2262 --- /dev/null +++ b/Pachyderm/Model/WellKnown.swift @@ -0,0 +1,18 @@ +// +// WellKnown.swift +// Pachyderm +// +// Created by Shadowfacts on 1/22/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import Foundation + +struct WellKnown: Decodable { + let links: [Link] + + struct Link: Decodable { + let href: String + let rel: String + } +} diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 290817d2..bce06545 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -116,8 +116,11 @@ D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; }; D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; }; D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2425217ABF63005076CC /* UserActivityType.swift */; }; + D62E9981279C691F00C26176 /* NodeInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9980279C691F00C26176 /* NodeInfo.swift */; }; + D62E9983279C69D400C26176 /* WellKnown.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9982279C69D400C26176 /* WellKnown.swift */; }; D62E9985279CA23900C26176 /* URLSession+Development.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9984279CA23900C26176 /* URLSession+Development.swift */; }; D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */; }; + D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9988279DB2D100C26176 /* InstanceFeatures.swift */; }; D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */; }; D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; }; D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; }; @@ -523,8 +526,11 @@ D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = ""; }; D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = ""; }; D62D2425217ABF63005076CC /* UserActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityType.swift; sourceTree = ""; }; + D62E9980279C691F00C26176 /* NodeInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfo.swift; sourceTree = ""; }; + D62E9982279C69D400C26176 /* WellKnown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellKnown.swift; sourceTree = ""; }; D62E9984279CA23900C26176 /* URLSession+Development.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+Development.swift"; sourceTree = ""; }; D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMetaIndicatorsView.swift; sourceTree = ""; }; + D62E9988279DB2D100C26176 /* InstanceFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceFeatures.swift; sourceTree = ""; }; D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringHelperTests.swift; sourceTree = ""; }; D6311C4F25B3765B00B27539 /* ImageDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataCache.swift; sourceTree = ""; }; D6333B362137838300CE884A /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = ""; }; @@ -905,6 +911,7 @@ D61099F02145686D00432DC2 /* List.swift */, D6109A062145756700432DC2 /* LoginSettings.swift */, D61099F22145688600432DC2 /* Mention.swift */, + D62E9980279C691F00C26176 /* NodeInfo.swift */, D61099F4214568C300432DC2 /* Notification.swift */, D623A53E2635F6910095BD04 /* Poll.swift */, D61099F62145693500432DC2 /* PushSubscription.swift */, @@ -916,6 +923,7 @@ D61099FE21456A4C00432DC2 /* Status.swift */, D6285B4E21EA695800FE4B39 /* StatusContentType.swift */, D6109A10214607D500432DC2 /* Timeline.swift */, + D62E9982279C69D400C26176 /* WellKnown.swift */, ); path = Model; sourceTree = ""; @@ -1590,6 +1598,7 @@ D6945C2E23AC47C3005C403C /* SavedDataManager.swift */, D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */, D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */, + D62E9988279DB2D100C26176 /* InstanceFeatures.swift */, D6D4DDD6212518A200E1C4BB /* Assets.xcassets */, D6AEBB3F2321640F00E5038B /* Activities */, D6F1F84E2193B9BE00F5FE67 /* Caching */, @@ -1999,6 +2008,7 @@ files = ( D61099E5214561AB00432DC2 /* Application.swift in Sources */, D667383C23299340000A2373 /* InstanceType.swift in Sources */, + D62E9983279C69D400C26176 /* WellKnown.swift in Sources */, D61099FF21456A4C00432DC2 /* Status.swift in Sources */, D61099E32144C38900432DC2 /* Emoji.swift in Sources */, D6109A0D214599E100432DC2 /* RequestRange.swift in Sources */, @@ -2026,6 +2036,7 @@ D6109A032145722C00432DC2 /* RegisteredApplication.swift in Sources */, D6109A0921458C4A00432DC2 /* Empty.swift in Sources */, D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */, + D62E9981279C691F00C26176 /* NodeInfo.swift in Sources */, D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */, D61099DF2144C11400432DC2 /* MastodonError.swift in Sources */, D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */, @@ -2080,6 +2091,7 @@ D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */, D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */, D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */, + D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */, D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */, D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */, D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */, diff --git a/Tusker/Controllers/MastodonController.swift b/Tusker/Controllers/MastodonController.swift index f9a646bf..09478903 100644 --- a/Tusker/Controllers/MastodonController.swift +++ b/Tusker/Controllers/MastodonController.swift @@ -44,6 +44,8 @@ class MastodonController: ObservableObject { @Published private(set) var account: Account! @Published private(set) var instance: Instance! + @Published private(set) var nodeInfo: NodeInfo! + @Published private(set) var instanceFeatures = InstanceFeatures() private(set) var customEmojis: [Emoji]? private var pendingOwnInstanceRequestCallbacks = [(Instance) -> Void]() @@ -161,6 +163,7 @@ class MastodonController: ObservableObject { DispatchQueue.main.async { self.ownInstanceRequest = nil self.instance = instance + self.instanceFeatures.update(instance: instance, nodeInfo: self.nodeInfo) for completion in self.pendingOwnInstanceRequestCallbacks { completion(instance) @@ -169,6 +172,21 @@ class MastodonController: ObservableObject { } } } + + client.nodeInfo { result in + switch result { + case let .failure(error): + print("Unable to get node info: \(error)") + + case let .success(nodeInfo, _): + DispatchQueue.main.async { + self.nodeInfo = nodeInfo + if let instance = self.instance { + self.instanceFeatures.update(instance: instance, nodeInfo: nodeInfo) + } + } + } + } } } } diff --git a/Tusker/InstanceFeatures.swift b/Tusker/InstanceFeatures.swift new file mode 100644 index 00000000..21dad119 --- /dev/null +++ b/Tusker/InstanceFeatures.swift @@ -0,0 +1,20 @@ +// +// InstanceFeatures.swift +// Tusker +// +// Created by Shadowfacts on 1/23/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import Foundation +import Pachyderm + +struct InstanceFeatures { + private(set) var maxStatusChars = 500 + private(set) var localOnlyPosts = false + + mutating func update(instance: Instance, nodeInfo: NodeInfo?) { + maxStatusChars = instance.maxStatusCharacters ?? 500 + localOnlyPosts = nodeInfo?.software.name == "hometown" + } +} diff --git a/Tusker/Screens/Compose/ComposeView.swift b/Tusker/Screens/Compose/ComposeView.swift index 9fe304bb..da6f5382 100644 --- a/Tusker/Screens/Compose/ComposeView.swift +++ b/Tusker/Screens/Compose/ComposeView.swift @@ -28,7 +28,7 @@ struct ComposeView: View { } var charactersRemaining: Int { - let limit = mastodonController.instance?.maxStatusCharacters ?? 500 + let limit = mastodonController.instanceFeatures.maxStatusChars let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0 return limit - (cwCount + CharacterCounter.count(text: draft.text, for: mastodonController.instance)) }