Merge pull request #686 from mastodon/mute_block_delete_data
This commit is contained in:
commit
097c99cc65
|
@ -16,10 +16,17 @@ extension DataSourceFacade {
|
|||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await dependency.context.apiService.toggleBlock(
|
||||
|
||||
let apiService = dependency.context.apiService
|
||||
let authBox = dependency.authContext.mastodonAuthenticationBox
|
||||
|
||||
_ = try await apiService.toggleBlock(
|
||||
user: user,
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
authenticationBox: authBox
|
||||
)
|
||||
|
||||
try await dependency.context.apiService.getBlocked(
|
||||
authenticationBox: authBox
|
||||
)
|
||||
} // end func
|
||||
}
|
||||
|
|
|
@ -86,6 +86,8 @@ extension HomeTimelineViewModel.LoadLatestState {
|
|||
await enter(state: Idle.self)
|
||||
viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(.finished)
|
||||
|
||||
viewModel.context.instanceService.updateMutesAndBlocks()
|
||||
|
||||
// stop refresher if no new statuses
|
||||
let statuses = response.value
|
||||
let newStatuses = statuses.filter { !latestStatusIDs.contains($0.id) }
|
||||
|
|
|
@ -112,6 +112,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
|
||||
// trigger authenticated user account update
|
||||
AppContext.shared.authenticationService.updateActiveUserAccountPublisher.send()
|
||||
|
||||
// update mutes and blocks and remove related data
|
||||
AppContext.shared.instanceService.updateMutesAndBlocks()
|
||||
|
||||
if let shortcutItem = savedShortCutItem {
|
||||
Task {
|
||||
|
|
|
@ -22,6 +22,45 @@ extension APIService {
|
|||
let isFollowing: Bool
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func getBlocked(
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||
try await _getBlocked(sinceID: nil, limit: nil, authenticationBox: authenticationBox)
|
||||
}
|
||||
|
||||
private func _getBlocked(
|
||||
sinceID: Mastodon.Entity.Status.ID?,
|
||||
limit: Int?,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let response = try await Mastodon.API.Account.blocks(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
sinceID: sinceID,
|
||||
limit: limit,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
let userIDs = response.value.map { $0.id }
|
||||
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
||||
|
||||
let fetchRequest = MastodonUser.fetchRequest()
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.includesPropertyValues = false
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser]
|
||||
|
||||
for user in users {
|
||||
user.deleteStatusAndNotificationFeeds(in: managedObjectContext)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
public func toggleBlock(
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
|
@ -110,3 +149,21 @@ extension APIService {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
func deleteStatusAndNotificationFeeds(in context: NSManagedObjectContext) {
|
||||
statuses.map {
|
||||
$0.feeds
|
||||
.union($0.reblogFrom.map { $0.feeds }.flatMap { $0 })
|
||||
.union($0.notifications.map { $0.feeds }.flatMap { $0 })
|
||||
}
|
||||
.flatMap { $0 }
|
||||
.forEach(context.delete)
|
||||
|
||||
notifications.map {
|
||||
$0.feeds
|
||||
}
|
||||
.flatMap { $0 }
|
||||
.forEach(context.delete)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,45 @@ extension APIService {
|
|||
let isMuting: Bool
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func getMutes(
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||
try await _getMutes(sinceID: nil, limit: nil, authenticationBox: authenticationBox)
|
||||
}
|
||||
|
||||
private func _getMutes(
|
||||
sinceID: Mastodon.Entity.Status.ID?,
|
||||
limit: Int?,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
let response = try await Mastodon.API.Account.mutes(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
sinceID: sinceID,
|
||||
limit: limit,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
let userIDs = response.value.map { $0.id }
|
||||
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
||||
|
||||
let fetchRequest = MastodonUser.fetchRequest()
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.includesPropertyValues = false
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser]
|
||||
|
||||
for user in users {
|
||||
user.deleteStatusAndNotificationFeeds(in: managedObjectContext)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
public func toggleMute(
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
|
@ -58,6 +97,7 @@ extension APIService {
|
|||
accountID: muteContext.targetUserID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
try await getMutes(authenticationBox: authenticationBox)
|
||||
result = .success(response)
|
||||
} else {
|
||||
let response = try await Mastodon.API.Account.mute(
|
||||
|
@ -66,6 +106,7 @@ extension APIService {
|
|||
accountID: muteContext.targetUserID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
try await getMutes(authenticationBox: authenticationBox)
|
||||
result = .success(response)
|
||||
}
|
||||
} catch {
|
||||
|
|
|
@ -101,3 +101,25 @@ extension InstanceService {
|
|||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
public extension InstanceService {
|
||||
func updateMutesAndBlocks() {
|
||||
Task {
|
||||
for authBox in authenticationService?.mastodonAuthenticationBoxes ?? [] {
|
||||
do {
|
||||
try await apiService?.getMutes(
|
||||
authenticationBox: authBox
|
||||
)
|
||||
|
||||
try await apiService?.getBlocked(
|
||||
authenticationBox: authBox
|
||||
)
|
||||
|
||||
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Instance] update mutes and blocks succeeded")
|
||||
} catch {
|
||||
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Instance] update mutes and blocks failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,6 +215,70 @@ extension Mastodon.API.Account {
|
|||
|
||||
}
|
||||
|
||||
public extension Mastodon.API.Account {
|
||||
|
||||
static func blocksEndpointURL(domain: String) -> URL {
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("blocks")
|
||||
}
|
||||
|
||||
/// Block
|
||||
///
|
||||
/// Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline).
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/4/1
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/blocks/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - authorization: User token.
|
||||
/// - Returns: `AnyPublisher` contains `Relationship` nested in the response
|
||||
static func blocks(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
sinceID: Mastodon.Entity.Status.ID?,
|
||||
limit: Int?,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: blocksEndpointURL(domain: domain),
|
||||
query: BlocksQuery(sinceID: sinceID, limit: limit),
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private struct BlocksQuery: GetQuery {
|
||||
private let sinceID: Mastodon.Entity.Status.ID?
|
||||
private let limit: Int?
|
||||
|
||||
public init(
|
||||
sinceID: Mastodon.Entity.Status.ID?,
|
||||
limit: Int?
|
||||
) {
|
||||
self.sinceID = sinceID
|
||||
self.limit = limit
|
||||
}
|
||||
|
||||
var queryItems: [URLQueryItem]? {
|
||||
var items: [URLQueryItem] = []
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.Account {
|
||||
|
||||
static func blockEndpointURL(domain: String, accountID: Mastodon.Entity.Account.ID) -> URL {
|
||||
|
@ -414,3 +478,70 @@ extension Mastodon.API.Account {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.Account {
|
||||
|
||||
static func mutesEndpointURL(
|
||||
domain: String
|
||||
) -> URL {
|
||||
return Mastodon.API.endpointURL(domain: domain)
|
||||
.appendingPathComponent("mutes")
|
||||
}
|
||||
|
||||
/// View all mutes
|
||||
///
|
||||
/// View your mutes. See also accounts/:id/{mute,unmute}.
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/4/1
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - accountID: id for account
|
||||
/// - authorization: User token.
|
||||
/// - Returns: `AnyPublisher` contains `Relationship` nested in the response
|
||||
public static func mutes(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
sinceID: Mastodon.Entity.Status.ID? = nil,
|
||||
limit: Int?,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: mutesEndpointURL(domain: domain),
|
||||
query: MutesQuery(sinceID: sinceID, limit: limit),
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
struct MutesQuery: GetQuery {
|
||||
private let sinceID: Mastodon.Entity.Status.ID?
|
||||
private let limit: Int?
|
||||
|
||||
public init(
|
||||
sinceID: Mastodon.Entity.Status.ID?,
|
||||
limit: Int?
|
||||
) {
|
||||
self.sinceID = sinceID
|
||||
self.limit = limit
|
||||
}
|
||||
|
||||
var queryItems: [URLQueryItem]? {
|
||||
var items: [URLQueryItem] = []
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ extension Mastodon.Response {
|
|||
public struct Link {
|
||||
public let maxID: Mastodon.Entity.Status.ID?
|
||||
public let minID: Mastodon.Entity.Status.ID?
|
||||
public let linkIDs: [String: Mastodon.Entity.Status.ID]
|
||||
public let offset: Int?
|
||||
|
||||
init(link: String) {
|
||||
|
@ -135,6 +136,33 @@ extension Mastodon.Response {
|
|||
let offset = link[range]
|
||||
return Int(offset)
|
||||
}()
|
||||
self.linkIDs = {
|
||||
var linkIDs = [String: Mastodon.Entity.Status.ID]()
|
||||
let links = link.components(separatedBy: ", ")
|
||||
for link in links {
|
||||
guard let regex = try? NSRegularExpression(pattern: "<(.*)>; *rel=\"(.*)\"") else { return [:] }
|
||||
let results = regex.matches(in: link, options: [], range: NSRange(link.startIndex..<link.endIndex, in: link))
|
||||
for match in results {
|
||||
guard
|
||||
let labelRange = Range(match.range(at: 2), in: link),
|
||||
let linkRange = Range(match.range(at: 1), in: link)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
linkIDs[String(link[labelRange])] = String(link[linkRange])
|
||||
}
|
||||
}
|
||||
return linkIDs
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Mastodon.Entity.Status.ID {
|
||||
static let linkPrev = "prev"
|
||||
static let linkNext = "next"
|
||||
|
||||
var sinceId: String? {
|
||||
components(separatedBy: "&since_id=").last
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue