메인 콘텐츠로 건너뛰기

개요

Bluesky Facade는 BlueskyManager의 얇은 래퍼입니다. BlueskyManager는 여러 Agent를 관리하며, HasShortHand 트레이트가 자주 사용하는 메서드의 단축키를 한데 모아 제공합니다.

아키텍처

BlueskyServiceProviderFactory::classBlueskyManager로서 스코프 싱글톤(요청당 1개 인스턴스)으로 등록합니다.
// BlueskyServiceProvider::register()
$this->app->scoped(Factory::class, BlueskyManager::class);
Facade의 getFacadeAccessor()가 이 바인딩을 해결합니다.
// Facades/Bluesky.php
protected static function getFacadeAccessor(): string
{
    return Factory::class;
}

BlueskyManager의 코어 메서드

BlueskyManager 자체가 정의하는 코어 메서드는 다음과 같습니다. HasShortHand 트레이트의 메서드와 구분해서 파악해 두면 내부 구조 이해에 도움이 됩니다.

인증

메서드설명
login(string $identifier, string $password, ?string $service = null)App Password로 인증하고 LegacyAgent를 설정
withToken(?AbstractSession $token)OAuthSession 또는 LegacySession을 전달하여 Agent를 설정
check(): bool인증되었는지 확인(토큰 유효 기간도 검증)
refreshSession()토큰을 리프레시
logout()Agent를 초기화

Agent 조작

메서드설명
agent(): ?Agent현재의 Agent를 취득
withAgent(?Agent $agent)Agent를 직접 설정
assertDid(): string인증된 DID를 반환. 미인증이면 예외를 throw

HTTP 클라이언트

메서드설명
client(bool $auth = true): AtpClient인증된(또는 익명의) XRPC 클라이언트를 반환
public(): BskyClient인증이 필요 없는 퍼블릭 엔드포인트 클라이언트를 반환
send(BackedEnum|string $api, string $method, bool $auth, ?array $params, ?callable $callback)임의의 AT Protocol API를 직접 호출

유틸리티

메서드설명
identity(): IdentityDID 해결 등 아이덴티티 서비스를 취득
pds(): PDSPDS 정보 서비스를 취득
entryway(?string $path): string서비스 URL(bsky.social 등)을 반환
publicEndpoint(): string퍼블릭 엔드포인트 URL을 반환

HasShortHand 트레이트

HasShortHand 트레이트는 AT Protocol의 저수준 API를 PHP에 알맞은 이해하기 쉬운 메서드로 래핑합니다. BlueskyManageruse HasShortHand;라고 기재하기만 하면, 이 메서드들을 Bluesky::에서 직접 호출할 수 있습니다.
// BlueskyManager.php
class BlueskyManager implements Factory
{
    use Conditionable;
    use HasShortHand;
    use Macroable;
    // ...
}
HasShortHand를 독립된 트레이트로 만든 이유는 테스트·커스터마이즈의 용이성 때문입니다. 이 트레이트를 사용하면 BlueskyManager의 코드를 단순하게 유지하면서도 풍부한 API 단축키를 제공할 수 있습니다.

게시·피드

메서드설명
post(Post|string|array $text)신규 게시를 작성
getPost(string $uri)AT-URI로 게시를 취득
getPosts(array $uris)여러 게시를 한꺼번에 취득
deletePost(string $uri)게시를 삭제
getTimeline(?string $algorithm, ?int $limit, ?string $cursor)홈 타임라인을 취득
getAuthorFeed(?string $actor, ...)지정 계정의 피드를 취득
searchPosts(string $q, ...)게시를 검색

인게이지먼트

메서드설명
like(Like|StrongRef $subject)좋아요를 작성
deleteLike(string $uri)좋아요를 취소
repost(Repost|StrongRef $subject)리포스트를 작성
deleteRepost(string $uri)리포스트를 취소
getActorLikes(?string $actor, ...)계정의 좋아요 목록을 취득

팔로우

메서드설명
follow(Follow|string $did)팔로우
deleteFollow(string $uri)팔로우 해제
getFollowers(?string $actor, ...)팔로워 목록을 취득
getFollows(?string $actor, ...)팔로우 중인 목록을 취득

프로필·계정

메서드설명
getProfile(?string $actor)프로필을 취득
upsertProfile(callable $callback)프로필을 갱신
resolveHandle(string $handle)핸들을 DID로 해결

미디어

메서드설명
uploadBlob(StreamInterface|string $data, string $type)이미지 등의 블롭을 업로드
uploadVideo(StreamInterface|string $data, string $type)동영상을 업로드
getJobStatus(string $jobId)동영상 업로드 잡의 상태를 확인
getUploadLimits()업로드 제한을 취득

알림

메서드설명
listNotifications(...)알림 목록을 취득
countUnreadNotifications(...)미읽음 알림 건수를 취득
updateSeenNotifications(string $seenAt)읽음 처리를 갱신

AT Protocol 레코드 조작

메서드설명
createRecord(string $repo, string $collection, ...)레코드를 작성
getRecord(string $repo, string $collection, string $rkey, ...)레코드를 취득
listRecords(string $repo, string $collection, ...)레코드 목록을 취득
putRecord(string $repo, string $collection, string $rkey, ...)레코드를 갱신·작성
deleteRecord(string $repo, string $collection, string $rkey, ...)레코드를 삭제

피드 제너레이터·라벨러

메서드설명
publishFeedGenerator(BackedEnum|string $name, Generator $generator)피드 제너레이터를 공개
createThreadGate(string $post, ?array $allow)스레드 게이트를 작성
upsertLabelDefinitions(callable $callback)라벨 정의를 갱신
deleteLabelDefinitions()라벨 정의를 삭제
createLabels(RepoRef|StrongRef|array $subject, array $labels)라벨을 부여
deleteLabels(RepoRef|StrongRef|array $subject, array $labels)라벨을 삭제

자주 사용하는 단축키 예시

게시하기

use Revolution\Bluesky\Facades\Bluesky;

$response = Bluesky::login(
    identifier: config('bluesky.identifier'),
    password: config('bluesky.password'),
)->post('Hello Bluesky');

리플라이하기

리플라이는 Post::build()로 부모 게시의 StrongRef를 설정합니다. HasShortHand에는 전용 reply() 메서드가 없고, post()Post 객체를 전달합니다.
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\Types\StrongRef;

$parent = StrongRef::to(uri: 'at://did:plc:.../app.bsky.feed.post/...', cid: 'bafyrei...');

$reply = Post::create('리플라이 텍스트')
    ->reply(root: $parent, parent: $parent);

Bluesky::login(config('bluesky.identifier'), config('bluesky.password'))
    ->post($reply);

좋아요·리포스트

use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Types\StrongRef;

$ref = StrongRef::to(uri: 'at://did:plc:.../app.bsky.feed.post/...', cid: 'bafyrei...');

// 좋아요
Bluesky::withToken($session)->like($ref);

// 리포스트
Bluesky::withToken($session)->repost($ref);

프로필 갱신하기

upsertProfile()은 현재 프로필을 취득하고, 클로저 내에서 변경한 내용을 저장합니다. Profile 객체의 메서드를 사용해 displayName·description 등을 설정할 수 있습니다.
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Profile;

Bluesky::login(config('bluesky.identifier'), config('bluesky.password'))
    ->upsertProfile(function (Profile $profile) {
        $profile->displayName('새로운 표시 이름')
                ->description('프로필 설명문');
    });

셀프 라벨 설정하기

SelfLabels를 사용해 계정 전체에 대해 셀프 라벨을 설정할 수 있습니다.
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Profile;
use Revolution\Bluesky\Types\SelfLabels;

Bluesky::login(config('bluesky.identifier'), config('bluesky.password'))
    ->upsertProfile(function (Profile $profile) {
        $profile->labels(SelfLabels::make(['!no-unauthenticated']));
    });

임의의 API를 직접 호출하기

HasShortHand에 없는 메서드는 send() 또는 client()를 사용합니다.
use Revolution\Bluesky\Facades\Bluesky;

// send()로 임의의 XRPC 메서드를 호출
$response = Bluesky::withToken($session)
    ->send(
        api: 'app.bsky.actor.getProfiles',
        method: 'get',
        params: ['actors' => ['did:plc:...', 'did:plc:...']],
    );

// client()로 더 세밀한 조작
$response = Bluesky::withToken($session)
    ->client()
    ->bsky()
    ->getProfiles(actors: ['did:plc:...']);

Facade 경유와 직접 이용의 차이

Bluesky::post()app(Factory::class)->post()는 동일한 BlueskyManager 인스턴스에 대한 조작입니다.
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Contracts\Factory;

// Facade 경유(일반적인 사용법)
Bluesky::login(config('bluesky.identifier'), config('bluesky.password'))
    ->post('Hello');

// 컨테이너에서 직접 취득
$manager = app(Factory::class);
$manager->login(config('bluesky.identifier'), config('bluesky.password'))
        ->post('Hello');

// 타입 힌트로 DI
class MyService
{
    public function __construct(private Factory $bluesky) {}

    public function doPost(): void
    {
        $this->bluesky->login(
            config('bluesky.identifier'),
            config('bluesky.password'),
        )->post('Hello from DI');
    }
}
scoped 등록이므로 하나의 요청 내에서는 login() 이후의 세션 상태가 유지됩니다.

Conditionable과 Macroable

BlueskyManagerConditionableMacroable 트레이트도 사용합니다.

Conditionable: when() / unless()

use Revolution\Bluesky\Facades\Bluesky;

Bluesky::login(config('bluesky.identifier'), config('bluesky.password'))
    ->when(config('app.env') === 'production', function ($bluesky) {
        $bluesky->post('프로덕션 환경에서의 게시');
    });

Macroable: 커스텀 메서드 추가

macro()를 사용하면 애플리케이션 측에서 BlueskyManager에 메서드를 추가할 수 있습니다. AppServiceProviderboot() 안에서 정의하는 것이 일반적입니다.
use Revolution\Bluesky\Facades\Bluesky;

Bluesky::macro('postWithHashtag', function (string $text, string $tag) {
    /** @var \Revolution\Bluesky\BlueskyManager $this */
    return $this->post("{$text} #{$tag}");
});

// 사용 예시
Bluesky::login(config('bluesky.identifier'), config('bluesky.password'))
    ->postWithHashtag('Hello', 'laravel');

커스텀 Agent 삽입

withAgent()를 사용하면 임의의 Agent 구현을 직접 설정할 수 있습니다.
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Contracts\Agent;

// 커스텀 Agent를 구현하는 경우 Agent 인터페이스를 구현
class MyCustomAgent implements Agent
{
    // ...
}

Bluesky::withAgent(new MyCustomAgent());
일반적인 인증(login() / withToken())을 사용하면 자동으로 에이전트가 설정되므로, withAgent()를 직접 사용하는 경우는 주로 테스트 시입니다.

참고 링크

마지막 수정일 2026년 7월 13일