mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-01-31 09:35:13 +01:00
feature: blockDomain
This commit is contained in:
parent
211e2f25d5
commit
ccdc48add1
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20D75" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Application" representedClassName=".Application" syncable="YES">
|
||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
@ -24,6 +24,18 @@
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="mediaAttachments" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="DomainBlock" representedClassName=".DomainBlock" syncable="YES">
|
||||
<attribute name="blockedDomain" attributeType="String"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="userID"/>
|
||||
<constraint value="domain"/>
|
||||
<constraint value="blockedDomain"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Emoji" representedClassName=".Emoji" syncable="YES">
|
||||
<attribute name="category" optional="YES" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
@ -252,6 +264,7 @@
|
||||
<elements>
|
||||
<element name="Application" positionX="0" positionY="0" width="128" height="104"/>
|
||||
<element name="Attachment" positionX="0" positionY="0" width="128" height="254"/>
|
||||
<element name="DomainBlock" positionX="45" positionY="162" width="128" height="74"/>
|
||||
<element name="Emoji" positionX="0" positionY="0" width="128" height="149"/>
|
||||
<element name="History" positionX="0" positionY="0" width="128" height="119"/>
|
||||
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
|
||||
@ -269,4 +282,4 @@
|
||||
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="149"/>
|
||||
<element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
|
||||
</elements>
|
||||
</model>
|
||||
</model>
|
73
CoreDataStack/Entity/DomainBlock.swift
Normal file
73
CoreDataStack/Entity/DomainBlock.swift
Normal file
@ -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)
|
||||
])
|
||||
}
|
||||
}
|
@ -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)
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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 = "<group>"; };
|
||||
2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewController+Avatar.swift"; sourceTree = "<group>"; };
|
||||
2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDomainService.swift; sourceTree = "<group>"; };
|
||||
2D9DB968263A833E007C1D71 /* DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainBlock.swift; sourceTree = "<group>"; };
|
||||
2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+DomainBlock.swift"; sourceTree = "<group>"; };
|
||||
2DA504682601ADE7008F4E6C /* SawToothView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SawToothView.swift; sourceTree = "<group>"; };
|
||||
2DA6054625F716A2006356F9 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = "<group>"; };
|
||||
2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -1091,6 +1097,7 @@
|
||||
5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */,
|
||||
DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */,
|
||||
DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */,
|
||||
2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */,
|
||||
);
|
||||
path = Service;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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] {
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
|
129
Mastodon/Service/APIService/APIService+DomainBlock.swift
Normal file
129
Mastodon/Service/APIService/APIService+DomainBlock.swift
Normal file
@ -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<Mastodon.Response.Content<[String]>, 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<Mastodon.Response.Content<[String]>, 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<Mastodon.Response.Content<String>, Error> {
|
||||
let authorization = authorizationBox.userAuthorization
|
||||
|
||||
return Mastodon.API.DomainBlock.blockDomain(
|
||||
domain: authorizationBox.domain,
|
||||
blockDomain: domain,
|
||||
session: session,
|
||||
authorization: authorization
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<String>, 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<String> in
|
||||
switch result {
|
||||
case .success:
|
||||
return response
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func unblockDomain(
|
||||
domain: String,
|
||||
authorizationBox: AuthenticationService.MastodonAuthenticationBox
|
||||
) -> AnyPublisher<Mastodon.Response.Content<String>, Error> {
|
||||
let authorization = authorizationBox.userAuthorization
|
||||
|
||||
return Mastodon.API.DomainBlock.unblockDomain(
|
||||
domain: authorizationBox.domain,
|
||||
blockDomain: domain,
|
||||
session: session,
|
||||
authorization: authorization
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<String>, 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<String> in
|
||||
switch result {
|
||||
case .success:
|
||||
return response
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
22
Mastodon/Service/BlockDomainService.swift
Normal file
22
Mastodon/Service/BlockDomainService.swift
Normal file
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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<Mastodon.Response.Content<[String]>, 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<Mastodon.Response.Content<String>, 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<Mastodon.Response.Content<String>, 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user