2 unstable releases
new 0.2.0 | Jan 18, 2025 |
---|---|
0.1.0 | Jan 18, 2025 |
#1394 in Web programming
31KB
598 lines
Skyfeed
A library for quickly building bluesky feed generators.
Primarily uses, warp, atrium api, and jetstream-oxide to greatly simplify the process of building a bluesky feed generator.
Quick Start
Create a .env file with the following variables:
PUBLISHER_BLUESKY_HANDLE
Your handle - something like "someguy.bsky.social"
PUBLISHER_BLUESKY_HANDLE="someguy.bsky.social"
PUBLISHER_BLUESKY_PASSWORD
An app password. You can create app passwords here
PUBLISHER_BLUESKY_PASSWORD="..."
PUBLISHER_DID
Your DID.
This can be a little hard to track down - you can use this utility to check your DID once you've added PUBLISHER_BLUESKY_HANDLE
& PUBLISHER_BLUESKY_PASSWORD
to your .env file.
To run the my_did utility - clone this repo & run this command inside the crate directory
cargo run --bin my_did
PUBLISHER_DID="..."
FEED_GENERATOR_HOSTNAME
The host name for your feed generator.
(In the URL https://github.com/cyypherus/skyfeed
the host name is github.com
)
You can develop your feed locally without setting this to a real value. However, when publishing, this value must be a domain that:
- Points to your service.
- Is secured with SSL (HTTPS).
- Is accessible on the public internet.
FEED_GENERATOR_HOSTNAME="..."
Building a Feed
Let's build a simple feed generator about cats.
[Note] In a real implementation storage should be implemented with a database such as sqlite for more efficient queries & persistent data.
Implement the FeedHandler
Trait
Your feed handler is responsible for storing and managing firehose input. For the sake of simplicity, we'll use HashMaps to manage posts and likes.
struct MyFeedHandler {
posts: HashMap<Uri, Post>,
likes: HashMap<Uri, Uri>,
}
impl FeedHandler for MyFeedHandler {
async fn insert_post(&mut self, post: Post) {
if post.text.to_lowercase().contains(" cat ") {
info!("Storing {post:?}");
const MAX_POSTS: usize = 100;
self.posts.insert(post.uri.clone(), post);
if self.posts.len() > MAX_POSTS {
let mut post_likes: HashMap<&Uri, u32> = HashMap::new();
for liked_post_uri in self.likes.values() {
*post_likes.entry(liked_post_uri).or_insert(0) += 1;
}
if let Some(least_liked_uri) = self
.posts
.keys()
.min_by_key(|uri| post_likes.get(uri).unwrap_or(&0))
.cloned()
{
self.posts.remove(&least_liked_uri);
}
}
}
}
async fn delete_post(&mut self, uri: Uri) {
self.posts.remove(&uri);
}
async fn like_post(&mut self, like_uri: Uri, liked_post_uri: Uri) {
self.likes.insert(like_uri, liked_post_uri);
}
async fn delete_like(&mut self, like_uri: Uri) {
self.likes.remove(&like_uri);
}
async fn serve_feed(&self, request: Request) -> FeedResult {
info!("Serving {request:?}");
let mut post_likes: HashMap<&Uri, u32> = HashMap::new();
for liked_post_uri in self.likes.values() {
*post_likes.entry(liked_post_uri).or_insert(0) += 1;
}
let mut top_posts: Vec<_> = self.posts.values().collect();
top_posts.sort_by(|a, b| {
let likes_a = post_likes.get(&a.uri).unwrap_or(&0);
let likes_b = post_likes.get(&b.uri).unwrap_or(&0);
likes_b.cmp(likes_a)
});
let top_5_posts: Vec<_> = top_posts.into_iter().take(5).collect();
FeedResult {
cursor: None,
feed: top_5_posts
.into_iter()
.map(|post| post.uri.clone())
.collect(),
}
}
}
Implement the Feed
trait
We'll need to use Arc<Mutex<FeedHandler>>
to enable concurrent shared access.
struct MyFeed {
handler: Arc<Mutex<MyFeedHandler>>,
}
impl Feed<MyFeedHandler> for MyFeed {
fn handler(&mut self) -> Arc<Mutex<MyFeedHandler>> {
self.handler.clone()
}
}
Start your feed!
Now we can create an instance of our Feed
and start it on a local address.
#[tokio::main]
async fn main() {
let mut feed = MyFeed {
handler: Arc::new(Mutex::new(MyFeedHandler {
posts: HashMap::new(),
likes: HashMap::new(),
})),
};
feed.start("Cats", ([0, 0, 0, 0], 3030)).await
}
Publish to bluesky
This repo also contains publish (and unpublish) utilities for managing your feed's publicity.
To run these, clone this repo & run this command inside the crate directory
cargo run --bin publish
If you'd like to verify your feed server's endpoints locally before you publish, you can also use the verify utility.
Dependencies
~20–37MB
~596K SLoC