Allow posting local-only from Glitch instances

See #130
This commit is contained in:
Shadowfacts 2022-01-24 22:49:18 -05:00
parent 02461ad46c
commit 41a31c23b7
11 changed files with 79 additions and 70 deletions

View File

@ -336,7 +336,7 @@ public class Client {
pollOptions: [String]? = nil, pollOptions: [String]? = nil,
pollExpiresIn: Int? = nil, pollExpiresIn: Int? = nil,
pollMultiple: Bool? = nil, pollMultiple: Bool? = nil,
localOnly: Bool? = nil) -> Request<Status> { localOnly: Bool? = nil /* hometown only, not glitch */) -> Request<Status> {
return Request<Status>(method: .post, path: "/api/v1/statuses", body: ParametersBody([ return Request<Status>(method: .post, path: "/api/v1/statuses", body: ParametersBody([
"status" => text, "status" => text,
"content_type" => contentType.mimeType, "content_type" => contentType.mimeType,

View File

@ -1,24 +0,0 @@
//
// InstanceType.swift
// Pachyderm
//
// Created by Shadowfacts on 9/11/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import Foundation
public enum InstanceType {
case mastodon, pleroma
}
public extension Instance {
var instanceType: InstanceType {
let lowercased = version.lowercased()
if lowercased.contains("pleroma") {
return .pleroma
} else {
return .mastodon
}
}
}

View File

@ -176,7 +176,6 @@
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626321360D2300C9CBA2 /* AvatarStyle.swift */; }; D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626321360D2300C9CBA2 /* AvatarStyle.swift */; };
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; }; D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */; }; D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */; };
D667383C23299340000A2373 /* InstanceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667383B23299340000A2373 /* InstanceType.swift */; };
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; }; D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; };
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */; }; D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */; };
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */; }; D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */; };
@ -589,7 +588,6 @@
D663626321360D2300C9CBA2 /* AvatarStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarStyle.swift; sourceTree = "<group>"; }; D663626321360D2300C9CBA2 /* AvatarStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarStyle.swift; sourceTree = "<group>"; };
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Preferences.swift"; sourceTree = "<group>"; }; D663626B21361C6700C9CBA2 /* Account+Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Preferences.swift"; sourceTree = "<group>"; };
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Visibility+Helpers.swift"; sourceTree = "<group>"; }; D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Visibility+Helpers.swift"; sourceTree = "<group>"; };
D667383B23299340000A2373 /* InstanceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceType.swift; sourceTree = "<group>"; };
D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = "<group>"; }; D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = "<group>"; };
D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimelineStatusTableViewCell.xib; sourceTree = "<group>"; }; D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimelineStatusTableViewCell.xib; sourceTree = "<group>"; };
D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Delegates.swift"; sourceTree = "<group>"; }; D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Delegates.swift"; sourceTree = "<group>"; };
@ -1374,7 +1372,6 @@
D6E6F26221603F8B006A8599 /* CharacterCounter.swift */, D6E6F26221603F8B006A8599 /* CharacterCounter.swift */,
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */, D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */,
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */, D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */,
D667383B23299340000A2373 /* InstanceType.swift */,
D61AC1D2232E928600C54D2D /* InstanceSelector.swift */, D61AC1D2232E928600C54D2D /* InstanceSelector.swift */,
); );
path = Utilities; path = Utilities;
@ -2007,7 +2004,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D61099E5214561AB00432DC2 /* Application.swift in Sources */, D61099E5214561AB00432DC2 /* Application.swift in Sources */,
D667383C23299340000A2373 /* InstanceType.swift in Sources */,
D62E9983279C69D400C26176 /* WellKnown.swift in Sources */, D62E9983279C69D400C26176 /* WellKnown.swift in Sources */,
D61099FF21456A4C00432DC2 /* Status.swift in Sources */, D61099FF21456A4C00432DC2 /* Status.swift in Sources */,
D61099E32144C38900432DC2 /* Emoji.swift in Sources */, D61099E32144C38900432DC2 /* Emoji.swift in Sources */,

View File

@ -10,11 +10,54 @@ import Foundation
import Pachyderm import Pachyderm
struct InstanceFeatures { struct InstanceFeatures {
private(set) var instanceType = InstanceType.mastodon
private(set) var maxStatusChars = 500 private(set) var maxStatusChars = 500
private(set) var localOnlyPosts = false
var localOnlyPosts: Bool {
instanceType == .hometown || instanceType == .glitch
}
var mastodonAttachmentRestrictions: Bool {
instanceType.isMastodon
}
var pollsAndAttachments: Bool {
instanceType == .pleroma
}
var boostToOriginalAudience: Bool {
instanceType == .pleroma
}
mutating func update(instance: Instance, nodeInfo: NodeInfo?) { mutating func update(instance: Instance, nodeInfo: NodeInfo?) {
if instance.version.contains("glitch") {
instanceType = .glitch
} else if nodeInfo?.software.name == "hometown" {
instanceType = .hometown
} else if instance.version.contains("pleroma") {
instanceType = .pleroma
} else {
instanceType = .mastodon
}
maxStatusChars = instance.maxStatusCharacters ?? 500 maxStatusChars = instance.maxStatusCharacters ?? 500
localOnlyPosts = nodeInfo?.software.name == "hometown" }
}
extension InstanceFeatures {
enum InstanceType: Equatable {
case mastodon // vanilla
case pleroma
case hometown
case glitch
var isMastodon: Bool {
switch self {
case .mastodon, .hometown, .glitch:
return true
default:
return false
}
}
} }
} }

View File

@ -32,12 +32,6 @@ class Draft: Codable, ObservableObject {
poll?.hasContent == true poll?.hasContent == true
} }
var textForPosting: String {
// 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.replacingOccurrences(of: "\u{fffc}", with: "")
}
init(accountID: String) { init(accountID: String) {
self.id = UUID() self.id = UUID()
self.lastModified = Date() self.lastModified = Date()
@ -92,6 +86,19 @@ class Draft: Codable, ObservableObject {
try container.encode(initialText, forKey: .initialText) 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 { extension Draft: Equatable {

View File

@ -87,23 +87,17 @@ struct ComposeAttachmentsList: View {
} }
private var canAddAttachment: Bool { private var canAddAttachment: Bool {
switch mastodonController.instance?.instanceType { if mastodonController.instanceFeatures.mastodonAttachmentRestrictions {
case nil:
return false
case .pleroma:
return true
case .mastodon:
return draft.attachments.count < 4 && draft.attachments.allSatisfy { $0.data.type == .image } && draft.poll == nil return draft.attachments.count < 4 && draft.attachments.allSatisfy { $0.data.type == .image } && draft.poll == nil
} else {
return true
} }
} }
private var canAddPoll: Bool { private var canAddPoll: Bool {
switch mastodonController.instance?.instanceType { if mastodonController.instanceFeatures.pollsAndAttachments {
case nil:
return false
case .pleroma:
return true return true
case .mastodon: } else {
return draft.attachments.isEmpty return draft.attachments.isEmpty
} }
} }

View File

@ -234,13 +234,12 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
override func canPaste(_ itemProviders: [NSItemProvider]) -> Bool { override func canPaste(_ itemProviders: [NSItemProvider]) -> Bool {
guard itemProviders.allSatisfy({ $0.canLoadObject(ofClass: CompositionAttachment.self) }) else { return false } guard itemProviders.allSatisfy({ $0.canLoadObject(ofClass: CompositionAttachment.self) }) else { return false }
switch mastodonController.instance.instanceType { if mastodonController.instanceFeatures.mastodonAttachmentRestrictions {
case .pleroma:
return true
case .mastodon:
guard draft.attachments.allSatisfy({ $0.data.type == .image }) else { return false } guard draft.attachments.allSatisfy({ $0.data.type == .image }) else { return false }
// todo: if providers are videos, this technically allows invalid video/image combinations // todo: if providers are videos, this technically allows invalid video/image combinations
return itemProviders.count + draft.attachments.count <= 4 return itemProviders.count + draft.attachments.count <= 4
} else {
return true
} }
} }
@ -321,16 +320,15 @@ extension ComposeHostingController: ComposeUIStateDelegate {
extension ComposeHostingController: AssetPickerViewControllerDelegate { extension ComposeHostingController: AssetPickerViewControllerDelegate {
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachmentData.AttachmentType) -> Bool { func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachmentData.AttachmentType) -> Bool {
switch mastodonController.instance.instanceType { if mastodonController.instanceFeatures.mastodonAttachmentRestrictions {
case .pleroma:
return true
case .mastodon:
if (type == .video && draft.attachments.count > 0) || if (type == .video && draft.attachments.count > 0) ||
draft.attachments.contains(where: { $0.data.type == .video }) || draft.attachments.contains(where: { $0.data.type == .video }) ||
assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) { assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) {
return false return false
} }
return draft.attachments.count + assetPicker.currentCollectionSelectedAssets.count < 4 return draft.attachments.count + assetPicker.currentCollectionSelectedAssets.count < 4
} else {
return true
} }
} }

View File

@ -209,7 +209,7 @@ struct ComposeView: View {
self.isPosting = false self.isPosting = false
case let .success(uploadedAttachments): case let .success(uploadedAttachments):
let request = Client.createStatus(text: draft.textForPosting, let request = Client.createStatus(text: draft.textForPosting(on: mastodonController.instanceFeatures),
contentType: Preferences.shared.statusContentType, contentType: Preferences.shared.statusContentType,
inReplyTo: draft.inReplyToID, inReplyTo: draft.inReplyToID,
media: uploadedAttachments, media: uploadedAttachments,
@ -220,7 +220,7 @@ struct ComposeView: View {
pollOptions: draft.poll?.options.map(\.text), pollOptions: draft.poll?.options.map(\.text),
pollExpiresIn: draft.poll == nil ? nil : Int(draft.poll!.duration), pollExpiresIn: draft.poll == nil ? nil : Int(draft.poll!.duration),
pollMultiple: draft.poll?.multiple, pollMultiple: draft.poll?.multiple,
localOnly: mastodonController.instanceFeatures.localOnlyPosts ? draft.localOnly : nil) localOnly: mastodonController.instanceFeatures.instanceType == .hometown ? draft.localOnly : nil)
self.mastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
switch response { switch response {
case let .failure(error): case let .failure(error):

View File

@ -138,7 +138,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Section.allCases.filter { $0 != .discover }) snapshot.appendSections(Section.allCases.filter { $0 != .discover })
snapshot.appendItems([.bookmarks], toSection: .bookmarks) snapshot.appendItems([.bookmarks], toSection: .bookmarks)
if case .mastodon = mastodonController.instance?.instanceType { if mastodonController.instanceFeatures.instanceType.isMastodon {
snapshot.insertSections([.discover], afterSection: .bookmarks) snapshot.insertSections([.discover], afterSection: .bookmarks)
snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover) snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover)
} }
@ -154,7 +154,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
private func ownInstanceLoaded(_ instance: Instance) { private func ownInstanceLoaded(_ instance: Instance) {
var snapshot = self.dataSource.snapshot() var snapshot = self.dataSource.snapshot()
if case .mastodon = instance.instanceType { if mastodonController.instanceFeatures.instanceType.isMastodon {
snapshot.insertSections([.discover], afterSection: .bookmarks) snapshot.insertSections([.discover], afterSection: .bookmarks)
snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover) snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover)
} }

View File

@ -145,7 +145,7 @@ class MainSidebarViewController: UIViewController {
snapshot.appendItems([ snapshot.appendItems([
.tab(.compose) .tab(.compose)
], toSection: .compose) ], toSection: .compose)
if case .mastodon = mastodonController.instance?.instanceType { if mastodonController.instanceFeatures.instanceType.isMastodon {
snapshot.insertSections([.discover], afterSection: .compose) snapshot.insertSections([.discover], afterSection: .compose)
snapshot.appendItems([ snapshot.appendItems([
.trendingTags, .trendingTags,
@ -161,7 +161,7 @@ class MainSidebarViewController: UIViewController {
private func ownInstanceLoaded(_ instance: Instance) { private func ownInstanceLoaded(_ instance: Instance) {
var snapshot = self.dataSource.snapshot() var snapshot = self.dataSource.snapshot()
if case .mastodon = mastodonController.instance?.instanceType { if mastodonController.instanceFeatures.instanceType.isMastodon {
snapshot.insertSections([.discover], afterSection: .compose) snapshot.insertSections([.discover], afterSection: .compose)
snapshot.appendItems([ snapshot.appendItems([
.trendingTags, .trendingTags,

View File

@ -166,16 +166,11 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
} }
let reblogDisabled: Bool let reblogDisabled: Bool
switch mastodonController.instance?.instanceType { if mastodonController.instanceFeatures.boostToOriginalAudience {
case nil:
// todo: this handle a race condition in instance public timelines
// a somewhat better solution would be waiting to load the timeline until after the instance is loaded
reblogDisabled = true
case .mastodon:
reblogDisabled = status.visibility == .private || status.visibility == .direct
case .pleroma:
// Pleroma allows 'Boost to original audience' for your own private posts // Pleroma allows 'Boost to original audience' for your own private posts
reblogDisabled = status.visibility == .direct || (status.visibility == .private && status.account.id != mastodonController.account.id) reblogDisabled = status.visibility == .direct || (status.visibility == .private && status.account.id != mastodonController.account.id)
} else {
reblogDisabled = status.visibility == .private || status.visibility == .direct
} }
reblogButton.isEnabled = !reblogDisabled && mastodonController.loggedIn reblogButton.isEnabled = !reblogDisabled && mastodonController.loggedIn