> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# TextBuilder

> 详细介绍如何用 TextBuilder 类在 PHP 中构建 Bluesky 的富文本 (facets)。

## 什么是 TextBuilder

`TextBuilder` 是一个用来通过方法链构建 Bluesky AT Protocol 中 **facets**（富文本注解）的类。

Bluesky 的帖子正文本身是纯文本，但要显示 mention、链接、hashtag 时，必须一并发送 `facets` 数组来标记文本中位置（字节偏移）与类型。`TextBuilder` 会自动完成这些偏移计算和数组构造。

```mermaid theme={null}
sequenceDiagram
    participant App as Laravel 应用
    participant TB as TextBuilder
    participant Post as Post record
    participant AT as AT Protocol

    App->>TB: TextBuilder::make('Hello')
    App->>TB: ->mention('@alice.bsky.social')
    App->>TB: ->link('https://example.com')
    App->>TB: ->tag('#Laravel')
    TB->>TB: 计算字节偏移并<br>构建 facets 数组
    TB->>Post: toPost()
    Post->>AT: Bluesky::post($post)
    AT-->>App: 响应
```

## 基本文本构建

### TextBuilder::make()

通过 `TextBuilder::make()` 指定初始文本并创建实例。初始文本可省略。

```php theme={null}
use Revolution\Bluesky\RichText\TextBuilder;

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

### text()

`text()` 将文本追加到末尾。

```php theme={null}
$builder = TextBuilder::make()
    ->text('Hello ')
    ->text('Bluesky');

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

### newLine()

添加换行。可通过 `count` 指定行数（默认: 1）。

```php theme={null}
$builder = TextBuilder::make('第 1 行')
    ->newLine()
    ->text('第 2 行')
    ->newLine(count: 2)
    ->text('第 4 行');
```

### toPost()

将 `TextBuilder` 实例转换为 `Post` record。可以直接传给 `Bluesky::post()`。

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\RichText\TextBuilder;

$post = TextBuilder::make('简单的帖子')->toPost();

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

### Post::build()

也可以通过给 `Post::build()` 传闭包的方式使用，返回值就是 `Post`。

```php theme={null}
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。

```php theme={null}
$builder->mention(text: '@alice.bsky.social');
```

### 自动解析 DID

省略 `did` 时会从 handle 自动解析 DID（会调用 `Bluesky::resolveHandle()`）。

```php theme={null}
// 自动解析 DID（会产生 API 调用）
$builder->mention('@alice.bsky.social');
```

### 显式指定 DID

如果已经知道 DID，可以显式传入以避免 API 调用。

```php theme={null}
// 直接指定 DID（推荐：无需 API 调用）
$builder->mention(text: '@alice.bsky.social', did: 'did:plc:xxxxxxxxxxxxxxxxxxxx');
```

<Tip>
  在生产环境中大量使用 mention 时，缓存 DID 可以减少 API 调用。
</Tip>

## 嵌入链接（URL）

通过 `link()` 添加 link facet。

```php theme={null}
// 直接把 URL 本身作为显示文本
$builder->link('https://laravel.com');

// 分别指定显示文本和 URL
$builder->link(text: 'Laravel 官方网站', uri: 'https://laravel.com');
```

省略 `uri` 时，`text` 会被直接当作 URI 使用。

## 添加 hashtag

通过 `tag()` 添加 hashtag facet。

```php theme={null}
// 传入以 # 开头的文本时会自动抽取 tag
$builder->tag('#Laravel');

// 显式指定 tag 字符串（不带 # 的字符串）
$builder->tag(text: '#Laravel', tag: 'Laravel');
```

## 构建复合文本

可以组合多个 facet 来生成富文本帖子。

```php theme={null}
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() 的写法

```php theme={null}
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() 结合

```php theme={null}
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()`。

```php theme={null}
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。

```php theme={null}
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();
```

<Warning>
  `detectFacets()` 是基于正则表达式的检测。如果希望确保链接一定生效，明确调用 `link()` / `mention()` / `tag()` 更可靠。
</Warning>

## 添加自定义 facet

通过 `facet()` 方法可以直接添加任意的 facet 数组。

```php theme={null}
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 等多字节字符即使只有一个字符也会占用多个字节，所以偏移是按 **字节数** 而不是 **字符数** 计算的。

```php theme={null}
// 'Hello' 在 UTF-8 中占 5 字节（1 字符 = 1 字节）
// '日本語' 在 UTF-8 中占 9 字节（1 字符 = 3 字节）
$builder = TextBuilder::make('日本語');
// strlen($builder->text) === 9
```

### 帖子字数限制

Bluesky 帖子的限制是 **按 grapheme（显示字符数）最多 300 个字符**。限制是按 grapheme 而非字节数计算，所以中文、日文也能写满 300 字符。

```php theme={null}
use Illuminate\Support\Str;

$text = '这是帖子内容。';

// 按 grapheme 查看字符数
$length = Str::length($text); // 与 mb_strlen() 等价
```

<Info>
  `TextBuilder` 本身不做字数校验。如果发送超过 300 grapheme 的帖子，AT Protocol API 会返回错误。
</Info>

## 方法一览

| 方法                                           | 说明                                        |
| -------------------------------------------- | ----------------------------------------- |
| `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' => ...]` 数组 |

<Info>
  Source: [src/RichText/TextBuilder.php](https://github.com/invokable/laravel-bluesky/blob/main/src/RichText/TextBuilder.php)
</Info>


## Related topics

- [Basic client - Laravel Bluesky](/zh/packages/laravel-bluesky/basic-client.md)
- [Laravel Bluesky](/zh/packages/laravel-bluesky/index.md)
- [通知通道 - Laravel Bluesky](/zh/packages/laravel-bluesky/notification.md)
- [Eloquent 作用域](/zh/advanced/eloquent-scopes.md)
- [Get accent phrases from text](/zh/packages/laravel-voicevox/engine-api/query-editing/get-accent-phrases-from-text.md)
