import Foundation // Response types for profile-related XRPC queries. struct GetFollowersResponse: Codable, Sendable { var totalCount: Int? var items: [FollowerItem]? var cursor: String? } struct GetFollowingResponse: Codable, Sendable { var totalCount: Int? var items: [FollowingItem]? var cursor: String? } struct GetKnownFollowersResponse: Codable, Sendable { var items: [FollowerItem]? } struct GetGalleryFavoritesResponse: Codable, Sendable { var totalCount: Int? var items: [FavoriteItem]? var cursor: String? } struct GetSuggestedFollowsResponse: Codable, Sendable { var items: [SuggestedItem]? } struct SearchProfilesResponse: Codable, Sendable { var items: [ProfileSearchResult]? var cursor: String? } struct FollowerItem: Codable, Sendable, Identifiable { let did: String var handle: String? var displayName: String? var description: String? var avatar: String? var viewer: ActorViewerState? var id: String { did } } struct FollowingItem: Codable, Sendable, Identifiable { let did: String var handle: String? var displayName: String? var description: String? var avatar: String? var viewer: ActorViewerState? var id: String { did } } struct FavoriteItem: Codable, Sendable, Identifiable { let did: String var handle: String? var displayName: String? var description: String? var avatar: String? var viewer: ActorViewerState? var id: String { did } } struct SuggestedItem: Codable, Sendable, Identifiable { let did: String var handle: String? var displayName: String? var description: String? var avatar: String? var followersCount: Int? var id: String { did } } struct ProfileSearchResult: Codable, Sendable, Identifiable { let did: String var handle: String? var displayName: String? var description: String? var avatar: String? var id: String { did } } // MARK: - Convenience Extensions extension XRPCClient { func getActorProfile(actor: String, auth: AuthContext? = nil) async throws -> GrainProfileDetailed { try await query("social.grain.actor.getProfile", params: ["actor": actor], auth: auth, as: GrainProfileDetailed.self) } func getFollowers(actor: String, limit: Int = 50, cursor: String? = nil, auth: AuthContext? = nil) async throws -> GetFollowersResponse { var params = ["actor": actor, "limit": String(limit)] if let cursor { params["cursor"] = cursor } return try await query("social.grain.graph.getFollowers", params: params, auth: auth, as: GetFollowersResponse.self) } func getFollowing(actor: String, limit: Int = 50, cursor: String? = nil, auth: AuthContext? = nil) async throws -> GetFollowingResponse { var params = ["actor": actor, "limit": String(limit)] if let cursor { params["cursor"] = cursor } return try await query("social.grain.graph.getFollowing", params: params, auth: auth, as: GetFollowingResponse.self) } /// The accounts the signed-in viewer follows that follow `actor`. func getKnownFollowers(actor: String, auth: AuthContext? = nil) async throws -> GetKnownFollowersResponse { try await query("social.grain.graph.getKnownFollowers", params: ["actor": actor], auth: auth, as: GetKnownFollowersResponse.self) } func getGalleryFavorites(gallery: String, limit: Int = 50, cursor: String? = nil, auth: AuthContext? = nil) async throws -> GetGalleryFavoritesResponse { var params = ["gallery": gallery, "limit": String(limit)] if let cursor { params["cursor"] = cursor } return try await query("social.grain.gallery.getFavorites", params: params, auth: auth, as: GetGalleryFavoritesResponse.self) } func getSuggestedFollows(actor: String, limit: Int = 10, auth: AuthContext? = nil) async throws -> GetSuggestedFollowsResponse { try await query("social.grain.actor.getSuggestedFollows", params: ["actor": actor, "limit": String(limit)], auth: auth, as: GetSuggestedFollowsResponse.self) } func searchProfiles(query queryString: String, limit: Int = 30, cursor: String? = nil, auth: AuthContext? = nil) async throws -> SearchProfilesResponse { var params = ["q": queryString, "limit": String(limit)] if let cursor { params["cursor"] = cursor } return try await query("social.grain.actor.searchActors", params: params, auth: auth, as: SearchProfilesResponse.self) } }