DEV Community

Shinya Kato
Shinya Kato

Posted on

How to Create Bluesky BOT using Dart and Firehose

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.

bluesky | Dart Package

The most famous and powerful Dart/Flutter library for Bluesky Social.

favicon pub.dev

GitHub logo myConsciousness / atproto.dart

AT Protocol and Bluesky things for Dart and Flutter 🎯

bluesky

AT Protocol and Bluesky Social Things for Dart/Flutter 🎯


GitHub Sponsor GitHub Sponsor melos Netlify Status

Test/Analyzer codecov Issues Pull Requests Stars Contributors Code size Last Commits License Contributor Covenant


1. Guide 🌎

The packages in this monorepo will minimize your learning time about AT Protocol and Bluesky things and maximize your Dart/Flutter development productivity with AT Protocol and Bluesky things.

Give a ⭐ on this repository to activate a project!

github_star

1.1. Packages & Tools

1.1.1. Dart & Flutter

Name pub.dev Description
at_identifier pub package Provide standard validation for identifier supported by AT Protocol to Dart/Flutter.
nsid pub package Provide standard NSID object supported by AT Protocol to Dart/Flutter.
at_uri pub package Provide standard uri supported by AT Protocol to Dart/Flutter.
xrpc pub package Provide an HTTP client specialized for XRPC communication in AT Protocol.
multiformats pub package Provide useful interfaces such a CID

If you want to learn more about bluesky package, see following official website.

AT Protocol and Bluesky Social Things for Dart and Flutter | atproto.dart

Powerful suite of AT Protocol and Bluesky-related packages for Dart/Flutter

favicon atprotodart.com

Install

Let's install bluesky with the following commands.

dart pub add bluesky
Enter fullscreen mode Exit fullscreen mode
dart pub get
Enter fullscreen mode Exit fullscreen mode

Import

Basically, when you use features of the bluesky package, just add the following import.

import 'package:bluesky/bluesky.dart';
Enter fullscreen mode Exit fullscreen mode

Basic

Let's begin with the most basic implementation for using the Firehose API with bluesky package. You can implement it as follows.

import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  final bluesky = bsky.Bluesky.anonymous();

  final subscription = await bluesky.sync.subscribeRepoUpdates();

  await for (final event in subscription.data.stream.handleError(print)) {
    print(event);
  }
}
Enter fullscreen mode Exit fullscreen mode

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' as bsky;

Future<void> main() async {
  //  This session is active for 120 minutes.
  final session = await bsky.createSession(
    identifier: 'username or email',
    password: 'password',
  );

  // Refreshed session is active for 90 days.
  final refreshedSession = await bsky.refreshSession(
    refreshJwt: session.data.refreshJwt,
  );

  // Create an instance from authenticated session.
  final bluesky = bsky.Bluesky.fromSession(refreshedSession.data);

  final subscription = await bluesky.sync.subscribeRepoUpdates();

  await for (final event in subscription.data.stream.handleError(print)) {
    print(event);
  }
}
Enter fullscreen mode Exit fullscreen mode

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' as bsky;

Future<void> main() async {
  //  This session is active for 120 minutes.
  final session = await bsky.createSession(
    identifier: 'username or email',
    password: 'password',
  );

  // Refreshed session is active for 90 days.
  final refreshedSession = await bsky.refreshSession(
    refreshJwt: session.data.refreshJwt,
  );

  // Create an instance from authenticated session.
  final bluesky = bsky.Bluesky.fromSession(refreshedSession.data);

  final subscription = await bluesky.sync.subscribeRepoUpdates();

  // This is a very useful adaptor.
  // You can filter only specific events from Firehose.
  final adaptor = bsky.RepoCommitAdaptor(
    // Triggered only when post is created.
    onCreatePost: (data) async {
      if (data.record.text.contains('Say hello @test.shinyakato.dev')) {
        // Post a reply
        await bluesky.feeds.createPost(
          text: 'Hello!',

          // Reply setting
          reply: bsky.ReplyRef(
            root: data.toStrongRef(),
            parent: data.toStrongRef(),
          ),
        );

        print('said hello to ${data.author}!');
      }
    },
  );

  await for (final event in subscription.data.stream.handleError(print)) {
    switch (event) {
      // Firehose events are union type.
      // Use `USubscribedRepoCommit` to filter only commit events.
      case bsky.USubscribedRepoCommit():
        // Execute adaptor like this.
        await adaptor.execute(event.data);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
  final adaptor = bsky.RepoCommitAdaptor(
    // Triggered only when post is created.
    onCreatePost: (data) {
      // Do something for post data.
    },
  );
Enter fullscreen mode Exit fullscreen mode

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.

  await for (final event in subscription.data.stream.handleError(print)) {
    switch (event) {
      // Firehose events are union type.
      // Use `USubscribedRepoCommit` to filter only commit events.
      case bsky.USubscribedRepoCommit():
        // Execute adaptor like this.
        await adaptor.execute(event.data);
    }
  }
Enter fullscreen mode Exit fullscreen mode

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.

GitHub logo myConsciousness / atproto.dart

AT Protocol and Bluesky things for Dart and Flutter 🎯

bluesky

AT Protocol and Bluesky Social Things for Dart/Flutter 🎯


GitHub Sponsor GitHub Sponsor melos Netlify Status

Test/Analyzer codecov Issues Pull Requests Stars Contributors Code size Last Commits License Contributor Covenant


1. Guide 🌎

The packages in this monorepo will minimize your learning time about AT Protocol and Bluesky things and maximize your Dart/Flutter development productivity with AT Protocol and Bluesky things.

Give a ⭐ on this repository to activate a project!

github_star

1.1. Packages & Tools

1.1.1. Dart & Flutter

Name pub.dev Description
at_identifier pub package Provide standard validation for identifier supported by AT Protocol to Dart/Flutter.
nsid pub package Provide standard NSID object supported by AT Protocol to Dart/Flutter.
at_uri pub package Provide standard uri supported by AT Protocol to Dart/Flutter.
xrpc pub package Provide an HTTP client specialized for XRPC communication in AT Protocol.
multiformats pub package Provide useful interfaces such a CID

Thank you.

Top comments (0)