🖼

【Dart/Flutter】Bluesky APIからメディアを添付してBlueskyにポストする

2023/07/25に公開

概要

どうも、真也です。

この記事ではBluesky APIを使用してBlueskyへポストする際にメディアを添付する方法を紹介します。

使用パッケージ

DartでBluesky関連のAPIを使用する際には、Bluesky APIをDart/Flutter向けにラッピングしたblueskyがとても便利です。この記事でもこのblueskyを使用してポストにメディアを添付する方法を説明していきます。

https://pub.dev/packages/bluesky

https://atprotodart.com

パッケージをインストール

まずは先ほど紹介したblueskyをDartプロジェクトにインストールします。

https://pub.dev/packages/bluesky/install

With Dart:

dart pub add bluesky
dart pub get

With Flutter:

flutter pub add bluesky
flutter pub get

pubspec.yamlに以下のようにblueskyの依存性が追加されていれば成功です。この記事ではv0.7.6を使用します。

dependencies:
  bluesky: ^0.7.6

実装

さて、blueskyをインストールできたら、実際にblueskyを使用してポストにメディアを添付するための実装を見ていきましょう。

導入

まずはblueskyを使用したことがない人向けに、簡単にblueskyを使用してポストする実装を紹介します。次のようにしてblueskyを使用してBlueskyへポストすることができます。

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

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

  // ここでポストする。
  await bluesky.feeds.createPost(
    text: 'Hello, Bluesky!',
  );
}

/// 認証
Future<bsky.Session> get _session async {
  final session = await bsky.createSession(
    identifier: 'shinyakato.dev',
    password: 'xxxxxxx',
  );

  return session.data;
}

上記の例にあるように、blueskyを使用してBluesky APIを使用するためにはまずBlueskyオブジェクトのインスタンスを生成します。Blueskyオブジェクトを生成する方法は主に二通りあり、認証を必要とする.fromSessionと認証を必要としない.anonymousがあります。今回使用するポストを送信するエンドポイントは認証が必須のため、.fromSessionからBlueskyオブジェクトのインスタンスを生成します。実際の認証処理はcreateSession関数で行っています。

認証とBlueskyオブジェクトのインスタンスを生成できたら、あとは実際に使用するエンドポイントに対応するメソッドを使用するだけです。今回の例だとポストを送信するエンドポイントなので、例にあるbluesky.feeds.createPostのようにFeedsServiceの中のcreatePostメソッドを使用するといったようにアクセスします。

余談ですが、blueskyは先にも触れたようにBluesky APIの主要なエンドポイントを全てサポートしているので、ポストを送信する以外にも興味のある方は以下の公式ページにあるマトリクスを確認してみてください。

https://atprotodart.com/docs/api_support_matrix#bluesky

メディアをアップロードする

導入blueskyを使用してポストを送信する方法がなんとなくわかったところで、次はblueskyを使用して特定のPDSにメディアデータをアップロードする方法を紹介します。メディアのアップロードというと難しい通信処理を実装しなければいけない印象があると思いますが、blueskyを使用すると次のように簡単に実装できます。

import 'dart:io';

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

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

  // アップロードする画像ファイル。
  final file = File('./funny_photo.png');

  final uploaded = await bluesky.repositories.uploadBlob(
    // バイトデータを渡す。
    file.readAsBytesSync(),
  );

  print(uploaded);
}

/// 認証
Future<bsky.Session> get _session async {
  final session = await bsky.createSession(
    identifier: 'shinyakato.dev',
    password: 'xxxxxxx',
  );

  return session.data;
}

メディアデータを特定のPDSにアップロードするためには、上記の例でbluesky.repositories.uploadBlobとあるようにRepositoriesServiceuploadBlobメソッドを使用します。uploadBlobはメディアデータを特定のPDSにアップロードするためのメソッドで、難しい処理を考えなくてもメディアのバイトデータを渡すだけでアップロードを行うことができます。アップロードが正常に完了するとアップロードデータに関するメタ情報が返却されるので、これを次のセクションで紹介する実装で使用します。

また、認証とBlueskyオブジェクトのインスタンスを生成する部分に関しては先ほど導入で触れた例とまったく同じです。

アップロードしたデータをポストに添付する

いよいよこの記事のメインディッシュです。導入メディアのアップロードで触れた内容を応用して、以下のように実装できます。

import 'dart:io';

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

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

  // アップロードする画像ファイル。
  final file = File('./funny_photo.png');

  final uploaded = await bluesky.repositories.uploadBlob(
    // バイトデータを渡す。
    file.readAsBytesSync(),
  );

  await bluesky.feeds.createPost(
    text: 'Hello, Bluesky!',

    // 画像を添付するためにはembedパラメータを使用する。
    embed: bsky.Embed.images(
      data: bsky.EmbedImages(
        images: [
          bsky.Image(
            alt: 'Funny Photo', // ALTテキスト。
            image: uploaded.data.blob, // アップロードした画像を添付。
          ),
        ],
      ),
    ),
  );
}

/// 認証
Future<bsky.Session> get _session async {
  final session = await bsky.createSession(
    identifier: 'shinyakato.dev',
    password: 'xxxxxxx',
  );

  return session.data;
}

少し難しそうな実装が増えたような感じですが、実際に追加された実装は以下のcreatePostメソッドのembedパラメータだけです。

    embed: bsky.Embed.images(
      data: bsky.EmbedImages(
        images: [
          bsky.Image(
            alt: 'Funny Photo', // ALTテキスト。
            image: uploaded.data.blob, // アップロードした画像を添付。
          ),
        ],
      ),
    ),

この記事では深く触れませんが、createPostメソッドのembedパラメータは文字通り埋め込み要素を扱うもので、メディアの添付以外にも特定のポストを引用することやリンクカードをポストに埋め込むことができます。

https://pub.dev/documentation/bluesky/latest/bluesky/Embed-class.html

今回の場合だと画像を添付する必要があるので、embedパラメータに対してEmbed.imagesオブジェクトを渡します。EmbedオブジェクトはUnion型であり、Embed.imagesEmbedオブジェクトの中でも画像を添付するためのオブジェクトです。上記の例にあるように、Embed.imagesEmbedImagesオブジェクトを受け取り、そのEmbedImagesに複数のImageオブジェクトとアップロード済みの画像データを設定することができます。

この実装でDartプログラムを実行すると、アップロードした画像が添付された状態でポストが送信されることを確認できるかと思います。

ただ、ここまで見た感じだとembedの実装は少し難しいですよね?blueskyだとユースケースに応じてもう少し簡単に書くことができます。

アップロードデータからEmbedを生成

ポストに添付する画像が1枚しかない場合には、アップロードされたメディアデータから直接Embedオブジェクトを生成することができます。先のembedパラメータを次のように修正します。

  await bluesky.feeds.createPost(
    text: 'Hello, Bluesky!',

    // アップロードした画像を添付。
    embed: uploaded.data.blob.toEmbedImage(
      alt: 'Funny Photo',
    ),
  );

先の例との違いは一目瞭然だと思います。10行近くあった処理がたったの2行になり、直感的にも理解しやすい構造になりました。ただ、toEmbedImageを使用した方法は添付する画像が1枚の場合のみ使用できることに注意してください。

アップロードデータからImageを生成

ポストに添付する画像が複数枚ある場合は、toImageメソッドを使用するのが便利です。

    embed: bsky.Embed.images(
      data: bsky.EmbedImages(
        images: [
          uploaded.data.blob.toImage(alt: 'Funny Photo1'),
          uploaded.data.blob.toImage(alt: 'Funny Photo2'),
        ],
      ),
    ),

アップロードされたBLOBからEmbedImageを直接生成するtoEmbedImageメソッドとは異なり、toImageメソッドではアップロードされたBLOB単位でImageオブジェクトを生成します。先に紹介したtoEmbedImageほど強力なメソッドではありませんが、Imageオブジェクトを直接生成する必要がなくネストが深くなりすぎることを防ぐこともできるので是非使ってみてください。

最後に

ここまでblueskyを使用してポストにメディアを添付する方法を紹介しました。

この記事では主にアップロードした画像をポストに添付する方法に触れましたが、先にも触れたようにblueskyは非常に強力なパッケージで主要なパッケージを全てサポートしています。既にいくつかのサードパーティのクライアントやサービスで使用されており、非常に動作が安定しているパッケージです。もしBluesky関連のクライアントやサービスなどをDartやFlutterで開発してみたい方は、是非blueskyを試してみてください。

また、私はbluesky以外のBluesky関連の便利なパッケージも以下のモノレポで開発しています。もし私の開発しているパッケージを気に入ってくれた場合や役に立った場合はGitHubスターをお願いします!

https://github.com/myConsciousness/atproto.dart

もし質問などがあればGitHubのIssueやBlueskyなどで気軽に声をかけてください。

リファレンス

atproto.dart - 公式サイト

https://atprotodart.com

atproto.dart - 開発リポジトリ

https://github.com/myConsciousness/atproto.dart

atproto.dart - pub.dev

https://pub.dev/publishers/atprotodart.com/packages

GitHubで編集を提案

Discussion