This article shows how to create a BOT with real-time data using the Dart language and Bluesky's Firehose API.
From the Firehose API, you can retrieve Bluesky events that occur on a particular server in real time, allowing you to develop very interactive BOTs. For example, a reply can be returned in real time to data that has been mentions to a specific account.
After reading this article you will surely be able to create a BOT using the Firehose API.
BOTs intended to spam will be subject to account suspension.
I will not be liable for any account suspension as a result of spamming or otherwise. Please execute with care when testing.
Using Package
To easily handle the Bluesky API in Dart/Flutter, use the following package.
This wonderful platform needs a standard and highly integrated SDK
atproto.dart provides the best development experience in such matters for Dart/Flutter devs.
2. Packages & Tools ⚒️
2.1. Dart Packages
Package
pub.dev
Docs
at_identifier: core library for the syntax in the AT Protocol standard
To start with the most basic point, almost all events (likes and postings and etc) that occur on a particular server in Bluesky are public data. In other words, the Firehose API does not require user authentication.
With the above code, Bluesky's Firehose works perfectly, but user authentication is always required with a specific account to develop a BOT like the one we will cover in this article. This is because the BOT must be logged in with a specific account in order to respond with a reply or other action when a specific event is detected.
So, we need following implementations.
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{// This session is active for 120 minutes.finalsession=awaitbsky.createSession(identifier:'username or email',password:'password',);// Refreshed session is active for 90 days.finalrefreshedSession=awaitbsky.refreshSession(refreshJwt:session.data.refreshJwt,);// Create an instance from authenticated session.finalbluesky=bsky.Bluesky.fromSession(refreshedSession.data);finalsubscription=awaitbluesky.sync.subscribeRepoUpdates();awaitfor(finaleventinsubscription.data.stream.handleError(print)){print(event);}}
Comparing with the previous code, you will notice that the .createSession function adds a process to authenticate the user. By passing username and password credentials to the .createSession function, a Bluesky session is created and you are logged in with a specific account.
But it's important to note, however, that sessions created with the .createSession function are only valid for 120 minutes. This is a somewhat unreliable time limit when using the Firehose API for long-time connections.
So, let's use the .refreshSession function. By passing refreshJwt in the session object created by the .createSession function as an argument, you can issue a session that is valid for 90 days.
Advanced
Move on to the main issue!
Let's create a BOT that replies with Hello! if it detects a post "Say hello @test.shinyakato.dev".
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{// This session is active for 120 minutes.finalsession=awaitbsky.createSession(identifier:'username or email',password:'password',);// Refreshed session is active for 90 days.finalrefreshedSession=awaitbsky.refreshSession(refreshJwt:session.data.refreshJwt,);// Create an instance from authenticated session.finalbluesky=bsky.Bluesky.fromSession(refreshedSession.data);finalsubscription=awaitbluesky.sync.subscribeRepoUpdates();// This is a very useful adaptor.// You can filter only specific events from Firehose.finaladaptor=bsky.RepoCommitAdaptor(// Triggered only when post is created.onCreatePost:(data)async{if(data.record.text.contains('Say hello @test.shinyakato.dev')){// Post a replyawaitbluesky.feeds.createPost(text:'Hello!',// Reply settingreply:bsky.ReplyRef(root:data.toStrongRef(),parent:data.toStrongRef(),),);print('said hello to ${data.author}!');}},);awaitfor(finaleventinsubscription.data.stream.handleError(print)){switch(event){// Firehose events are union type.// Use `USubscribedRepoCommit` to filter only commit events.casebsky.USubscribedRepoCommit():// Execute adaptor like this.awaitadaptor.execute(event.data);}}}
Well done, if you run this code and post "Say hello @test.shinyakato.dev", you will instantly receive a reply saying "Hello!".
"Oh no, this seems very difficult to implement!"
Don't worry. The implementation has increased but it's very simple.
First, I describe the RepoCommitAdaptor class. The RepoCommitAdaptor class is a solution that allows troublesome commit data records to be filtered and treated as a specific type.
The commit data returned from Firehose contains almost all the record data generated by Bluesky. This means that the implementer must check as to whether the content of the record is a post or a like and so on. But, this is a fairly burdensome task for implementors using Firehose... Well, let RepoCommitAdaptor take care of all that tedious work.
When using RepoCommitAdaptor, you can define processing directly in callbacks for specific events such as onCreatePost as follows.
// This is a very useful adaptor.// You can filter only specific events from Firehose.finaladaptor=bsky.RepoCommitAdaptor(// Triggered only when post is created.onCreatePost:(data){// Do something for post data.},);
In other words, this callback is executed only for events for which a post was created upon receipt of the Firehose results.
The code for Firehose is then as follows.
awaitfor(finaleventinsubscription.data.stream.handleError(print)){switch(event){// Firehose events are union type.// Use `USubscribedRepoCommit` to filter only commit events.casebsky.USubscribedRepoCommit():// Execute adaptor like this.awaitadaptor.execute(event.data);}}
As noted in the comments to the above code, the data returned from Firehose is union. But, it can be easily handled using pattern matching in Dart3.
Union type names in bluesky package is always prefixed with U, so in this case we specify the USubscribedRepoCommit class in case, which represents Firehose's repo commit event.
Finally, the RepoCommitAdaptor defined earlier is executed in a case statement. This will all be easily resolved.
Conclusion
After reading this article you now understand how to use Dart and Bluesky's Firehose to create a realtime oriented BOT. Although we created a very simple BOT in this article, it's possible to create a more serviceable BOT by incorporating more complex rules. Try various things with the bluesky package!
If you are still not sure how to implement it after reading this article, please mention me on Bluesky.
Also, if you found this article useful, please give a star on GitHub repository. This is very helpful to activate the development community for atproto.dart.
This wonderful platform needs a standard and highly integrated SDK
atproto.dart provides the best development experience in such matters for Dart/Flutter devs.
2. Packages & Tools ⚒️
2.1. Dart Packages
Package
pub.dev
Docs
at_identifier: core library for the syntax in the AT Protocol standard