How to Create Bluesky BOT using Dart and Firehose
Tags: dart, bluesky, atproto, bot
Published: 2023-09-09 12:50:06

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](https://pub.dev/packages/bluesky)**

{% embed https://pub.dev/packages/bluesky %}
{% embed https://github.com/myConsciousness/atproto.dart %}

If you want to learn more about **[bluesky](https://pub.dev/packages/bluesky)** package, see following **[official website](https://atprotodart.com)**.

{% embed https://atprotodart.com %}

## Install

Let's install **[bluesky](https://pub.dev/packages/bluesky)** with the following commands.

```bash
dart pub add bluesky
```

```dash
dart pub get
```

## Import

Basically, when you use features of the **[bluesky](https://pub.dev/packages/bluesky)** package, just add the following import.

```dart
import 'package:bluesky/bluesky.dart';
```

## Basic

Let's begin with the most basic implementation for using the Firehose API with **[bluesky](https://pub.dev/packages/bluesky)** package. You can implement it as follows.

```dart
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);
  }
}
```

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.

```dart
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);
  }
}
```

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".

```dart
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);
    }
  }
}
```

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. 

```dart 
  // 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.
    },
  );
```

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.

```dart
  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);
    }
  }
```

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](https://pub.dev/packages/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](https://pub.dev/packages/bluesky)** package!

If you are still not sure how to implement it after reading this article, please mention me on Bluesky.

- **[@shinyakato.dev](https://bsky.app/profile/shinyakato.dev)**

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](https://github.com/myConsciousness/atproto.dart)**.

{% embed https://github.com/myConsciousness/atproto.dart %}

Thank you.