From 84278ae5c50fd98cb59f2b5d9e10399fa59d294c Mon Sep 17 00:00:00 2001 From: Thomas Rademaker Date: Thu, 5 Feb 2026 14:06:38 -0500 Subject: [PATCH] support for feeds --- Sources/bskyKit/BskyAPI.swift | 9 +++++- Sources/bskyKit/BskyService.swift | 11 +++++++ Sources/bskyKit/Models/Preferences.swift | 38 +++++++++++++++++++----- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Sources/bskyKit/BskyAPI.swift b/Sources/bskyKit/BskyAPI.swift index 731aa25..23b1fe0 100644 --- a/Sources/bskyKit/BskyAPI.swift +++ b/Sources/bskyKit/BskyAPI.swift @@ -17,6 +17,7 @@ enum BskyAPI { case searchActorsTypeahead(query: String, limit: Int) // Feed endpoints + case getFeed(feed: String, limit: Int, cursor: String?) case getFeedGenerators(feeds: [String]) case getTimeline(limit: Int, cursor: String?) case getAuthorFeed(did: String, limit: Int, cursor: String?, filter: String?) @@ -56,6 +57,7 @@ extension BskyAPI: EndpointType { case .searchActors: "/xrpc/app.bsky.actor.searchActors" case .searchActorsTypeahead: "/xrpc/app.bsky.actor.searchActorsTypeahead" // Feed + case .getFeed: "/xrpc/app.bsky.feed.getFeed" case .getFeedGenerators: "/xrpc/app.bsky.feed.getFeedGenerators" case .getTimeline: "/xrpc/app.bsky.feed.getTimeline" case .getAuthorFeed: "/xrpc/app.bsky.feed.getAuthorFeed" @@ -79,7 +81,7 @@ extension BskyAPI: EndpointType { var httpMethod: HTTPMethod { switch self { case .getPreferences, .getProfile, .getProfiles, .searchActors, .searchActorsTypeahead, - .getFeedGenerators, .getTimeline, .getAuthorFeed, .getPostThread, .getPosts, .getActorLikes, .getLikes, .getRepostedBy, + .getFeed, .getFeedGenerators, .getTimeline, .getAuthorFeed, .getPostThread, .getPosts, .getActorLikes, .getLikes, .getRepostedBy, .getFollows, .getFollowers, .getBlocks, .getMutes, .listNotifications, .getUnreadCount: return .get @@ -113,6 +115,11 @@ extension BskyAPI: EndpointType { ])) // Feed endpoints + case .getFeed(let feed, let limit, let cursor): + var params: Parameters = ["feed": feed, "limit": limit] + if let cursor { params["cursor"] = cursor } + return .requestParameters(encoding: .urlEncoding(parameters: params)) + case .getFeedGenerators(let feeds): return .requestParameters(encoding: .urlEncoding(parameters: ["feeds": feeds])) diff --git a/Sources/bskyKit/BskyService.swift b/Sources/bskyKit/BskyService.swift index ff702a7..e258b94 100644 --- a/Sources/bskyKit/BskyService.swift +++ b/Sources/bskyKit/BskyService.swift @@ -119,6 +119,17 @@ public struct BskyService: Sendable { // MARK: - Feed + /// Fetches a custom feed by its AT-URI. + /// - Parameters: + /// - feed: The AT-URI of the feed generator. + /// - limit: Maximum number of posts to return (default: 50, max: 100). + /// - cursor: Pagination cursor from a previous response. + /// - Returns: Feed posts with cursor for pagination. + /// - Throws: An error if the feed is not found or the request fails. + public func getFeed(feed: String, limit: Int = 50, cursor: String? = nil) async throws -> AuthorFeed { + try await router.execute(.getFeed(feed: feed, limit: limit, cursor: cursor)) + } + /// Fetches information about custom feed generators. /// - Parameter feeds: Array of feed generator AT-URIs. /// - Returns: Details about the requested feed generators. diff --git a/Sources/bskyKit/Models/Preferences.swift b/Sources/bskyKit/Models/Preferences.swift index 1f23ef1..75ca488 100644 --- a/Sources/bskyKit/Models/Preferences.swift +++ b/Sources/bskyKit/Models/Preferences.swift @@ -8,18 +8,42 @@ import Foundation public struct Preferences: Codable, Sendable { - public var preferences: [Preference] + public var preferences: [PreferenceItem] } -public struct Preference: Codable, Sendable { +public struct PreferenceItem: Codable, Sendable { public let type: String - public var saved: [String] - public var pinned: [String] - + public let items: [SavedFeedItem]? + public let saved: [String]? + public let pinned: [String]? + enum CodingKeys: String, CodingKey { case type = "$type" - case saved - case pinned + case items, saved, pinned } } +public struct SavedFeedItem: Codable, Sendable, Identifiable { + public let id: String + public let type: String + public let value: String + public let pinned: Bool +} + +// MARK: - Convenience + +public extension Preferences { + var savedFeedItems: [SavedFeedItem] { + preferences + .filter { $0.type == "app.bsky.actor.defs#savedFeedsPrefV2" } + .flatMap { $0.items ?? [] } + } + + var pinnedFeeds: [SavedFeedItem] { + savedFeedItems.filter(\.pinned) + } + + var savedFeeds: [SavedFeedItem] { + savedFeedItems.filter { !$0.pinned } + } +} -- 2.51.2