From 99a1c76cb1c08a888a36a25997cb185a6651fdf1 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 14 Nov 2022 21:17:08 -0500 Subject: [PATCH] Clean up instance type/feature detection Add akkoma detection --- Tusker/API/InstanceFeatures.swift | 107 +++++++++++++----- Tusker/API/PostService.swift | 15 ++- Tusker/Models/Draft.swift | 13 --- .../Explore/ExploreViewController.swift | 4 +- .../Main/MainSidebarViewController.swift | 4 +- 5 files changed, 94 insertions(+), 49 deletions(-) diff --git a/Tusker/API/InstanceFeatures.swift b/Tusker/API/InstanceFeatures.swift index 1f2b376028..95c10a098e 100644 --- a/Tusker/API/InstanceFeatures.swift +++ b/Tusker/API/InstanceFeatures.swift @@ -11,15 +11,18 @@ import Pachyderm struct InstanceFeatures { private static let pleromaVersionRegex = try! NSRegularExpression(pattern: "\\(compatible; pleroma (.*)\\)", options: .caseInsensitive) + private static let akkomaVersionRegex = try! NSRegularExpression(pattern: "\\(compatible; akkoma (.*)\\)", options: .caseInsensitive) - private(set) var instanceType = InstanceType.mastodon - private(set) var version: Version? - private(set) var pleromaVersion: Version? - private(set) var hometownVersion: Version? + private var instanceType: InstanceType = .mastodon(.vanilla, nil) private(set) var maxStatusChars = 500 var localOnlyPosts: Bool { - instanceType == .hometown || instanceType == .glitch + switch instanceType { + case .mastodon(.hometown(_), _), .mastodon(.glitch, _): + return true + default: + return false + } } var mastodonAttachmentRestrictions: Bool { @@ -27,15 +30,20 @@ struct InstanceFeatures { } var pollsAndAttachments: Bool { - instanceType == .pleroma + instanceType.isPleroma } var boostToOriginalAudience: Bool { - instanceType == .pleroma || instanceType.isMastodon + instanceType.isPleroma || instanceType.isMastodon } var profilePinnedStatuses: Bool { - instanceType != .pixelfed + switch instanceType { + case .pixelfed: + return false + default: + return true + } } var trends: Bool { @@ -48,45 +56,65 @@ struct InstanceFeatures { var reblogVisibility: Bool { (instanceType.isMastodon && hasVersion(2, 8, 0)) - || (instanceType == .pleroma && hasPleromaVersion(2, 0, 0)) + || (instanceType.isPleroma && hasPleromaVersion(2, 0, 0)) } var probablySupportsMarkdown: Bool { - instanceType == .pleroma || instanceType == .glitch || instanceType == .hometown + switch instanceType { + case .pleroma(_), .mastodon(.glitch, _), .mastodon(.hometown(_), _): + return true + default: + return false + } + } + + var needsLocalOnlyEmojiHack: Bool { + if case .mastodon(.glitch, _) = instanceType { + return true + } else { + return false + } } mutating func update(instance: Instance, nodeInfo: NodeInfo?) { - var version: Version? - let ver = instance.version.lowercased() if ver.contains("glitch") { - instanceType = .glitch + instanceType = .mastodon(.glitch, Version(string: ver)) } else if nodeInfo?.software.name == "hometown" { - instanceType = .hometown + var mastoVersion: Version? + var hometownVersion: Version? // like "1.0.6+3.5.2" let parts = ver.split(separator: "+") if parts.count == 2 { - version = Version(string: String(parts[1])) + mastoVersion = Version(string: String(parts[1])) hometownVersion = Version(string: String(parts[0])) + } else { + mastoVersion = Version(string: ver) } + instanceType = .mastodon(.hometown(hometownVersion), mastoVersion) } else if ver.contains("pleroma") { - instanceType = .pleroma + var pleromaVersion: Version? if let match = InstanceFeatures.pleromaVersionRegex.firstMatch(in: ver, range: NSRange(location: 0, length: ver.utf16.count)) { pleromaVersion = Version(string: (ver as NSString).substring(with: match.range(at: 1))) } + instanceType = .pleroma(.vanilla(pleromaVersion)) + } else if ver.contains("akkoma") { + var akkomaVersion: Version? + if let match = InstanceFeatures.akkomaVersionRegex.firstMatch(in: ver, range: NSRange(location: 0, length: ver.utf16.count)) { + akkomaVersion = Version(string: (ver as NSString).substring(with: match.range(at: 1))) + } + instanceType = .pleroma(.akkoma(akkomaVersion)) } else if ver.contains("pixelfed") { instanceType = .pixelfed } else { - instanceType = .mastodon + instanceType = .mastodon(.vanilla, Version(string: ver)) } - self.version = version ?? Version(string: ver) - maxStatusChars = instance.maxStatusCharacters ?? 500 } func hasVersion(_ major: Int, _ minor: Int, _ patch: Int) -> Bool { - if let version { + if case .mastodon(_, .some(let version)) = instanceType { return version >= Version(major, minor, patch) } else { return false @@ -94,30 +122,47 @@ struct InstanceFeatures { } func hasPleromaVersion(_ major: Int, _ minor: Int, _ patch: Int) -> Bool { - if let pleromaVersion { - return pleromaVersion >= Version(major, minor, patch) - } else { + switch instanceType { + case .pleroma(.vanilla(.some(let version))), .pleroma(.akkoma(.some(let version))): + return version >= Version(major, minor, patch) + default: return false } } } extension InstanceFeatures { - enum InstanceType: Equatable { - case mastodon // vanilla - case pleroma - case hometown - case glitch + enum InstanceType { + case mastodon(MastodonType, Version?) + case pleroma(PleromaType) case pixelfed var isMastodon: Bool { - switch self { - case .mastodon, .hometown, .glitch: + if case .mastodon(_, _) = self { return true - default: + } else { return false } } + + var isPleroma: Bool { + if case .pleroma(_) = self { + return true + } else { + return false + } + } + } + + enum MastodonType { + case vanilla + case hometown(Version?) + case glitch + } + + enum PleromaType { + case vanilla(Version?) + case akkoma(Version?) } } diff --git a/Tusker/API/PostService.swift b/Tusker/API/PostService.swift index c952e99e8e..5628327c0b 100644 --- a/Tusker/API/PostService.swift +++ b/Tusker/API/PostService.swift @@ -39,7 +39,7 @@ class PostService: ObservableObject { let sensitive = contentWarning != nil let request = Client.createStatus( - text: draft.textForPosting(on: mastodonController.instanceFeatures), + text: textForPosting(), contentType: Preferences.shared.statusContentType, inReplyTo: draft.inReplyToID, media: uploadedAttachments, @@ -104,6 +104,19 @@ class PostService: ObservableObject { return try await mastodonController.run(req).0 } + private func textForPosting() -> String { + var text = draft.text + // when using dictation, iOS sometimes leaves a U+FFFC OBJECT REPLACEMENT CHARACTER behind in the text, + // which we want to strip out before actually posting the status + text = text.replacingOccurrences(of: "\u{fffc}", with: "") + + if draft.localOnly && mastodonController.instanceFeatures.needsLocalOnlyEmojiHack { + text += " 👁" + } + + return text + } + enum Error: Swift.Error, LocalizedError { case attachmentData(index: Int, cause: CompositionAttachmentData.Error) case attachmentUpload(index: Int, cause: Client.Error) diff --git a/Tusker/Models/Draft.swift b/Tusker/Models/Draft.swift index 6c2ef939de..c8ac41b339 100644 --- a/Tusker/Models/Draft.swift +++ b/Tusker/Models/Draft.swift @@ -86,19 +86,6 @@ class Draft: Codable, ObservableObject { try container.encode(initialText, forKey: .initialText) } - - func textForPosting(on instance: InstanceFeatures) -> String { - var text = self.text - // when using dictation, iOS sometimes leaves a U+FFFC OBJECT REPLACEMENT CHARACTER behind in the text, - // which we want to strip out before actually posting the status - text = text.replacingOccurrences(of: "\u{fffc}", with: "") - - if localOnly && instance.instanceType == .glitch { - text += " 👁" - } - - return text - } } extension Draft: Equatable { diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 602ac7c949..e41584e5f6 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -141,7 +141,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections(Section.allCases.filter { $0 != .discover }) snapshot.appendItems([.bookmarks], toSection: .bookmarks) - if mastodonController.instanceFeatures.instanceType.isMastodon, + if mastodonController.instanceFeatures.trends, !Preferences.shared.hideDiscover { addDiscoverSection(to: &snapshot) } @@ -172,7 +172,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { private func ownInstanceLoaded(_ instance: Instance) { var snapshot = self.dataSource.snapshot() - if mastodonController.instanceFeatures.instanceType.isMastodon, + if mastodonController.instanceFeatures.trends, !snapshot.sectionIdentifiers.contains(.discover) { snapshot.insertSections([.discover], afterSection: .bookmarks) snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover) diff --git a/Tusker/Screens/Main/MainSidebarViewController.swift b/Tusker/Screens/Main/MainSidebarViewController.swift index 9cfe9f2c36..2bfd1f8b6b 100644 --- a/Tusker/Screens/Main/MainSidebarViewController.swift +++ b/Tusker/Screens/Main/MainSidebarViewController.swift @@ -163,7 +163,7 @@ class MainSidebarViewController: UIViewController { snapshot.appendItems([ .tab(.compose) ], toSection: .compose) - if mastodonController.instanceFeatures.instanceType.isMastodon, + if mastodonController.instanceFeatures.trends, !Preferences.shared.hideDiscover { snapshot.insertSections([.discover], afterSection: .compose) } @@ -188,7 +188,7 @@ class MainSidebarViewController: UIViewController { } private func ownInstanceLoaded(_ instance: Instance) { - if mastodonController.instanceFeatures.instanceType.isMastodon { + if mastodonController.instanceFeatures.trends { var snapshot = self.dataSource.snapshot() if !snapshot.sectionIdentifiers.contains(.discover) { snapshot.appendSections([.discover])