跳转到主要内容

什么是 TextBuilder

TextBuilder 是一个用来通过方法链构建 Bluesky AT Protocol 中 facets(富文本注解)的类。 Bluesky 的帖子正文本身是纯文本,但要显示 mention、链接、hashtag 时,必须一并发送 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 record。可以直接传给 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

通过 mention() 添加 mention facet。
$builder->mention(text: '@alice.bsky.social');

自动解析 DID

省略 did 时会从 handle 自动解析 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');
在生产环境中大量使用 mention 时,缓存 DID 可以减少 API 调用。

嵌入链接(URL)

通过 link() 添加 link facet。
// 直接把 URL 本身作为显示文本
$builder->link('https://laravel.com');

// 分别指定显示文本和 URL
$builder->link(text: 'Laravel 官方网站', uri: 'https://laravel.com');
省略 uri 时,text 会被直接当作 URI 使用。

添加 hashtag

通过 tag() 添加 hashtag facet。
// 传入以 # 开头的文本时会自动抽取 tag
$builder->tag('#Laravel');

// 显式指定 tag 字符串(不带 # 的字符串)
$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() 计算字节数。 中文、日文、emoji 等多字节字符即使只有一个字符也会占用多个字节,所以偏移是按 字节数 而不是 字符数 计算的。
// '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)添加 mention facet
link(string $text, ?string $uri = null)添加 link facet
tag(string $text, ?string $tag = null)添加 hashtag facet
detectFacets()从文本中自动检测 facets
facet(array $facet)添加自定义 facet
resetFacets()重置全部 facets
toPost()转换为 Post record
toArray()转换为 ['text' => ..., 'facets' => ...] 数组
最后修改于 2026年7月13日