Compare commits
6 Commits
822c2e0fa2
...
85765928b4
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 85765928b4 | |
Shadowfacts | f13874ee01 | |
Shadowfacts | bac272a2db | |
Shadowfacts | 48bd957276 | |
Shadowfacts | d4d42e7856 | |
Shadowfacts | 671a8e0cb3 |
|
@ -17,7 +17,7 @@ public class InstanceFeatures: ObservableObject {
|
||||||
private let _featuresUpdated = PassthroughSubject<Void, Never>()
|
private let _featuresUpdated = PassthroughSubject<Void, Never>()
|
||||||
public var featuresUpdated: some Publisher<Void, Never> { _featuresUpdated }
|
public var featuresUpdated: some Publisher<Void, Never> { _featuresUpdated }
|
||||||
|
|
||||||
@Published private var instanceType: InstanceType = .mastodon(.vanilla, nil)
|
@Published @_spi(InstanceType) public private(set) var instanceType: InstanceType = .mastodon(.vanilla, nil)
|
||||||
@Published public private(set) var maxStatusChars = 500
|
@Published public private(set) var maxStatusChars = 500
|
||||||
@Published public private(set) var charsReservedPerURL = 23
|
@Published public private(set) var charsReservedPerURL = 23
|
||||||
@Published public private(set) var maxPollOptionChars: Int?
|
@Published public private(set) var maxPollOptionChars: Int?
|
||||||
|
@ -120,8 +120,11 @@ public class InstanceFeatures: ObservableObject {
|
||||||
|
|
||||||
public func update(instance: Instance, nodeInfo: NodeInfo?) {
|
public func update(instance: Instance, nodeInfo: NodeInfo?) {
|
||||||
let ver = instance.version.lowercased()
|
let ver = instance.version.lowercased()
|
||||||
|
// check glitch first b/c it still reports "mastodon" as the software in nodeinfo
|
||||||
if ver.contains("glitch") {
|
if ver.contains("glitch") {
|
||||||
instanceType = .mastodon(.glitch, Version(string: ver))
|
instanceType = .mastodon(.glitch, Version(string: ver))
|
||||||
|
} else if nodeInfo?.software.name == "mastodon" {
|
||||||
|
instanceType = .mastodon(.vanilla, Version(string: ver))
|
||||||
} else if nodeInfo?.software.name == "hometown" {
|
} else if nodeInfo?.software.name == "hometown" {
|
||||||
var mastoVersion: Version?
|
var mastoVersion: Version?
|
||||||
var hometownVersion: Version?
|
var hometownVersion: Version?
|
||||||
|
@ -157,6 +160,10 @@ public class InstanceFeatures: ObservableObject {
|
||||||
instanceType = .pleroma(.akkoma(akkomaVersion))
|
instanceType = .pleroma(.akkoma(akkomaVersion))
|
||||||
} else if ver.contains("pixelfed") {
|
} else if ver.contains("pixelfed") {
|
||||||
instanceType = .pixelfed
|
instanceType = .pixelfed
|
||||||
|
} else if nodeInfo?.software.name == "gotosocial" {
|
||||||
|
instanceType = .gotosocial
|
||||||
|
} else if ver.contains("calckey") {
|
||||||
|
instanceType = .calckey(nodeInfo?.software.version)
|
||||||
} else {
|
} else {
|
||||||
instanceType = .mastodon(.vanilla, Version(string: ver))
|
instanceType = .mastodon(.vanilla, Version(string: ver))
|
||||||
}
|
}
|
||||||
|
@ -190,10 +197,12 @@ public class InstanceFeatures: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InstanceFeatures {
|
extension InstanceFeatures {
|
||||||
enum InstanceType {
|
@_spi(InstanceType) public enum InstanceType {
|
||||||
case mastodon(MastodonType, Version?)
|
case mastodon(MastodonType, Version?)
|
||||||
case pleroma(PleromaType)
|
case pleroma(PleromaType)
|
||||||
case pixelfed
|
case pixelfed
|
||||||
|
case gotosocial
|
||||||
|
case calckey(String?)
|
||||||
|
|
||||||
var isMastodon: Bool {
|
var isMastodon: Bool {
|
||||||
if case .mastodon(_, _) = self {
|
if case .mastodon(_, _) = self {
|
||||||
|
@ -230,7 +239,7 @@ extension InstanceFeatures {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MastodonType {
|
@_spi(InstanceType) public enum MastodonType {
|
||||||
case vanilla
|
case vanilla
|
||||||
case hometown(Version?)
|
case hometown(Version?)
|
||||||
case glitch
|
case glitch
|
||||||
|
@ -249,7 +258,7 @@ extension InstanceFeatures {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PleromaType {
|
@_spi(InstanceType) public enum PleromaType {
|
||||||
case vanilla(Version?)
|
case vanilla(Version?)
|
||||||
case akkoma(Version?)
|
case akkoma(Version?)
|
||||||
|
|
||||||
|
@ -267,7 +276,7 @@ extension InstanceFeatures {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InstanceFeatures {
|
extension InstanceFeatures {
|
||||||
struct Version: Equatable, Comparable {
|
@_spi(InstanceType) public struct Version: Equatable, Comparable, CustomStringConvertible {
|
||||||
private static let regex = try! NSRegularExpression(pattern: "^(\\d+)\\.(\\d+)\\.(\\d+).*$")
|
private static let regex = try! NSRegularExpression(pattern: "^(\\d+)\\.(\\d+)\\.(\\d+).*$")
|
||||||
|
|
||||||
let major: Int
|
let major: Int
|
||||||
|
@ -298,11 +307,15 @@ extension InstanceFeatures {
|
||||||
self.patch = patch
|
self.patch = patch
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: Version, rhs: Version) -> Bool {
|
public var description: String {
|
||||||
|
"\(major).\(minor).\(patch)"
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Version, rhs: Version) -> Bool {
|
||||||
return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch == rhs.patch
|
return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch == rhs.patch
|
||||||
}
|
}
|
||||||
|
|
||||||
static func < (lhs: InstanceFeatures.Version, rhs: InstanceFeatures.Version) -> Bool {
|
public static func < (lhs: InstanceFeatures.Version, rhs: InstanceFeatures.Version) -> Bool {
|
||||||
if lhs.major < rhs.major {
|
if lhs.major < rhs.major {
|
||||||
return true
|
return true
|
||||||
} else if lhs.major > rhs.major {
|
} else if lhs.major > rhs.major {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import WebURL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The base Mastodon API client.
|
The base Mastodon API client.
|
||||||
|
@ -186,9 +187,9 @@ public class Client {
|
||||||
|
|
||||||
case let .success(wellKnown, _):
|
case let .success(wellKnown, _):
|
||||||
if let url = wellKnown.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }),
|
if let url = wellKnown.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }),
|
||||||
let components = URLComponents(string: url.href),
|
let href = WebURL(url.href),
|
||||||
components.host == self.baseURL.host {
|
href.host == WebURL(self.baseURL)?.host {
|
||||||
let nodeInfo = Request<NodeInfo>(method: .get, path: Endpoint(stringLiteral: components.path))
|
let nodeInfo = Request<NodeInfo>(method: .get, path: Endpoint(stringLiteral: href.path))
|
||||||
self.run(nodeInfo, completion: completion)
|
self.run(nodeInfo, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -507,6 +508,8 @@ extension Client {
|
||||||
// todo: support more status codes
|
// todo: support more status codes
|
||||||
case .unexpectedStatus(413):
|
case .unexpectedStatus(413):
|
||||||
return "HTTP 413: Payload Too Large"
|
return "HTTP 413: Payload Too Large"
|
||||||
|
case .unexpectedStatus(429):
|
||||||
|
return "HTTP 429: Rate Limit Exceeded"
|
||||||
case .unexpectedStatus(let code):
|
case .unexpectedStatus(let code):
|
||||||
return "HTTP Code \(code)"
|
return "HTTP Code \(code)"
|
||||||
case .invalidRequest:
|
case .invalidRequest:
|
||||||
|
|
|
@ -64,7 +64,7 @@ public final class Status: StatusProtocol, Decodable, Sendable {
|
||||||
self.reblog = try container.decodeIfPresent(Status.self, forKey: .reblog)
|
self.reblog = try container.decodeIfPresent(Status.self, forKey: .reblog)
|
||||||
self.content = try container.decode(String.self, forKey: .content)
|
self.content = try container.decode(String.self, forKey: .content)
|
||||||
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
|
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
|
||||||
self.emojis = try container.decode([Emoji].self, forKey: .emojis)
|
self.emojis = try container.decodeIfPresent([Emoji].self, forKey: .emojis) ?? []
|
||||||
self.reblogsCount = try container.decode(Int.self, forKey: .reblogsCount)
|
self.reblogsCount = try container.decode(Int.self, forKey: .reblogsCount)
|
||||||
self.favouritesCount = try container.decode(Int.self, forKey: .favouritesCount)
|
self.favouritesCount = try container.decode(Int.self, forKey: .favouritesCount)
|
||||||
self.reblogged = try container.decodeIfPresent(Bool.self, forKey: .reblogged)
|
self.reblogged = try container.decodeIfPresent(Bool.self, forKey: .reblogged)
|
||||||
|
|
|
@ -114,10 +114,8 @@ private func createBookmarkAction(status: StatusMO, container: StatusSwipeAction
|
||||||
let (status, _) = try await container.mastodonController.run(request)
|
let (status, _) = try await container.mastodonController.run(request)
|
||||||
container.mastodonController.persistentContainer.addOrUpdate(status: status)
|
container.mastodonController.persistentContainer.addOrUpdate(status: status)
|
||||||
} catch {
|
} catch {
|
||||||
if let toastable = container.toastableViewController {
|
let config = ToastConfiguration(from: error, with: "Error \(bookmarked ? "Unb" : "B")ookmarking", in: container.navigationDelegate, retryAction: nil)
|
||||||
let config = ToastConfiguration(from: error, with: "Error \(bookmarked ? "Unb" : "B")ookmarking", in: toastable, retryAction: nil)
|
container.navigationDelegate.showToast(configuration: config, animated: true)
|
||||||
toastable.showToast(configuration: config, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,12 +241,12 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
} catch let error as Client.Error {
|
} catch let error as Client.Error {
|
||||||
acceptButton.isEnabled = true
|
acceptButton.isEnabled = true
|
||||||
rejectButton.isEnabled = true
|
rejectButton.isEnabled = true
|
||||||
if let toastable = delegate?.toastableViewController {
|
if let delegate = delegate {
|
||||||
let config = ToastConfiguration(from: error, with: "Rejecting Follow", in: toastable) { [weak self] toast in
|
let config = ToastConfiguration(from: error, with: "Rejecting Follow", in: delegate) { [weak self] toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
self?.rejectButtonPressed()
|
self?.rejectButtonPressed()
|
||||||
}
|
}
|
||||||
toastable.showToast(configuration: config, animated: true)
|
delegate.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,12 +268,12 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
acceptButton.isEnabled = true
|
acceptButton.isEnabled = true
|
||||||
rejectButton.isEnabled = true
|
rejectButton.isEnabled = true
|
||||||
|
|
||||||
if let toastable = delegate?.toastableViewController {
|
if let delegate = delegate {
|
||||||
let config = ToastConfiguration(from: error, with: "Accepting Follow", in: toastable) { [weak self] toast in
|
let config = ToastConfiguration(from: error, with: "Accepting Follow", in: delegate) { [weak self] toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
self?.acceptButtonPressed()
|
self?.acceptButtonPressed()
|
||||||
}
|
}
|
||||||
toastable.showToast(configuration: config, animated: true)
|
delegate.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ class PollFinishedNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
contentLabel.text = try! doc.text()
|
contentLabel.text = try! doc.text()
|
||||||
|
|
||||||
pollView.mastodonController = mastodonController
|
pollView.mastodonController = mastodonController
|
||||||
pollView.toastableViewController = delegate
|
pollView.delegate = delegate
|
||||||
pollView.updateUI(status: status, poll: poll)
|
pollView.updateUI(status: status, poll: poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,3 +193,7 @@ extension StatusActionAccountListViewController: ToastableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension StatusActionAccountListViewController: TuskerNavigationDelegate {
|
||||||
|
nonisolated var apiController: MastodonController! { mastodonController }
|
||||||
|
}
|
||||||
|
|
|
@ -141,13 +141,15 @@ class EnhancedNavigationViewController: UINavigationController {
|
||||||
if self.interactivePushTransition.interactive {
|
if self.interactivePushTransition.interactive {
|
||||||
// when an interactive push gesture is cancelled, make sure to adding the VC that was being pushed back onto the popped stack so it doesn't disappear
|
// when an interactive push gesture is cancelled, make sure to adding the VC that was being pushed back onto the popped stack so it doesn't disappear
|
||||||
self.poppedViewControllers.insert(self.interactivePushTransition.pushingViewController!, at: 0)
|
self.poppedViewControllers.insert(self.interactivePushTransition.pushingViewController!, at: 0)
|
||||||
} else {
|
} else if self.interactivePopGestureRecognizer?.state == .ended {
|
||||||
// when an interactive pop gesture is cancelled (i.e. the user lifts their finger before it triggers),
|
// when an interactive pop gesture is cancelled (i.e. the user lifts their finger before it triggers),
|
||||||
// the popViewController(animated:) method has already been called so the VC has already been added to the popped stack
|
// the popViewController(animated:) method has already been called so the VC has already been added to the popped stack
|
||||||
// so we make sure to remove it, otherwise there could be duplicate VCs on the navigation stasck
|
// so we make sure to remove it, otherwise there could be duplicate VCs on the navigation stasck
|
||||||
|
if !self.poppedViewControllers.isEmpty {
|
||||||
self.poppedViewControllers.remove(at: 0)
|
self.poppedViewControllers.remove(at: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -409,10 +409,10 @@ extension MenuActionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleError(_ error: Client.Error, title: String) {
|
private func handleError(_ error: Client.Error, title: String) {
|
||||||
if let toastable = self.toastableViewController {
|
if let navigationDelegate {
|
||||||
let config = ToastConfiguration(from: error, with: title, in: toastable, retryAction: nil)
|
let config = ToastConfiguration(from: error, with: title, in: navigationDelegate, retryAction: nil)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
toastable.showToast(configuration: config, animated: true)
|
navigationDelegate.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol TimelineLikeCollectionViewController: UIViewController, TimelineLikeControllerDelegate, ToastableViewController {
|
protocol TimelineLikeCollectionViewController: UIViewController, TimelineLikeControllerDelegate, TuskerNavigationDelegate {
|
||||||
associatedtype Section: TimelineLikeCollectionViewSection
|
associatedtype Section: TimelineLikeCollectionViewSection
|
||||||
associatedtype Item: TimelineLikeCollectionViewItem where Item.TimelineItem == Self.TimelineItem
|
associatedtype Item: TimelineLikeCollectionViewItem where Item.TimelineItem == Self.TimelineItem
|
||||||
associatedtype Error: TimelineLikeCollectionViewError
|
associatedtype Error: TimelineLikeCollectionViewError
|
||||||
|
|
|
@ -13,7 +13,7 @@ import ComposeUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol TuskerNavigationDelegate: UIViewController, ToastableViewController {
|
protocol TuskerNavigationDelegate: UIViewController, ToastableViewController {
|
||||||
var apiController: MastodonController! { get }
|
nonisolated var apiController: MastodonController! { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TuskerNavigationDelegate {
|
extension TuskerNavigationDelegate {
|
||||||
|
|
|
@ -21,7 +21,7 @@ class StatusPollView: UIView {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
weak var toastableViewController: ToastableViewController?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
|
|
||||||
private var statusID: String!
|
private var statusID: String!
|
||||||
private(set) var poll: Poll?
|
private(set) var poll: Poll?
|
||||||
|
@ -158,9 +158,9 @@ class StatusPollView: UIView {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.updateUI(status: self.mastodonController.persistentContainer.status(for: self.statusID)!, poll: self.poll)
|
self.updateUI(status: self.mastodonController.persistentContainer.status(for: self.statusID)!, poll: self.poll)
|
||||||
|
|
||||||
if let toastable = self.toastableViewController {
|
if let delegate = self.delegate {
|
||||||
let config = ToastConfiguration(from: error, with: "Error Voting", in: toastable, retryAction: nil)
|
let config = ToastConfiguration(from: error, with: "Error Voting", in: delegate, retryAction: nil)
|
||||||
toastable.showToast(configuration: config, animated: true)
|
delegate.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -392,12 +392,12 @@ class ProfileHeaderView: UIView {
|
||||||
} catch {
|
} catch {
|
||||||
followButton.isEnabled = true
|
followButton.isEnabled = true
|
||||||
followButton.configuration!.showsActivityIndicator = false
|
followButton.configuration!.showsActivityIndicator = false
|
||||||
if let toastable = delegate?.toastableViewController {
|
if let delegate = self.delegate {
|
||||||
let config = ToastConfiguration(from: error, with: "Error \(action)", in: toastable) { toast in
|
let config = ToastConfiguration(from: error, with: "Error \(action)", in: delegate) { toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
self.followPressed()
|
self.followPressed()
|
||||||
}
|
}
|
||||||
toastable.showToast(configuration: config, animated: true)
|
delegate.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ extension StatusCollectionViewCell {
|
||||||
|
|
||||||
contentContainer.pollView.isHidden = status.poll == nil
|
contentContainer.pollView.isHidden = status.poll == nil
|
||||||
contentContainer.pollView.mastodonController = mastodonController
|
contentContainer.pollView.mastodonController = mastodonController
|
||||||
contentContainer.pollView.toastableViewController = delegate?.toastableViewController
|
contentContainer.pollView.delegate = delegate
|
||||||
contentContainer.pollView.updateUI(status: status, poll: status.poll)
|
contentContainer.pollView.updateUI(status: status, poll: status.poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Sentry
|
import Sentry
|
||||||
import OSLog
|
import OSLog
|
||||||
|
@_spi(InstanceType) import InstanceFeatures
|
||||||
|
|
||||||
struct ToastConfiguration {
|
struct ToastConfiguration {
|
||||||
var systemImageName: String?
|
var systemImageName: String?
|
||||||
|
@ -39,7 +40,7 @@ struct ToastConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ToastConfiguration {
|
extension ToastConfiguration {
|
||||||
init(from error: Error, with title: String, in viewController: UIViewController, retryAction: ((ToastView) -> Void)?) {
|
init(from error: Error, with title: String, in viewController: TuskerNavigationDelegate, retryAction: ((ToastView) -> Void)?) {
|
||||||
self.init(title: title)
|
self.init(title: title)
|
||||||
// localizedDescription is statically dispatched, so we need to call it after the downcast
|
// localizedDescription is statically dispatched, so we need to call it after the downcast
|
||||||
if let error = error as? Pachyderm.Client.Error {
|
if let error = error as? Pachyderm.Client.Error {
|
||||||
|
@ -59,7 +60,7 @@ extension ToastConfiguration {
|
||||||
viewController.present(reporter, animated: true)
|
viewController.present(reporter, animated: true)
|
||||||
}
|
}
|
||||||
// TODO: this is a bizarre place to do this, but code path covers basically all errors
|
// TODO: this is a bizarre place to do this, but code path covers basically all errors
|
||||||
captureError(error, title: title)
|
captureError(error, in: viewController.apiController, title: title)
|
||||||
} else {
|
} else {
|
||||||
self.subtitle = error.localizedDescription
|
self.subtitle = error.localizedDescription
|
||||||
self.systemImageName = "exclamationmark.triangle"
|
self.systemImageName = "exclamationmark.triangle"
|
||||||
|
@ -70,7 +71,7 @@ extension ToastConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from error: Error, with title: String, in viewController: UIViewController, retryAction: @escaping @MainActor (ToastView) async -> Void) {
|
init(from error: Error, with title: String, in viewController: TuskerNavigationDelegate, retryAction: @escaping @MainActor (ToastView) async -> Void) {
|
||||||
self.init(from: error, with: title, in: viewController) { toast in
|
self.init(from: error, with: title, in: viewController) { toast in
|
||||||
Task {
|
Task {
|
||||||
await retryAction(toast)
|
await retryAction(toast)
|
||||||
|
@ -84,6 +85,8 @@ fileprivate extension Pachyderm.Client.Error {
|
||||||
switch type {
|
switch type {
|
||||||
case .networkError(_):
|
case .networkError(_):
|
||||||
return "wifi.exclamationmark"
|
return "wifi.exclamationmark"
|
||||||
|
case .unexpectedStatus(429):
|
||||||
|
return "clock.badge.exclamationmark"
|
||||||
default:
|
default:
|
||||||
return "exclamationmark.triangle"
|
return "exclamationmark.triangle"
|
||||||
}
|
}
|
||||||
|
@ -92,7 +95,7 @@ fileprivate extension Pachyderm.Client.Error {
|
||||||
|
|
||||||
private let toastErrorLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ToastError")
|
private let toastErrorLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ToastError")
|
||||||
|
|
||||||
private func captureError(_ error: Client.Error, title: String) {
|
private func captureError(_ error: Client.Error, in mastodonController: MastodonController, title: String) {
|
||||||
let event = Event(error: error)
|
let event = Event(error: error)
|
||||||
event.message = SentryMessage(formatted: "\(title): \(error)")
|
event.message = SentryMessage(formatted: "\(title): \(error)")
|
||||||
event.tags = [
|
event.tags = [
|
||||||
|
@ -125,6 +128,37 @@ private func captureError(_ error: Client.Error, title: String) {
|
||||||
code == "401" || code == "403" || code == "404" || code == "502" {
|
code == "401" || code == "403" || code == "404" || code == "502" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
switch mastodonController.instanceFeatures.instanceType {
|
||||||
|
case .mastodon(let mastodonType, let mastodonVersion):
|
||||||
|
event.tags!["instance_type"] = "mastodon"
|
||||||
|
event.tags!["mastodon_version"] = mastodonVersion?.description ?? "unknown"
|
||||||
|
switch mastodonType {
|
||||||
|
case .vanilla:
|
||||||
|
break
|
||||||
|
case .hometown(_):
|
||||||
|
event.tags!["mastodon_type"] = "hometown"
|
||||||
|
case .glitch:
|
||||||
|
event.tags!["mastodon_type"] = "glitch"
|
||||||
|
}
|
||||||
|
case .pleroma(let pleromaType):
|
||||||
|
event.tags!["instance_type"] = "pleroma"
|
||||||
|
switch pleromaType {
|
||||||
|
case .vanilla(let version):
|
||||||
|
event.tags!["pleroma_version"] = version?.description ?? "unknown"
|
||||||
|
case .akkoma(let version):
|
||||||
|
event.tags!["pleroma_type"] = "akkoma"
|
||||||
|
event.tags!["pleroma_version"] = version?.description ?? "unknown"
|
||||||
|
}
|
||||||
|
case .pixelfed:
|
||||||
|
event.tags!["instance_type"] = "pixelfed"
|
||||||
|
case .gotosocial:
|
||||||
|
event.tags!["instance_type"] = "gotosocial"
|
||||||
|
case .calckey(let calckeyVersion):
|
||||||
|
event.tags!["instance_type"] = "calckey"
|
||||||
|
if let calckeyVersion {
|
||||||
|
event.tags!["calckey_version"] = calckeyVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
SentrySDK.capture(event: event)
|
SentrySDK.capture(event: event)
|
||||||
|
|
||||||
toastErrorLogger.error("\(title, privacy: .public): \(error), \(event.tags!.debugDescription, privacy: .public)")
|
toastErrorLogger.error("\(title, privacy: .public): \(error), \(event.tags!.debugDescription, privacy: .public)")
|
||||||
|
|
Loading…
Reference in New Issue