> ## 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` 套件將 TypeScript 版的內容以 PHP/Laravel 實作。

官方教學中會透過 `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-TW/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>
  請於 GitHub Actions 的 secrets 中註冊 `BLUESKY_IDENTIFIER` 與 `BLUESKY_APP_PASSWORD`。
</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-TW/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-TW/packages/laravel-bluesky/feed-generator) 詳細說明使用 Laravel 實作自訂 feed generator 的方法。

### OAuth 認證

[Socialite](/zh-TW/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-TW/packages/laravel-bluesky/bluesky-manager)
* [通知頻道](/zh-TW/packages/laravel-bluesky/notification)
* [Amazon Bedrock 驅動](/zh-TW/packages/laravel-amazon-bedrock)


## Related topics

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