mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-03 18:57:46 +01:00
feat: Implement blocks/mutes pagination using link header
This commit is contained in:
parent
9c86dfe166
commit
12cb8cf8d6
@ -25,18 +25,27 @@ extension APIService {
|
|||||||
@discardableResult
|
@discardableResult
|
||||||
public func getBlocked(
|
public func getBlocked(
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||||
|
try await _getBlocked(sinceID: nil, limit: 40, authenticationBox: authenticationBox)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _getBlocked(
|
||||||
|
sinceID: Mastodon.Entity.Status.ID?,
|
||||||
|
limit: Int,
|
||||||
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let managedObjectContext = backgroundManagedObjectContext
|
||||||
|
|
||||||
let response = try await Mastodon.API.Account.blocks(
|
let response = try await Mastodon.API.Account.blocks(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
|
sinceID: sinceID,
|
||||||
|
limit: limit,
|
||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let userIDs = response.value.map { $0.id }
|
let userIDs = response.value.map { $0.id }
|
||||||
let predicate = NSPredicate(format: "%K IN %@", #keyPath(MastodonUser.id), userIDs)
|
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
||||||
|
|
||||||
let fetchRequest = MastodonUser.fetchRequest()
|
let fetchRequest = MastodonUser.fetchRequest()
|
||||||
fetchRequest.predicate = predicate
|
fetchRequest.predicate = predicate
|
||||||
fetchRequest.includesPropertyValues = false
|
fetchRequest.includesPropertyValues = false
|
||||||
@ -49,7 +58,12 @@ extension APIService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
/// only try to paginate if retrieved userIDs count is larger than the set limit and if we get a prev linkId that's different than the currently used one
|
||||||
|
guard userIDs.count == limit, let prevSinceId = response.link?.linkIDs[.linkPrev]?.sinceId, sinceID != prevSinceId else {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await _getBlocked(sinceID: prevSinceId, limit: limit, authenticationBox: authenticationBox)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func toggleBlock(
|
public func toggleBlock(
|
||||||
|
@ -24,18 +24,27 @@ extension APIService {
|
|||||||
@discardableResult
|
@discardableResult
|
||||||
public func getMutes(
|
public func getMutes(
|
||||||
authenticationBox: MastodonAuthenticationBox
|
authenticationBox: MastodonAuthenticationBox
|
||||||
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||||
|
try await _getMutes(sinceID: nil, limit: 40, authenticationBox: authenticationBox)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _getMutes(
|
||||||
|
sinceID: Mastodon.Entity.Status.ID?,
|
||||||
|
limit: Int,
|
||||||
|
authenticationBox: MastodonAuthenticationBox
|
||||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||||
let managedObjectContext = backgroundManagedObjectContext
|
let managedObjectContext = backgroundManagedObjectContext
|
||||||
|
|
||||||
let response = try await Mastodon.API.Account.mutes(
|
let response = try await Mastodon.API.Account.mutes(
|
||||||
session: session,
|
session: session,
|
||||||
domain: authenticationBox.domain,
|
domain: authenticationBox.domain,
|
||||||
|
sinceID: sinceID,
|
||||||
|
limit: limit,
|
||||||
authorization: authenticationBox.userAuthorization
|
authorization: authenticationBox.userAuthorization
|
||||||
).singleOutput()
|
).singleOutput()
|
||||||
|
|
||||||
let userIDs = response.value.map { $0.id }
|
let userIDs = response.value.map { $0.id }
|
||||||
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
|
||||||
|
|
||||||
let fetchRequest = MastodonUser.fetchRequest()
|
let fetchRequest = MastodonUser.fetchRequest()
|
||||||
fetchRequest.predicate = predicate
|
fetchRequest.predicate = predicate
|
||||||
fetchRequest.includesPropertyValues = false
|
fetchRequest.includesPropertyValues = false
|
||||||
@ -48,7 +57,12 @@ extension APIService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
/// only try to paginate if retrieved userIDs count is larger than the set limit and if we get a prev linkId that's different than the currently used one
|
||||||
|
guard userIDs.count == limit, let prevSinceId = response.link?.linkIDs[.linkPrev]?.sinceId, sinceID != prevSinceId else {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await _getMutes(sinceID: prevSinceId, limit: limit, authenticationBox: authenticationBox)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func toggleMute(
|
public func toggleMute(
|
||||||
|
@ -239,11 +239,13 @@ public extension Mastodon.API.Account {
|
|||||||
static func blocks(
|
static func blocks(
|
||||||
session: URLSession,
|
session: URLSession,
|
||||||
domain: String,
|
domain: String,
|
||||||
|
sinceID: Mastodon.Entity.Status.ID? = nil,
|
||||||
|
limit: Int,
|
||||||
authorization: Mastodon.API.OAuth.Authorization
|
authorization: Mastodon.API.OAuth.Authorization
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||||
let request = Mastodon.API.get(
|
let request = Mastodon.API.get(
|
||||||
url: blocksEndpointURL(domain: domain),
|
url: blocksEndpointURL(domain: domain),
|
||||||
query: BlocksQuery(),
|
query: BlocksQuery(sinceID: sinceID, limit: limit),
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
)
|
)
|
||||||
return session.dataTaskPublisher(for: request)
|
return session.dataTaskPublisher(for: request)
|
||||||
@ -255,8 +257,23 @@ public extension Mastodon.API.Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct BlocksQuery: GetQuery {
|
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 queryItems: [URLQueryItem]? {
|
||||||
nil
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,11 +507,13 @@ extension Mastodon.API.Account {
|
|||||||
public static func mutes(
|
public static func mutes(
|
||||||
session: URLSession,
|
session: URLSession,
|
||||||
domain: String,
|
domain: String,
|
||||||
|
sinceID: Mastodon.Entity.Status.ID? = nil,
|
||||||
|
limit: Int?,
|
||||||
authorization: Mastodon.API.OAuth.Authorization
|
authorization: Mastodon.API.OAuth.Authorization
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||||
let request = Mastodon.API.get(
|
let request = Mastodon.API.get(
|
||||||
url: mutesEndpointURL(domain: domain),
|
url: mutesEndpointURL(domain: domain),
|
||||||
query: MutesQuery(),
|
query: MutesQuery(sinceID: sinceID, limit: limit),
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
)
|
)
|
||||||
return session.dataTaskPublisher(for: request)
|
return session.dataTaskPublisher(for: request)
|
||||||
@ -505,8 +524,23 @@ extension Mastodon.API.Account {
|
|||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
struct MutesQuery: GetQuery {
|
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 queryItems: [URLQueryItem]? {
|
||||||
nil
|
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 struct Link {
|
||||||
public let maxID: Mastodon.Entity.Status.ID?
|
public let maxID: Mastodon.Entity.Status.ID?
|
||||||
public let minID: Mastodon.Entity.Status.ID?
|
public let minID: Mastodon.Entity.Status.ID?
|
||||||
|
public let linkIDs: [String: Mastodon.Entity.Status.ID]
|
||||||
public let offset: Int?
|
public let offset: Int?
|
||||||
|
|
||||||
init(link: String) {
|
init(link: String) {
|
||||||
@ -135,6 +136,33 @@ extension Mastodon.Response {
|
|||||||
let offset = link[range]
|
let offset = link[range]
|
||||||
return Int(offset)
|
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…
x
Reference in New Issue
Block a user