diff --git a/Sources/ATProtoKit/Models/Lexicons/app.bsky/Actor/AppBskyActorDefs.swift b/Sources/ATProtoKit/Models/Lexicons/app.bsky/Actor/AppBskyActorDefs.swift index 113dd1ab7c..5f534dfed4 100644 --- a/Sources/ATProtoKit/Models/Lexicons/app.bsky/Actor/AppBskyActorDefs.swift +++ b/Sources/ATProtoKit/Models/Lexicons/app.bsky/Actor/AppBskyActorDefs.swift @@ -379,6 +379,12 @@ extension AppBskyLexicon.Actor { /// A URI which indicates the user is being followed by the requesting account. public let followedByURI: String? + /// An array of mutual followers. Optional. + /// + /// - Note: According to the AT Protocol specifications: "The subject's followers whom you + /// also follow." + public let knownFollowers: KnownFollowers + enum CodingKeys: String, CodingKey { case isMuted = "muted" case mutedByArray = "mutedByList" @@ -387,6 +393,33 @@ extension AppBskyLexicon.Actor { case blockingByArray = "blockingByList" case followingURI = "following" case followedByURI = "followedBy" + case knownFollowers + } + } + + /// A definition model for mutual followers. + /// + /// - Note: According to the AT Protocol specifications: "The subject's followers whom you + /// also follow." + /// + /// - SeeAlso: This is based on the [`app.bsky.actor.defs`][github] lexicon. + /// + /// [github]: https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/defs.json + public struct KnownFollowers: Codable { + + /// The number of mutual followers related to the parent structure's specifications. + public let count: Int + + /// An array of user accounts that follow the viewer. + public let followers: [ProfileViewBasicDefinition] + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.count, forKey: .count) + + // Truncate `displayName` to 5 items before encoding + try truncatedEncode(self.followers, withContainer: &container, forKey: .followers, upToLength: 5) } } diff --git a/Sources/ATProtoKit/Models/Lexicons/app.bsky/Graph/AppBskyGraphGetKnownFollowers.swift b/Sources/ATProtoKit/Models/Lexicons/app.bsky/Graph/AppBskyGraphGetKnownFollowers.swift new file mode 100644 index 0000000000..45b004b902 --- /dev/null +++ b/Sources/ATProtoKit/Models/Lexicons/app.bsky/Graph/AppBskyGraphGetKnownFollowers.swift @@ -0,0 +1,31 @@ +// +// AppBskyGraphGetKnownFollowers.swift +// +// +// Created by Christopher Jr Riley on 2024-06-12. +// + +import Foundation + +extension AppBskyLexicon.Graph { + + /// An output model for identifying mutual followers. + /// + /// - Note: According to the AT Protocol specifications: "Enumerates accounts which follow a + /// specified account (actor) and are followed by the viewer." + /// + /// - SeeAlso: This is based on the [`app.bsky.graph.getKnownFollowers`][github] lexicon. + /// + /// [github]: https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/graph/getKnownFollowers.json + public struct GetKnownFollowersOutput: Codable { + + /// The user account that mutually follows ``followers``. + public let subject: AppBskyLexicon.Actor.ProfileViewDefinition + + /// The number of the last successful message decoded. Optional. + public let cursor: String? + + /// An array of mutual followers. + public let followers: [AppBskyLexicon.Actor.ProfileViewDefinition] + } +} diff --git a/Sources/ATProtoKit/Networking/PlatformAPI/GetKnownFollowers.swift b/Sources/ATProtoKit/Networking/PlatformAPI/GetKnownFollowers.swift new file mode 100644 index 0000000000..79e5ecffac --- /dev/null +++ b/Sources/ATProtoKit/Networking/PlatformAPI/GetKnownFollowers.swift @@ -0,0 +1,77 @@ +// +// GetKnownFollowers.swift +// +// +// Created by Christopher Jr Riley on 2024-06-12. +// + +import Foundation + +extension ATProtoKit { + + /// Identifies mutual followers. + /// + /// - Note: According to the AT Protocol specifications: "Enumerates accounts which follow a + /// specified account (actor) and are followed by the viewer." + /// + /// - SeeAlso: This is based on the [`app.bsky.graph.getKnownFollowers`][github] lexicon. + /// + /// [github]: https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/graph/getKnownFollowers.json + /// + /// - Parameters: + /// - actor: The user account to check for mutual followers. + /// - limit: The number of items the list will hold. Optional. Defaults to `50`. + /// - cursor: The mark used to indicate the starting point for the next set of + /// result. Optional. + /// - Returns: A `Result`, containing either a + /// ``AppBskyLexicon/Graph/GetKnownFollowersOutput`` + /// if successful, or an `Error` if not. + public func getKnownFollowers( + from actor: String, + limit: Int? = 50, + cursor: String? = nil + ) async throws -> Result { + guard session != nil, + let accessToken = session?.accessToken else { + return .failure(ATRequestPrepareError.missingActiveSession) + } + + guard let sessionURL = session?.pdsURL, + let requestURL = URL(string: "\(sessionURL)/xrpc/app.bsky.graph.getKnownFollowers") else { + return .failure(ATRequestPrepareError.invalidRequestURL) + } + + var queryItems = [(String, String)]() + + if let limit { + let finalLimit = max(1, min(limit, 100)) + queryItems.append(("limit", "\(finalLimit)")) + } + + if let cursor { + queryItems.append(("cursor", cursor)) + } + + let queryURL: URL + + do { + queryURL = try APIClientService.setQueryItems( + for: requestURL, + with: queryItems + ) + + let request = APIClientService.createRequest(forRequest: queryURL, + andMethod: .get, + acceptValue: "application/json", + contentTypeValue: nil, + authorizationValue: "Bearer \(accessToken)") + let response = try await APIClientService.sendRequest(request, + decodeTo: AppBskyLexicon.Graph.GetKnownFollowersOutput.self) + + return .success(response) + + } catch { + return .failure(error) + } + } +}