Bluesky Facade 是 BlueskyManager 的一层薄封装。BlueskyManager 负责管理多个 Agent,而 HasShortHand trait 集中提供了一批常用方法的快捷方式。
BlueskyServiceProvider 会将 Factory::class 以作用域单例(scoped singleton,每次请求一个实例)的形式注册为 BlueskyManager。
// 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 trait 提供的方法区分开来,有助于理解内部结构。
| 方法 | 说明 |
|---|
login(string $identifier, string $password, ?string $service = null) | 使用 App Password 认证并设置 LegacyAgent |
withToken(?AbstractSession $token) | 传入 OAuthSession 或 LegacySession 来设置 Agent |
check(): bool | 判断是否已认证(同时校验 token 是否有效) |
refreshSession() | 刷新 token |
logout() | 清除 Agent |
Agent 操作
| 方法 | 说明 |
|---|
agent(): ?Agent | 获取当前的 Agent |
withAgent(?Agent $agent) | 直接设置 Agent |
assertDid(): string | 返回已认证的 DID。未认证时抛出异常 |
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(): Identity | 获取 DID 解析等身份服务 |
pds(): PDS | 获取 PDS 信息服务 |
entryway(?string $path): string | 返回服务 URL(例如 bsky.social) |
publicEndpoint(): string | 返回公开端点 URL |
HasShortHand trait
HasShortHand trait 把 AT Protocol 的底层 API 包装成了对 PHP 更友好的方法。只要在 BlueskyManager 中写上 use HasShortHand;,这些方法就可以直接通过 Bluesky:: 调用。
// BlueskyManager.php
class BlueskyManager implements Factory
{
use Conditionable;
use HasShortHand;
use Macroable;
// ...
}
把 HasShortHand 拆成独立 trait,是为了便于测试和自定义。这样既能保持 BlueskyManager 代码简洁,又能提供丰富的 API 快捷方式。
发帖 / feed
| 方法 | 说明 |
|---|
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, ...) | 获取指定账号的 feed |
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) | 将 handle 解析为 DID |
| 方法 | 说明 |
|---|
uploadBlob(StreamInterface|string $data, string $type) | 上传图片等 blob |
uploadVideo(StreamInterface|string $data, string $type) | 上传视频 |
getJobStatus(string $jobId) | 查看视频上传的任务状态 |
getUploadLimits() | 获取上传限制 |
| 方法 | 说明 |
|---|
listNotifications(...) | 获取通知列表 |
countUnreadNotifications(...) | 获取未读通知数 |
updateSeenNotifications(string $seenAt) | 更新已读状态 |
AT Protocol 记录操作
| 方法 | 说明 |
|---|
createRecord(string $repo, string $collection, ...) | 创建 record |
getRecord(string $repo, string $collection, string $rkey, ...) | 获取 record |
listRecords(string $repo, string $collection, ...) | 获取 record 列表 |
putRecord(string $repo, string $collection, string $rkey, ...) | 更新或创建 record |
deleteRecord(string $repo, string $collection, string $rkey, ...) | 删除 record |
Feed generator 与 labeler
| 方法 | 说明 |
|---|
publishFeedGenerator(BackedEnum|string $name, Generator $generator) | 发布 feed generator |
createThreadGate(string $post, ?array $allow) | 创建 thread gate |
upsertLabelDefinitions(callable $callback) | 更新 label 定义 |
deleteLabelDefinitions() | 删除 label 定义 |
createLabels(RepoRef|StrongRef|array $subject, array $labels) | 添加 label |
deleteLabels(RepoRef|StrongRef|array $subject, array $labels) | 删除 label |
常用快捷方式示例
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('个人简介');
});
设置 self label
通过 SelfLabels,可以对整个账号设置 self label。
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() 后的 session 状态会被保留。
Conditionable 与 Macroable
BlueskyManager 也使用了 Conditionable 和 Macroable trait。
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 添加方法。通常写在 AppServiceProvider 的 boot() 中。
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())会自动设置 agent,所以直接使用 withAgent() 的场合主要出现在测试中。
参考链接