メインコンテンツへスキップ

TextBuilder とは

TextBuilder は、Bluesky の AT Protocol が定義する facets(リッチテキスト注釈)をメソッドチェーンで組み立てるためのクラスです。 Bluesky の投稿本文はプレーンテキストですが、メンション・リンク・ハッシュタグを表示するためにはテキスト中の位置(バイトオフセット)と種別を示す facets 配列を一緒に送る必要があります。TextBuilder はこのオフセット計算と配列構築を自動的に行います。

基本的なテキスト作成

TextBuilder::make()

TextBuilder::make() で初期テキストを指定してインスタンスを生成します。初期テキストは省略可能です。
use Revolution\Bluesky\RichText\TextBuilder;

$builder = TextBuilder::make(text: 'Hello Bluesky');

text()

text() でテキストを末尾に追加します。
$builder = TextBuilder::make()
    ->text('Hello ')
    ->text('Bluesky');

// $builder->text === 'Hello Bluesky'

newLine()

改行を追加します。count で行数を指定できます(デフォルト: 1)。
$builder = TextBuilder::make('1行目')
    ->newLine()
    ->text('2行目')
    ->newLine(count: 2)
    ->text('4行目');

toPost()

TextBuilder インスタンスを Post レコードに変換します。Bluesky::post() に直接渡せます。
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\RichText\TextBuilder;

$post = TextBuilder::make('シンプルな投稿')->toPost();

$response = Bluesky::withToken()->post($post);

Post::build()

Post::build() にクロージャを渡す方法でも使えます。戻り値が Post になります。
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\RichText\TextBuilder;

$post = Post::build(function (TextBuilder $builder) {
    $builder->text('Hello Bluesky');
});

$response = Bluesky::withToken()->post($post);

メンション(@mention)の追加

mention() でメンション facet を追加します。
$builder->mention(text: '@alice.bsky.social');

DID の自動解決

did を省略すると、ハンドルから DID を自動解決します(Bluesky::resolveHandle() が呼ばれます)。
// DID を自動解決(API 呼び出しが発生します)
$builder->mention('@alice.bsky.social');

DID を明示指定

DID が既にわかっている場合は明示的に渡すことで API 呼び出しを回避できます。
// DID を直接指定(推奨: API 呼び出しなし)
$builder->mention(text: '@alice.bsky.social', did: 'did:plc:xxxxxxxxxxxxxxxxxxxx');
本番環境でメンションを多用する場合は、DID をキャッシュしておくと API 呼び出しを削減できます。

リンク(URL)の埋め込み

link() でリンク facet を追加します。
// URL そのものを表示テキストにする
$builder->link('https://laravel.com');

// 表示テキストと URL を別々に指定
$builder->link(text: 'Laravel 公式サイト', uri: 'https://laravel.com');
uri を省略すると text がそのまま URI として使われます。

ハッシュタグの追加

tag() でハッシュタグ facet を追加します。
// # から始まるテキストを渡すとタグを自動抽出
$builder->tag('#Laravel');

// タグ文字列を明示指定(# なしの文字列)
$builder->tag(text: '#Laravel', tag: 'Laravel');

複合テキストの構築

複数の facet を組み合わせてリッチな投稿テキストを作れます。
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\RichText\TextBuilder;

$post = TextBuilder::make('新しい記事を公開しました!')
    ->newLine(count: 2)
    ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
    ->text(' さんにもぜひ読んでほしいです。')
    ->newLine()
    ->link(text: '記事を読む', uri: 'https://example.com/article/1')
    ->newLine()
    ->tag('#Laravel')
    ->text(' ')
    ->tag('#PHP')
    ->toPost();

$response = Bluesky::withToken()->post($post);

Post::build() を使った記述

use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\RichText\TextBuilder;

$post = Post::build(function (TextBuilder $builder) {
    $builder->text('新しい記事を公開しました!')
            ->newLine(count: 2)
            ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
            ->text(' さんにもぜひ読んでほしいです。')
            ->newLine()
            ->link(text: '記事を読む', uri: 'https://example.com/article/1')
            ->newLine()
            ->tag('#Laravel')
            ->text(' ')
            ->tag('#PHP');
});

$response = Bluesky::withToken()->post($post);

投稿との統合

Bluesky::post() との組み合わせ

use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\RichText\TextBuilder;

$post = TextBuilder::make('test')
    ->newLine()
    ->link('https://bsky.app/')
    ->toPost();

$response = Bluesky::withToken()->post($post);

Notification チャンネルとの組み合わせ

BlueskyChanneltoBluesky() メソッドで Post::build() を使えます。
use Illuminate\Notifications\Notification;
use Revolution\Bluesky\Notifications\BlueskyChannel;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\RichText\TextBuilder;

class DeployedNotification extends Notification
{
    public function __construct(
        private string $url,
    ) {}

    public function via(object $notifiable): array
    {
        return [BlueskyChannel::class];
    }

    public function toBluesky(object $notifiable): Post
    {
        return Post::build(function (TextBuilder $builder) {
            $builder->text('デプロイが完了しました')
                    ->newLine()
                    ->link(text: '確認する', uri: $this->url)
                    ->newLine()
                    ->tag('#Laravel');
        });
    }
}

facets の自動検出

テキスト中の @mention・URL・#hashtag を自動で検出して facets を設定することもできます。
use Revolution\Bluesky\RichText\TextBuilder;

$builder = TextBuilder::make('@alice.bsky.social test https://example.com #alice')
    ->detectFacets();

// detectFacets() 後にさらに facets を追加することも可能
$builder->newLine()->tag('#bob');

$post = $builder->toPost();
detectFacets() は正規表現ベースの検出です。確実にリンクしたい場合は link()mention()tag() を明示的に使う方が確実です。

カスタム facet の追加

facet() メソッドで任意の facet 配列を直接追加できます。
use Revolution\Bluesky\RichText\TextBuilder;

$builder = TextBuilder::make();

$builder->facet([
    'index' => [
        'byteStart' => 0,
        'byteEnd' => 5,
    ],
    'features' => [
        [
            '$type' => 'app.bsky.richtext.facet#link',
            'uri' => 'https://example.com',
        ],
    ],
]);

文字数制限と注意事項

バイトオフセットと grapheme

AT Protocol の facet インデックスは UTF-8 バイトオフセットで指定します。TextBuilder は内部で strlen() を使ってバイト数を計算しています。 日本語・絵文字などのマルチバイト文字は 1 文字でも複数バイトを消費するため、文字数ではなくバイト数でオフセットが決まります。
// 'Hello' は UTF-8 で 5 バイト(1 文字 = 1 バイト)
// '日本語' は UTF-8 で 9 バイト(1 文字 = 3 バイト)
$builder = TextBuilder::make('日本語');
// strlen($builder->text) === 9

投稿の文字数制限

Bluesky の投稿は grapheme(表示上の文字数)で最大 300 文字です。バイト数ではなく grapheme で制限されるため、日本語でも 300 文字書けます。
use Illuminate\Support\Str;

$text = 'これが投稿テキストです。';

// grapheme での文字数確認
$length = Str::length($text); // mb_strlen() と同等
TextBuilder 自体は文字数チェックを行いません。300 grapheme を超える投稿を送ると AT Protocol API がエラーを返します。

メソッド一覧

メソッド説明
TextBuilder::make(string $text = '')インスタンスを生成
text(string $text)テキストを末尾に追加
newLine(int $count = 1)改行を追加
mention(string $text, ?string $did = null)メンション facet を追加
link(string $text, ?string $uri = null)リンク facet を追加
tag(string $text, ?string $tag = null)ハッシュタグ facet を追加
detectFacets()テキストから facets を自動検出
facet(array $facet)カスタム facet を追加
resetFacets()全 facets をリセット
toPost()Post レコードへ変換
toArray()['text' => ..., 'facets' => ...] 配列へ変換
Last modified on April 26, 2026