Timelines and Feeds
Reading your timeline
To get the timeline for a logged in account you call agent.GetTimeLine()
.
HttpResult<Timeline> timelineResult = await agent.GetTimeline();
if (timelineResult.Succeeded && timelineResult.Result.Count != 0)
{
foreach (FeedViewPost timelineView in timelineResult.Result)
{
if (timelineView.Post.Record is Post postRecord && !string.IsNullOrEmpty(postRecord.Text))
{
Console.WriteLine($"{feedView.Post.Record.Text}";
}
Console.WriteLine($" From {@timelineView.Post.Author} {GetLabels(timelineView.Post.Author)}");
Console.WriteLine($" Posted at: {timelineView.Post.Record.CreatedAt.ToLocalTime():G}");
Console.Write($" {timelineView.Post.LikeCount} like{(timelineView.Post.LikeCount != 1 ? "s" : "")} ");
Console.WriteLine($"{timelineView.Post.RepostCount} repost{(timelineView.Post.RepostCount != 1 ? "s" : "")}.");
Console.WriteLine($" AtUri: {timelineView.Post.Uri}");
Console.WriteLine($" Cid: {timelineView.Post.Cid}");
if (timelineView.Reason is ReasonRepost repost)
{
Console.WriteLine($" ♲ Reposted by {repost.By}");
}
if (timelineView.Reply is not null)
{
// Display the parent of this post, as it was in reply to something.
}
}
}
Before attempting to iterate through the timeline the code above first checks if the API call was successful, if so the result from the API call
will contain a collection of FeedViewPost
s.
Each FeedViewPost
(defined in the lexicon for Feeds has a
Post
property, and optional Reply
and Reason
properties. The Post
property contains information about the post, such as its author,
the time it was created, etc., and a Record
property that is typically a PostRecord
, a type of record that has a Text
property that contains any
text for that Post
.
The code above shows iterating through Feed
, retrieving each individual FeedView
and writing some information from the FeedView.Post
to the console.
The Timeline sample also contains code which pages through results. When browsing timelines and feeds you will need to understand how AtProto implements pagination with cursors.
Reading feeds
A timeline is a well known feed. A feed is a view created by a feed generator over collections of posts, the criteria for which the feed controls.
A feed is referenced by its at:// uri, loaded with GetFeed()
rather than GetTimeLine()
and then paginated in exactly the same way.
AtUri feedUri = new("at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot");
const int pageSize = 25;
const int maxPagesToIterate = 10;
var getFeedResult = await agent.GetFeed(feedUri, limit: pageSize);
if (getFeedResult.Succeeded && getFeedResult.Result.Count != 0)
{
do
{
// Display the feed data
await agent.GetFeed(feedUri,limit: pageSize, cursor: getFeedResult.Result.Cursor);
} while (getFeedResult.Succeeded &&
!string.IsNullOrEmpty(getFeedResult.Result.Cursor) &&
page < maxPagesToIterate) ;
}
Tip
Some feeds, like the Discover feed use a specialized cursor, rather than the more typical timestamp. This can grow with each page, to the point when it is too large to send back to the server, hence the code above, taken from the The Feed sample limiting the do while loop not just until data runs out but also to a maximum number of pages.
Searching
Feeds aren't just lists of posts, SearchPosts()
returns its results as a feed.
const int pageSize = 25;
const int maxPagesToIterate = 10;
var searchResult = await agent.SearchPosts("#beans", limit: pageSize);
if (searchResult.Succeeded && getFeedResult.Result.Count != 0)
{
do
{
// Display the feed data
await agent.GetFeed(feedUri,limit: pageSize, cursor: searchResult.Result.Cursor);
} while (searchResult.Succeeded &&
!string.IsNullOrEmpty(getFeedResult.Result.Cursor) &&
page < maxPagesToIterate) ;
}
SearchActors
also returns a feed of results. GetActorLikes
returns a feed of likes for an actor, GetLikes
returns a feed of likes for an individual post,
and so on.
You can find these APIs and more in the HTTP reference, each API has its equivalent BlueskyAgent
method.