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

# 봇 튜토리얼 - Laravel Bluesky

> AT Protocol 공식 봇 튜토리얼의 Laravel 버전. laravel-bluesky 패키지를 사용해 게시 봇·리플라이 봇·라벨 봇을 구현합니다.

## 개요

이 페이지는 [AT Protocol 공식 봇 튜토리얼](https://atproto.com/ja/guides/bot-tutorial)의 Laravel 버전입니다. TypeScript용 내용을 `laravel-bluesky` 패키지를 사용해 PHP/Laravel로 구현하는 방법을 설명합니다.

공식 튜토리얼에서는 `lex` 명령어로 Lexicon 파일을 매번 다운로드하지만, `laravel-bluesky`에서는 [atproto-lexicon-contracts](https://github.com/invokable/atproto-lexicon-contracts)에서 사전에 모든 Lexicon을 임포트해 두었기 때문에 이 절차는 필요 없습니다. Artisan 명령어와 `HasShortHand` 트레이트의 메서드를 사용하면 거의 모든 조작을 실현할 수 있습니다.

```mermaid theme={null}
graph LR
    A["공식 튜토리얼<br>(TypeScript)"] -->|"Laravel 버전"| B["laravel-bluesky"]
    B --> C["Artisan 명령어"]
    B --> D["HasShortHand 트레이트"]
    B --> E["태스크 스케줄<br>/ GitHub Actions"]
```

## 전제 조건

* `laravel-bluesky` 패키지가 설치되어 있을 것
* Bluesky 계정과 앱 패스워드가 준비되어 있을 것

설치 방법은 [Laravel Bluesky](/ko/packages/laravel-bluesky/index)를 참조하세요.

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

***

## Part 1: 기본 봇(게시)

### Artisan 명령어 만들기

`php artisan make:command`로 봇용 명령어를 생성합니다.

```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의 시크릿에 `BLUESKY_IDENTIFIER`와 `BLUESKY_APP_PASSWORD`를 등록하세요.
</Tip>

***

## Part 2: 리플라이 봇(멘션 모니터링)

멘션에 자동 회신하는 봇을 만듭니다. AI를 사용해 리플라이를 생성하는 예시도 소개합니다.

### 알림을 취득하여 멘션에 답장하기

```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) {
            // 멘션 알림만 처리
            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('안녕하세요! 멘션 감사합니다. 🙂')
                ->reply($reply);

            Bluesky::post($post);

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

        // 알림을 읽음 처리
        Bluesky::updateSeenNotifications(now()->toISOString());
    }
}
```

<Info>
  스레드의 root가 다른 경우에는 `app.bsky.feed.getPostThread`로 스레드 정보를 취득해 `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` 드라이버를 조합하면 멘션 내용에 맞춘 AI 리플라이를 생성할 수 있습니다.

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

AI 에이전트를 생성합니다.

```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에서 활동하는 친근한 봇입니다.'
            . '사용자의 메시지에 대해 짧고 친숙한 답장을 한국어로 생성해 주세요.'
            . '답장은 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 드라이버](/ko/packages/laravel-amazon-bedrock)를 참조하세요.
</Tip>

***

## Part 3: 라벨 봇

공식 튜토리얼의 `labelAsBot`에 해당하는 기능입니다. 봇 계정의 프로필에 셀프 라벨을 부여하여 자동화된 계정임을 명시합니다.

<Info>
  라벨 부여는 최초 셋업 시에 한 번만 실행합니다. Part 1·2의 명령어와는 독립된 별도의 명령어로 구현합니다.
</Info>

### 라벨 봇 명령어 만들기

```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 라벨을 설정
            $profile->labels(SelfLabels::make(['!bot']));
        });

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

### 실행하기

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

이 명령어는 봇의 최초 셋업 시에 한 번만 실행하세요. 라벨은 프로필에 영구적으로 저장됩니다.

<Warning>
  `!bot`은 Bluesky의 표준 셀프 라벨입니다. 봇 계정에는 반드시 설정하는 것이 권장됩니다.
</Warning>

***

## 다른 공식 튜토리얼에 대해

AT Protocol 공식 튜토리얼에는 이 외에도 몇 가지 토픽이 있습니다. `laravel-bluesky`에서의 지원 상황을 정리합니다.

### 커스텀 피드

[Feed Generator](/ko/packages/laravel-bluesky/feed-generator)에서 Laravel을 사용한 커스텀 피드 제너레이터 구현 방법을 자세히 설명하고 있습니다.

### OAuth 인증

[Socialite](/ko/packages/laravel-bluesky/socialite)에서 Laravel Socialite를 사용한 OAuth 인증 구현 방법을 설명하고 있습니다. 공식 튜토리얼보다 간단하게 구현할 수 있습니다.

### 소셜 앱(statusphere)

Laravel 버전의 statusphere가 [invokable/statusphere](https://github.com/invokable/statusphere)로 공개되어 있습니다. `lex`나 `tap` 명령어를 사용하지 않은 이전 statusphere를 기반으로 하고 있어, 최신 공식 튜토리얼과는 약간 다른 점이 있습니다.

## 참고 링크

* [AT Protocol 공식 봇 튜토리얼(영어)](https://atproto.com/guides/bot-tutorial)
* [AT Protocol 공식 봇 튜토리얼(일본어)](https://atproto.com/ja/guides/bot-tutorial)
* [laravel-bluesky](https://github.com/invokable/laravel-bluesky)
* [BlueskyManager와 HasShortHand](/ko/packages/laravel-bluesky/bluesky-manager)
* [알림 채널](/ko/packages/laravel-bluesky/notification)
* [Amazon Bedrock 드라이버](/ko/packages/laravel-amazon-bedrock)


## Related topics

- [튜토리얼 - Laravel Console Starter](/ko/packages/laravel-console-starter/tutorial.md)
- [Laravel Socialite (소셜 인증)](/ko/socialite.md)
- [Feed Generator](/ko/packages/laravel-bluesky/feed-generator.md)
- [Laravel이란](/ko/introduction.md)
- [검증](/ko/validation.md)
