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

# Bot 教程 - Laravel Bluesky

> AT Protocol 官方 bot 教程的 Laravel 版。使用 laravel-bluesky 包实现发帖 bot、回复 bot 和 label bot。

## 概览

本页是 [AT Protocol 官方 bot 教程](https://atproto.com/ja/guides/bot-tutorial) 的 Laravel 版。介绍如何用 `laravel-bluesky` 包，通过 PHP/Laravel 实现原本面向 TypeScript 的内容。

官方教程会通过 `lex` 命令每次下载 Lexicon 文件，但 `laravel-bluesky` 已经从 [atproto-lexicon-contracts](https://github.com/invokable/atproto-lexicon-contracts) 事先导入了全部 Lexicon，所以不需要这一步。通过 Artisan 命令和 `HasShortHand` trait 的方法，几乎可以完成所有操作。

```mermaid theme={null}
graph LR
    A["官方教程<br>(TypeScript)"] -->|"Laravel 版"| B["laravel-bluesky"]
    B --> C["Artisan 命令"]
    B --> D["HasShortHand trait"]
    B --> E["定时任务<br>/ GitHub Actions"]
```

## 前置条件

* 已经安装 `laravel-bluesky` 包
* 已经准备好 Bluesky 账号和 App Password

安装方法请参考 [Laravel Bluesky](/zh/packages/laravel-bluesky/index)。

```dotenv theme={null}
BLUESKY_IDENTIFIER=yourbot.bsky.social
BLUESKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
```

***

## 第 1 部分：基础 bot（发帖）

### 创建 Artisan 命令

使用 `php artisan make:command` 创建 bot 用的命令。

```bash theme={null}
php artisan make:command BotPostCommand
```

编辑生成的 `app/Console/Commands/BotPostCommand.php`。

```php theme={null}
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Revolution\Bluesky\Facades\Bluesky;

class BotPostCommand extends Command
{
    protected $signature = 'bot:post';

    protected $description = 'Post to Bluesky';

    public function handle(): void
    {
        Bluesky::login(
            identifier: config('bluesky.identifier'),
            password: config('bluesky.password'),
        )->post('🙂');

        $this->info('Posted successfully.');
    }
}
```

### 手动执行

```bash theme={null}
php artisan bot:post
```

### 使用定时任务自动执行

在 `routes/console.php` 中添加定时执行的设置。

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

// 每 3 小时发一次
Schedule::command('bot:post')->everyThreeHours();
```

要启用调度器，请在 cron 中配置以下条目。

```bash theme={null}
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
```

### 用 GitHub Actions 自动执行

如果没有服务器，也可以用 GitHub Actions 自动执行。

```yaml theme={null}
# .github/workflows/bot.yml
name: Bot Post

on:
  schedule:
    - cron: '0 */3 * * *'  # 每 3 小时
  workflow_dispatch:

jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
      - run: composer install --no-dev --optimize-autoloader
      - run: php artisan bot:post
        env:
          BLUESKY_IDENTIFIER: ${{ secrets.BLUESKY_IDENTIFIER }}
          BLUESKY_APP_PASSWORD: ${{ secrets.BLUESKY_APP_PASSWORD }}
```

<Tip>
  请把 `BLUESKY_IDENTIFIER` 和 `BLUESKY_APP_PASSWORD` 注册到 GitHub Actions 的 secrets 中。
</Tip>

***

## 第 2 部分：回复 bot（监听 mention）

创建一个对 mention 自动回复的 bot。同时也介绍使用 AI 生成回复的示例。

### 获取通知并对 mention 回复

```bash theme={null}
php artisan make:command BotReplyCommand
```

```php theme={null}
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\Types\ReplyRef;
use Revolution\Bluesky\Types\StrongRef;

class BotReplyCommand extends Command
{
    protected $signature = 'bot:reply';

    protected $description = 'Reply to mentions on Bluesky';

    public function handle(): void
    {
        Bluesky::login(
            identifier: config('bluesky.identifier'),
            password: config('bluesky.password'),
        );

        $notifications = Bluesky::listNotifications(limit: 20)->json('notifications', []);

        foreach ($notifications as $notification) {
            // 只处理 mention 类型的通知
            if (data_get($notification, 'reason') !== 'mention') {
                continue;
            }

            // 跳过已读通知（isRead 为 true 的表示已处理）
            if (data_get($notification, 'isRead')) {
                continue;
            }

            $uri = data_get($notification, 'uri');
            $cid = data_get($notification, 'cid');

            if (! $uri || ! $cid) {
                continue;
            }

            $ref = StrongRef::to(uri: $uri, cid: $cid);
            $reply = ReplyRef::to(root: $ref, parent: $ref);

            $post = Post::create('你好！感谢你的 mention。🙂')
                ->reply($reply);

            Bluesky::post($post);

            $this->info("Replied to: {$uri}");
        }

        // 将通知标记为已读
        Bluesky::updateSeenNotifications(now()->toISOString());
    }
}
```

<Info>
  如果 thread 的 root 与父帖不同，请通过 `app.bsky.feed.getPostThread` 获取 thread 信息并设置到 `root`。这里的简易实现直接把父帖当作 root。
</Info>

### 定时轮询

```php theme={null}
// routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('bot:reply')->everyFiveMinutes();
```

### 用 AI 生成回复

结合 `laravel/ai` 包和 `laravel-amazon-bedrock` 驱动，可以根据 mention 内容生成 AI 回复。

```bash theme={null}
composer require laravel/ai revolution/laravel-amazon-bedrock
```

创建 AI agent。

```php theme={null}
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;

class BotReplyAgent implements Agent
{
    use Promptable;

    public function instructions(): string
    {
        return '你是一个在 Bluesky 上活动的友好 bot。'
            . '请对用户的消息用中文生成简短且亲切的回复。'
            . '回复请控制在 200 字以内。';
    }
}
```

在命令中使用 AI 回复。

```php theme={null}
<?php

namespace App\Console\Commands;

use App\Ai\Agents\BotReplyAgent;
use Illuminate\Console\Command;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\Types\ReplyRef;
use Revolution\Bluesky\Types\StrongRef;

class BotReplyCommand extends Command
{
    protected $signature = 'bot:reply';

    protected $description = 'Reply to mentions on Bluesky using AI';

    public function handle(): void
    {
        Bluesky::login(
            identifier: config('bluesky.identifier'),
            password: config('bluesky.password'),
        );

        $notifications = Bluesky::listNotifications(limit: 20)->json('notifications', []);

        foreach ($notifications as $notification) {
            if (data_get($notification, 'reason') !== 'mention') {
                continue;
            }

            if (data_get($notification, 'isRead')) {
                continue;
            }

            $uri = data_get($notification, 'uri');
            $cid = data_get($notification, 'cid');
            $mentionText = data_get($notification, 'record.text', '');

            if (! $uri || ! $cid) {
                continue;
            }

            // 用 AI 生成回复
            $replyText = (new BotReplyAgent)->prompt($mentionText)->text;

            $ref = StrongRef::to(uri: $uri, cid: $cid);
            $reply = ReplyRef::to(root: $ref, parent: $ref);

            $post = Post::create($replyText)->reply($reply);

            Bluesky::post($post);

            $this->info("AI replied to: {$uri}");
        }

        Bluesky::updateSeenNotifications(now()->toISOString());
    }
}
```

<Tip>
  `laravel-amazon-bedrock` 的配置方法请参考 [Amazon Bedrock 驱动](/zh/packages/laravel-amazon-bedrock)。
</Tip>

***

## 第 3 部分：label bot

对应官方教程中的 `labelAsBot`。给 bot 账号的个人资料添加 self label，明确表明这是一个自动化账号。

<Info>
  label 只需要在初次设置时执行一次。它作为独立于第 1、2 部分的命令来实现。
</Info>

### 创建 label bot 命令

```bash theme={null}
php artisan make:command BotLabelCommand
```

```php theme={null}
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Profile;
use Revolution\Bluesky\Types\SelfLabels;

class BotLabelCommand extends Command
{
    protected $signature = 'bot:label';

    protected $description = 'Add bot self-label to the Bluesky profile';

    public function handle(): void
    {
        Bluesky::login(
            identifier: config('bluesky.identifier'),
            password: config('bluesky.password'),
        )->upsertProfile(function (Profile $profile) {
            // 设置 !bot label
            $profile->labels(SelfLabels::make(['!bot']));
        });

        $this->info('Bot label applied successfully.');
    }
}
```

### 执行

```bash theme={null}
php artisan bot:label
```

请在 bot 初次设置时执行一次即可。label 会持久保存在个人资料中。

<Warning>
  `!bot` 是 Bluesky 的标准 self label。官方推荐所有 bot 账号都应设置。
</Warning>

***

## 其他官方教程

AT Protocol 的官方教程还有其他几个主题。以下是 `laravel-bluesky` 的对应情况。

### 自定义 feed

[Feed Generator](/zh/packages/laravel-bluesky/feed-generator) 中详细介绍了用 Laravel 实现自定义 feed generator 的方法。

### OAuth 认证

[Socialite](/zh/packages/laravel-bluesky/socialite) 中介绍了如何用 Laravel Socialite 实现 OAuth 认证。比官方教程更易于实现。

### 社交应用（statusphere）

Laravel 版的 statusphere 以 [invokable/statusphere](https://github.com/invokable/statusphere) 公开发布。它基于尚未使用 `lex` 或 `tap` 命令的旧版 statusphere，因此与最新官方教程略有差异。

## 参考链接

* [AT Protocol 官方 bot 教程（英文）](https://atproto.com/guides/bot-tutorial)
* [AT Protocol 官方 bot 教程（日文）](https://atproto.com/ja/guides/bot-tutorial)
* [laravel-bluesky](https://github.com/invokable/laravel-bluesky)
* [BlueskyManager 与 HasShortHand](/zh/packages/laravel-bluesky/bluesky-manager)
* [通知通道](/zh/packages/laravel-bluesky/notification)
* [Amazon Bedrock 驱动](/zh/packages/laravel-amazon-bedrock)


## Related topics

- [Laravel Socialite（社交登录）](/zh/socialite.md)
- [Webhook / Bot - LINE SDK for Laravel](/zh/packages/laravel-line-sdk/bot.md)
- [Feed Generator](/zh/packages/laravel-bluesky/feed-generator.md)
- [教程 - Laravel Console Starter](/zh/packages/laravel-console-starter/tutorial.md)
- [Laravel Bluesky](/zh/packages/laravel-bluesky/index.md)
