什么是 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 通道结合
可以在 BlueskyChannel 的 toBluesky() 方法中使用 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' => ...] 数组 |