diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
index 0d0170282..1738e3310 100644
--- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
+++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
@@ -1,5 +1,5 @@
-
+
@@ -24,6 +24,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -252,6 +264,7 @@
+
@@ -269,4 +282,4 @@
-
+
\ No newline at end of file
diff --git a/CoreDataStack/Entity/DomainBlock.swift b/CoreDataStack/Entity/DomainBlock.swift
new file mode 100644
index 000000000..71fa7d461
--- /dev/null
+++ b/CoreDataStack/Entity/DomainBlock.swift
@@ -0,0 +1,73 @@
+//
+// DomainBlock.swift
+// CoreDataStack
+//
+// Created by sxiaojian on 2021/4/29.
+//
+
+import CoreData
+import Foundation
+
+public final class DomainBlock: NSManagedObject {
+ @NSManaged public private(set) var blockedDomain: String
+ @NSManaged public private(set) var createAt: Date
+
+ @NSManaged public private(set) var domain: String
+ @NSManaged public private(set) var userID: String
+
+ override public func awakeFromInsert() {
+ super.awakeFromInsert()
+ setPrimitiveValue(Date(), forKey: #keyPath(DomainBlock.createAt))
+ }
+}
+
+extension DomainBlock {
+ @discardableResult
+ public static func insert(
+ into context: NSManagedObjectContext,
+ blockedDomain: String,
+ domain: String,
+ userID: String
+ ) -> DomainBlock {
+ let domainBlock: DomainBlock = context.insertObject()
+ domainBlock.domain = domain
+ domainBlock.blockedDomain = blockedDomain
+ domainBlock.userID = userID
+ return domainBlock
+ }
+}
+
+extension DomainBlock: Managed {
+ public static var defaultSortDescriptors: [NSSortDescriptor] {
+ [NSSortDescriptor(keyPath: \DomainBlock.createAt, ascending: false)]
+ }
+}
+
+extension DomainBlock {
+ static func predicate(domain: String) -> NSPredicate {
+ NSPredicate(format: "%K == %@", #keyPath(DomainBlock.domain), domain)
+ }
+
+ static func predicate(userID: String) -> NSPredicate {
+ NSPredicate(format: "%K == %@", #keyPath(DomainBlock.userID), userID)
+ }
+
+ static func predicate(blockedDomain: String) -> NSPredicate {
+ NSPredicate(format: "%K == %@", #keyPath(DomainBlock.blockedDomain), blockedDomain)
+ }
+
+ public static func predicate(domain: String, userID: String) -> NSPredicate {
+ NSCompoundPredicate(andPredicateWithSubpredicates: [
+ DomainBlock.predicate(domain: domain),
+ DomainBlock.predicate(userID: userID)
+ ])
+ }
+
+ public static func predicate(domain: String, userID: String, blockedDomain: String) -> NSPredicate {
+ NSCompoundPredicate(andPredicateWithSubpredicates: [
+ DomainBlock.predicate(domain: domain),
+ DomainBlock.predicate(userID: userID),
+ DomainBlock.predicate(blockedDomain:blockedDomain)
+ ])
+ }
+}
diff --git a/CoreDataStack/Entity/MastodonUser.swift b/CoreDataStack/Entity/MastodonUser.swift
index 714b6d0f6..c094bf4f6 100644
--- a/CoreDataStack/Entity/MastodonUser.swift
+++ b/CoreDataStack/Entity/MastodonUser.swift
@@ -333,7 +333,7 @@ extension MastodonUser: Managed {
extension MastodonUser {
- static func predicate(domain: String) -> NSPredicate {
+ public static func predicate(domain: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.domain), domain)
}
@@ -369,5 +369,4 @@ extension MastodonUser {
MastodonUser.predicate(username: username)
])
}
-
}
diff --git a/CoreDataStack/Entity/Status.swift b/CoreDataStack/Entity/Status.swift
index 1bb71a1db..73c704046 100644
--- a/CoreDataStack/Entity/Status.swift
+++ b/CoreDataStack/Entity/Status.swift
@@ -310,7 +310,7 @@ extension Status: Managed {
extension Status {
- static func predicate(domain: String) -> NSPredicate {
+ public static func predicate(domain: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Status.domain), domain)
}
diff --git a/Localization/app.json b/Localization/app.json
index 35cdda165..80a9e2dd6 100644
--- a/Localization/app.json
+++ b/Localization/app.json
@@ -27,6 +27,10 @@
"title": "Sign out",
"message": "Are you sure you want to sign out?",
"confirm": "Sign Out"
+ },
+ "block_domain": {
+ "message": "Are you really, really sure you want to block the entire %s ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+ "block_entire_domain": "Block entire domain"
}
},
"controls": {
@@ -56,7 +60,8 @@
"find_people": "Find people to follow",
"manually_search": "Manually search instead",
"skip": "Skip",
- "report_user": "Report %s"
+ "report_user": "Report %s",
+ "block_domain": "Block %s"
},
"status": {
"user_reblogged": "%s reblogged",
diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj
index 99ca696f1..65a7447ee 100644
--- a/Mastodon.xcodeproj/project.pbxproj
+++ b/Mastodon.xcodeproj/project.pbxproj
@@ -117,6 +117,9 @@
2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
2D939AC825EE14620076FA61 /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = 2D939AC725EE14620076FA61 /* CropViewController */; };
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */; };
+ 2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */; };
+ 2D9DB969263A833E007C1D71 /* DomainBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB968263A833E007C1D71 /* DomainBlock.swift */; };
+ 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */; };
2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA504682601ADE7008F4E6C /* SawToothView.swift */; };
2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6054625F716A2006356F9 /* PlaybackState.swift */; };
2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */; };
@@ -543,6 +546,9 @@
2D927F1325C7EDD9004F19B8 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = ""; };
2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; };
2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewController+Avatar.swift"; sourceTree = ""; };
+ 2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDomainService.swift; sourceTree = ""; };
+ 2D9DB968263A833E007C1D71 /* DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainBlock.swift; sourceTree = ""; };
+ 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+DomainBlock.swift"; sourceTree = ""; };
2DA504682601ADE7008F4E6C /* SawToothView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SawToothView.swift; sourceTree = ""; };
2DA6054625F716A2006356F9 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = ""; };
2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerViewModel.swift; sourceTree = ""; };
@@ -1091,6 +1097,7 @@
5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */,
DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */,
DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */,
+ 2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */,
);
path = Service;
sourceTree = "";
@@ -1494,6 +1501,7 @@
DB98336A25C9420100AD9700 /* APIService+App.swift */,
DB98337025C9443200AD9700 /* APIService+Authentication.swift */,
DB98339B25C96DE600AD9700 /* APIService+Account.swift */,
+ 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */,
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */,
DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */,
DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */,
@@ -1683,6 +1691,7 @@
isa = PBXGroup;
children = (
DB89BA2625C110B4008580ED /* Status.swift */,
+ 2D9DB968263A833E007C1D71 /* DomainBlock.swift */,
2D6125462625436B00299647 /* Notification.swift */,
2D0B7A1C261D839600B44727 /* SearchHistory.swift */,
DB8AF52425C131D1002E6C99 /* MastodonUser.swift */,
@@ -2405,6 +2414,7 @@
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
+ 2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */,
DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */,
DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */,
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
@@ -2535,6 +2545,7 @@
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */,
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
+ 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */,
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */,
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
@@ -2752,6 +2763,7 @@
2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */,
2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */,
DB89BA1D25C1107F008580ED /* URL.swift in Sources */,
+ 2D9DB969263A833E007C1D71 /* DomainBlock.swift in Sources */,
2D0B7A1D261D839600B44727 /* SearchHistory.swift in Sources */,
2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */,
5B90C46E26259B2C0002E742 /* Subscription.swift in Sources */,
diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift
index 0e26e1479..839682c4a 100644
--- a/Mastodon/Diffiable/Section/StatusSection.swift
+++ b/Mastodon/Diffiable/Section/StatusSection.swift
@@ -781,7 +781,7 @@ extension StatusSection {
}
let author = status.authorForUserProvider
let canReport = authenticationBox.userID != author.id
-
+ let canBlockDomain = authenticationBox.domain != author.domain
let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
@@ -791,6 +791,7 @@ extension StatusSection {
isMuting: isMuting,
isBlocking: isBlocking,
canReport: canReport,
+ canBlockDomain: canBlockDomain,
provider: userProvider,
cell: cell,
indexPath: indexPath,
diff --git a/Mastodon/Extension/CoreDataStack/MastodonUser.swift b/Mastodon/Extension/CoreDataStack/MastodonUser.swift
index bb99b15d4..96f95e5e2 100644
--- a/Mastodon/Extension/CoreDataStack/MastodonUser.swift
+++ b/Mastodon/Extension/CoreDataStack/MastodonUser.swift
@@ -50,6 +50,15 @@ extension MastodonUser {
}
}
+ var domainFromAcct: String {
+ if !acct.contains("@") {
+ return domain
+ } else {
+ let domain = acct.split(separator: "@").last
+ return String(domain!)
+ }
+ }
+
}
extension MastodonUser {
diff --git a/Mastodon/Extension/CoreDataStack/Status.swift b/Mastodon/Extension/CoreDataStack/Status.swift
index 015671cb5..5eec9f4aa 100644
--- a/Mastodon/Extension/CoreDataStack/Status.swift
+++ b/Mastodon/Extension/CoreDataStack/Status.swift
@@ -74,7 +74,12 @@ extension Status {
extension Status {
var statusURL: URL {
- return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")!
+ if let urlString = self.url,
+ let url = URL(string: urlString) {
+ return url
+ } else {
+ return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")!
+ }
}
var activityItems: [Any] {
diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift
index c0d1cac6f..18de7e8e5 100644
--- a/Mastodon/Generated/Strings.swift
+++ b/Mastodon/Generated/Strings.swift
@@ -13,6 +13,14 @@ internal enum L10n {
internal enum Common {
internal enum Alerts {
+ internal enum BlockDomain {
+ /// Block entire domain
+ internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain")
+ /// Are you really, really sure you want to block the entire %@ ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.
+ internal static func message(_ p1: Any) -> String {
+ return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Message", String(describing: p1))
+ }
+ }
internal enum Common {
/// Please try again.
internal static let pleaseTryAgain = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgain")
@@ -60,6 +68,10 @@ internal enum L10n {
internal static let add = L10n.tr("Localizable", "Common.Controls.Actions.Add")
/// Back
internal static let back = L10n.tr("Localizable", "Common.Controls.Actions.Back")
+ /// Block %@
+ internal static func blockDomain(_ p1: Any) -> String {
+ return L10n.tr("Localizable", "Common.Controls.Actions.BlockDomain", String(describing: p1))
+ }
/// Cancel
internal static let cancel = L10n.tr("Localizable", "Common.Controls.Actions.Cancel")
/// Confirm
diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift
index cbde09034..a4356e71a 100644
--- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift
+++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift
@@ -159,6 +159,7 @@ extension UserProviderFacade {
isMuting: Bool,
isBlocking: Bool,
canReport: Bool,
+ canBlockDomain: Bool,
provider: UserProvider,
cell: UITableViewCell?,
indexPath: IndexPath?,
@@ -247,6 +248,23 @@ extension UserProviderFacade {
children.append(reportAction)
}
+ if canBlockDomain {
+ let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domain), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
+ guard let provider = provider else { return }
+ let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domain), preferredStyle: .alert)
+ let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
+
+ }
+ alertController.addAction(cancelAction)
+ let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in
+ BlockDomainService(context: provider.context).blockDomain(domain: mastodonUser.domain)
+ }
+ alertController.addAction(blockDomainAction)
+ provider.present(alertController, animated: true, completion: nil)
+ }
+ children.append(blockDomainAction)
+ }
+
if let shareUser = shareUser {
let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings
index a2f653d81..983de7327 100644
--- a/Mastodon/Resources/en.lproj/Localizable.strings
+++ b/Mastodon/Resources/en.lproj/Localizable.strings
@@ -1,3 +1,5 @@
+"Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain";
+"Common.Alerts.BlockDomain.Message" = "Are you really, really sure you want to block the entire %@ ? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.";
"Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
"Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content.";
@@ -14,6 +16,7 @@ Please check your internet connection.";
"Common.Alerts.VoteFailure.Title" = "Vote Failure";
"Common.Controls.Actions.Add" = "Add";
"Common.Controls.Actions.Back" = "Back";
+"Common.Controls.Actions.BlockDomain" = "Block %@";
"Common.Controls.Actions.Cancel" = "Cancel";
"Common.Controls.Actions.Confirm" = "Confirm";
"Common.Controls.Actions.Continue" = "Continue";
diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift
index 7a8742f18..21571faa7 100644
--- a/Mastodon/Scene/Profile/ProfileViewController.swift
+++ b/Mastodon/Scene/Profile/ProfileViewController.swift
@@ -374,14 +374,17 @@ extension ProfileViewController {
self.moreMenuBarButtonItem.menu = nil
return
}
+ guard let currentDomain = self.viewModel.domain.value else { return }
let isMuting = relationshipActionOptionSet.contains(.muting)
let isBlocking = relationshipActionOptionSet.contains(.blocking)
let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value
+ let canBlockDomain = mastodonUser.domain != currentDomain
self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(
for: mastodonUser,
isMuting: isMuting,
isBlocking: isBlocking,
canReport: true,
+ canBlockDomain: canBlockDomain,
provider: self,
cell: nil,
indexPath: nil,
diff --git a/Mastodon/Service/APIService/APIService+DomainBlock.swift b/Mastodon/Service/APIService/APIService+DomainBlock.swift
new file mode 100644
index 000000000..bb54d2983
--- /dev/null
+++ b/Mastodon/Service/APIService/APIService+DomainBlock.swift
@@ -0,0 +1,129 @@
+//
+// APIService+DomainBlock.swift
+// Mastodon
+//
+// Created by sxiaojian on 2021/4/29.
+//
+
+import Foundation
+import Combine
+import CoreData
+import CoreDataStack
+import CommonOSLog
+import DateToolsSwift
+import MastodonSDK
+
+extension APIService {
+
+ func getDomainblocks(
+ domain: String,
+ limit: Int = onceRequestDomainBlocksMaxCount,
+ authorizationBox: AuthenticationService.MastodonAuthenticationBox
+ ) -> AnyPublisher, Error> {
+ let authorization = authorizationBox.userAuthorization
+
+ let query = Mastodon.API.DomainBlock.Query(
+ maxID: nil, sinceID: nil, limit: limit
+ )
+ return Mastodon.API.DomainBlock.getDomainblocks(
+ domain: domain,
+ session: session,
+ authorization: authorization,
+ query: query
+ )
+ .flatMap { response -> AnyPublisher, Error> in
+ return self.backgroundManagedObjectContext.performChanges {
+ response.value.forEach { domain in
+ // use constrain to avoid repeated save
+ let _ = DomainBlock.insert(
+ into: self.backgroundManagedObjectContext,
+ blockedDomain: domain,
+ domain: authorizationBox.domain,
+ userID: authorizationBox.userID
+ )
+ }
+ }
+ .setFailureType(to: Error.self)
+ .tryMap { result -> Mastodon.Response.Content<[String]> in
+ switch result {
+ case .success:
+ return response
+ case .failure(let error):
+ throw error
+ }
+ }
+ .eraseToAnyPublisher()
+ }
+ .eraseToAnyPublisher()
+ }
+
+ func blockDomain(
+ domain: String,
+ authorizationBox: AuthenticationService.MastodonAuthenticationBox
+ ) -> AnyPublisher, Error> {
+ let authorization = authorizationBox.userAuthorization
+
+ return Mastodon.API.DomainBlock.blockDomain(
+ domain: authorizationBox.domain,
+ blockDomain: domain,
+ session: session,
+ authorization: authorization
+ )
+ .flatMap { response -> AnyPublisher, Error> in
+ return self.backgroundManagedObjectContext.performChanges {
+ let _ = DomainBlock.insert(
+ into: self.backgroundManagedObjectContext,
+ blockedDomain: domain,
+ domain: authorizationBox.domain,
+ userID: authorizationBox.userID
+ )
+ }
+ .setFailureType(to: Error.self)
+ .tryMap { result -> Mastodon.Response.Content in
+ switch result {
+ case .success:
+ return response
+ case .failure(let error):
+ throw error
+ }
+ }
+ .eraseToAnyPublisher()
+ }
+ .eraseToAnyPublisher()
+ }
+
+ func unblockDomain(
+ domain: String,
+ authorizationBox: AuthenticationService.MastodonAuthenticationBox
+ ) -> AnyPublisher, Error> {
+ let authorization = authorizationBox.userAuthorization
+
+ return Mastodon.API.DomainBlock.unblockDomain(
+ domain: authorizationBox.domain,
+ blockDomain: domain,
+ session: session,
+ authorization: authorization
+ )
+ .flatMap { response -> AnyPublisher, Error> in
+ return self.backgroundManagedObjectContext.performChanges {
+// let _ = DomainBlock.insert(
+// into: self.backgroundManagedObjectContext,
+// blockedDomain: domain,
+// domain: authorizationBox.domain,
+// userID: authorizationBox.userID
+// )
+ }
+ .setFailureType(to: Error.self)
+ .tryMap { result -> Mastodon.Response.Content in
+ switch result {
+ case .success:
+ return response
+ case .failure(let error):
+ throw error
+ }
+ }
+ .eraseToAnyPublisher()
+ }
+ .eraseToAnyPublisher()
+ }
+}
diff --git a/Mastodon/Service/APIService/APIService.swift b/Mastodon/Service/APIService/APIService.swift
index 11e6f5cac..b38e2e059 100644
--- a/Mastodon/Service/APIService/APIService.swift
+++ b/Mastodon/Service/APIService/APIService.swift
@@ -46,6 +46,7 @@ final class APIService {
extension APIService {
public static let onceRequestStatusMaxCount = 100
public static let onceRequestUserMaxCount = 100
+ public static let onceRequestDomainBlocksMaxCount = 100
}
extension APIService {
diff --git a/Mastodon/Service/BlockDomainService.swift b/Mastodon/Service/BlockDomainService.swift
new file mode 100644
index 000000000..91d226989
--- /dev/null
+++ b/Mastodon/Service/BlockDomainService.swift
@@ -0,0 +1,22 @@
+//
+// BlockDomainService.swift
+// Mastodon
+//
+// Created by sxiaojian on 2021/4/29.
+//
+
+import CoreData
+import CoreDataStack
+import Foundation
+
+final class BlockDomainService {
+ let context: AppContext
+
+ init(context: AppContext) {
+ self.context = context
+ }
+
+ func blockDomain(domain: String) {
+
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift
new file mode 100644
index 000000000..f0ee51d90
--- /dev/null
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+DomainBlock.swift
@@ -0,0 +1,136 @@
+//
+// File.swift
+//
+//
+// Created by sxiaojian on 2021/4/29.
+//
+
+import Foundation
+import Combine
+
+extension Mastodon.API.DomainBlock {
+ static func domainBlockEndpointURL(domain: String) -> URL {
+ Mastodon.API.endpointURL(domain: domain).appendingPathComponent("domain_blocks")
+ }
+
+ /// Fetch domain blocks
+ ///
+ /// - Since: 1.4.0
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/)
+ /// - Parameters:
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - session: `URLSession`
+ /// - authorization: User token
+ /// - Returns: `AnyPublisher` contains `String` nested in the response
+ public static func getDomainblocks(
+ domain: String,
+ session: URLSession,
+ authorization: Mastodon.API.OAuth.Authorization,
+ query: Mastodon.API.DomainBlock.Query
+ ) -> AnyPublisher, Error> {
+ let url = domainBlockEndpointURL(domain: domain)
+ let request = Mastodon.API.get(url: url, query: query, authorization: authorization)
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value = try Mastodon.API.decode(type: [String].self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+
+ /// Block a domain
+ ///
+ /// - Since: 1.4.0
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/)
+ /// - Parameters:
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - session: `URLSession`
+ /// - authorization: User token
+ /// - Returns: `AnyPublisher` contains `String` nested in the response
+ public static func blockDomain(
+ domain: String,
+ blockDomain:String,
+ session: URLSession,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) -> AnyPublisher, Error> {
+ let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain)
+ let request = Mastodon.API.post(
+ url: domainBlockEndpointURL(domain: domain),
+ query: query,
+ authorization: authorization
+ )
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value = try Mastodon.API.decode(type: String.self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+
+ /// Unblock a domain
+ ///
+ /// - Since: 1.4.0
+ /// # Reference
+ /// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/)
+ /// - Parameters:
+ /// - domain: Mastodon instance domain. e.g. "example.com"
+ /// - session: `URLSession`
+ /// - authorization: User token
+ /// - Returns: `AnyPublisher` contains `String` nested in the response
+ public static func unblockDomain(
+ domain: String,
+ blockDomain:String,
+ session: URLSession,
+ authorization: Mastodon.API.OAuth.Authorization
+ ) -> AnyPublisher, Error> {
+ let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain)
+ let request = Mastodon.API.delete(
+ url: domainBlockEndpointURL(domain: domain),
+ query: query,
+ authorization: authorization
+ )
+ return session.dataTaskPublisher(for: request)
+ .tryMap { data, response in
+ let value = try Mastodon.API.decode(type: String.self, from: data, response: response)
+ return Mastodon.Response.Content(value: value, response: response)
+ }
+ .eraseToAnyPublisher()
+ }
+}
+
+extension Mastodon.API.DomainBlock {
+ public struct Query: GetQuery {
+ public let maxID: Mastodon.Entity.Status.ID?
+ public let sinceID: Mastodon.Entity.Status.ID?
+ public let limit: Int?
+
+ public init(
+ maxID: Mastodon.Entity.Status.ID?,
+ sinceID: Mastodon.Entity.Status.ID?,
+ limit: Int?
+ ) {
+ self.maxID = maxID
+ self.sinceID = sinceID
+ self.limit = limit
+ }
+
+ var queryItems: [URLQueryItem]? {
+ var items: [URLQueryItem] = []
+ maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) }
+ sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) }
+ limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
+ guard !items.isEmpty else { return nil }
+ return items
+ }
+ }
+
+ public struct BlockQuery: Codable, PostQuery {
+ public let domain: String
+
+ public init(domain: String) {
+ self.domain = domain
+ }
+ }
+}
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
index cfaa1736d..79408f833 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift
@@ -117,6 +117,7 @@ extension Mastodon.API {
public enum Notifications { }
public enum Subscriptions { }
public enum Reports { }
+ public enum DomainBlock { }
}
extension Mastodon.API.V2 {
@@ -141,6 +142,14 @@ extension Mastodon.API {
) -> URLRequest {
return buildRequest(url: url, method: .POST, query: query, authorization: authorization)
}
+
+ static func delete(
+ url: URL,
+ query: PostQuery?,
+ authorization: OAuth.Authorization?
+ ) -> URLRequest {
+ return buildRequest(url: url, method: .DELETE, query: query, authorization: authorization)
+ }
static func patch(
url: URL,