diff --git a/composer.lock b/composer.lock index 6ac357e..02ecbd8 100644 --- a/composer.lock +++ b/composer.lock @@ -225,16 +225,16 @@ }, { "name": "nesbot/carbon", - "version": "2.72.6", + "version": "2.73.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "1e9d50601e7035a4c61441a208cb5bed73e108c5" + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1e9d50601e7035a4c61441a208cb5bed73e108c5", - "reference": "1e9d50601e7035a4c61441a208cb5bed73e108c5", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075", "shasum": "" }, "require": { @@ -328,7 +328,7 @@ "type": "tidelift" } ], - "time": "2024-12-27T09:28:11+00:00" + "time": "2025-01-08T20:10:23+00:00" }, { "name": "psr/clock", @@ -1052,16 +1052,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -1100,7 +1100,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -1108,7 +1108,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "nikic/php-parser", @@ -1288,16 +1288,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.16", + "version": "1.12.23", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e0bb5cb78545aae631220735aa706eac633a6be9" + "reference": "29201e7a743a6ab36f91394eab51889a82631428" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0bb5cb78545aae631220735aa706eac633a6be9", - "reference": "e0bb5cb78545aae631220735aa706eac633a6be9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/29201e7a743a6ab36f91394eab51889a82631428", + "reference": "29201e7a743a6ab36f91394eab51889a82631428", "shasum": "" }, "require": { @@ -1342,7 +1342,7 @@ "type": "github" } ], - "time": "2025-01-21T14:50:05+00:00" + "time": "2025-03-23T14:57:32+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Contracts/DefinitionContract.php b/src/Contracts/DefinitionContract.php new file mode 100644 index 0000000..2f9889c --- /dev/null +++ b/src/Contracts/DefinitionContract.php @@ -0,0 +1,9 @@ + followers + */ +class KnownFollowers implements DefinitionContract +{ + use Castable; + use BaseObject; + + protected function casts(): array + { + return [ + 'followers' => FieldType::array(FieldType::object(ProfileViewBasic::class)) + ->maxLength(5) + ->minLength(0) + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.actor.defs#knownFollowers'; + } +} diff --git a/src/Lexicons/App/Bsky/Actor/Defs/ProfileAssociated.php b/src/Lexicons/App/Bsky/Actor/Defs/ProfileAssociated.php new file mode 100644 index 0000000..286c10b --- /dev/null +++ b/src/Lexicons/App/Bsky/Actor/Defs/ProfileAssociated.php @@ -0,0 +1,37 @@ + ProfileAssociatedChat::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.actor.defs#profileAssociated'; + } +} diff --git a/src/Lexicons/App/Bsky/Actor/Defs/ProfileAssociatedChat.php b/src/Lexicons/App/Bsky/Actor/Defs/ProfileAssociatedChat.php new file mode 100644 index 0000000..765f3dd --- /dev/null +++ b/src/Lexicons/App/Bsky/Actor/Defs/ProfileAssociatedChat.php @@ -0,0 +1,24 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.actor.defs#profileAssociatedChat'; + } +} diff --git a/src/Lexicons/App/Bsky/Actor/Defs/ProfileView.php b/src/Lexicons/App/Bsky/Actor/Defs/ProfileView.php new file mode 100644 index 0000000..50a1fb3 --- /dev/null +++ b/src/Lexicons/App/Bsky/Actor/Defs/ProfileView.php @@ -0,0 +1,49 @@ + ProfileAssociated::class, + 'indexedAt' => DatetimeObject::class, + 'createdAt' => DatetimeObject::class, + 'viewer' => ViewerState::class, + 'labels' => Label::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.actor.defs#profileView'; + } +} diff --git a/src/Lexicons/App/Bsky/Actor/Defs/ProfileViewBasic.php b/src/Lexicons/App/Bsky/Actor/Defs/ProfileViewBasic.php new file mode 100644 index 0000000..dffc80c --- /dev/null +++ b/src/Lexicons/App/Bsky/Actor/Defs/ProfileViewBasic.php @@ -0,0 +1,46 @@ + ProfileAssociated::class, + 'viewer' => ViewerState::class, + 'labels' => Label::class, + 'createdAt' => DatetimeObject::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.actor.defs#profileViewBasic'; + } +} diff --git a/src/Lexicons/App/Bsky/Actor/Defs/ViewerState.php b/src/Lexicons/App/Bsky/Actor/Defs/ViewerState.php new file mode 100644 index 0000000..af3eb7b --- /dev/null +++ b/src/Lexicons/App/Bsky/Actor/Defs/ViewerState.php @@ -0,0 +1,43 @@ + ListViewBasic::class, + 'blockingByList' => ListViewBasic::class, + 'knownFollowers' => KnownFollowers::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.actor.defs#viewerState'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Defs/AspectRatio.php b/src/Lexicons/App/Bsky/Embed/Defs/AspectRatio.php new file mode 100644 index 0000000..bfa5894 --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Defs/AspectRatio.php @@ -0,0 +1,25 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.embed.defs#aspectRatio'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/External/External.php b/src/Lexicons/App/Bsky/Embed/External/External.php new file mode 100644 index 0000000..8c41151 --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/External/External.php @@ -0,0 +1,27 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.embed.external#external'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/External/View.php b/src/Lexicons/App/Bsky/Embed/External/View.php new file mode 100644 index 0000000..25f6bf3 --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/External/View.php @@ -0,0 +1,33 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'external' => External::class + ]; + } + + public static function nsid(): string + { + return 'app.bsky.embed.external#view'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Images/View.php b/src/Lexicons/App/Bsky/Embed/Images/View.php new file mode 100644 index 0000000..63a3553 --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Images/View.php @@ -0,0 +1,48 @@ +value = $content; + $this->content = $content; + + parent::__construct( + $this->type(), + array_map(function (array $data) { + return @$this->item($data)->cast(); + }, $this->value['images']) + ); + } + + protected function item($data): ObjectContract + { + return new ViewImage($data); + } + + protected function type(): \Closure + { + return static fn ($value): bool => $value instanceof ViewImage; + } + + public function validate($value): bool + { + return parent::validate($value) && count($this->content['images']) <= self::MAX_LENGTH; + } + + public static function nsid(): string + { + return 'app.bsky.embed.images#view'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Images/ViewImage.php b/src/Lexicons/App/Bsky/Embed/Images/ViewImage.php new file mode 100644 index 0000000..7180a2c --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Images/ViewImage.php @@ -0,0 +1,37 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'aspectRatio' => AspectRatio::class, + ]; + } + + public static function nsid(): string + { + return 'app.bsky.embed.images#viewImage'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Record/View.php b/src/Lexicons/App/Bsky/Embed/Record/View.php new file mode 100644 index 0000000..9a80833 --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Record/View.php @@ -0,0 +1,47 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'record' => FieldType::union([ + ViewRecord::class, + ViewNotFound::class, + ViewBlocked::class, + ViewDetached::class, + GeneratorView::class, + ListView::class, + LabelerView::class, + StarterPackViewBasic::class, + ]) + ]; + } + + public static function nsid(): string + { + return 'app.bsky.embed.record#view'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Record/ViewBlocked.php b/src/Lexicons/App/Bsky/Embed/Record/ViewBlocked.php new file mode 100644 index 0000000..b1739fd --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Record/ViewBlocked.php @@ -0,0 +1,36 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'author' => BlockedAuthor::class, + ]; + } + + public static function nsid(): string + { + return 'app.bsky.embed.record#viewBlocked'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Record/ViewDetached.php b/src/Lexicons/App/Bsky/Embed/Record/ViewDetached.php new file mode 100644 index 0000000..dfff98d --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Record/ViewDetached.php @@ -0,0 +1,25 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.embed.record#viewDetached'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Record/ViewNotFound.php b/src/Lexicons/App/Bsky/Embed/Record/ViewNotFound.php new file mode 100644 index 0000000..1ed4007 --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Record/ViewNotFound.php @@ -0,0 +1,25 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.embed.record#viewNotFound'; + } +} \ No newline at end of file diff --git a/src/Lexicons/App/Bsky/Embed/Record/ViewRecord.php b/src/Lexicons/App/Bsky/Embed/Record/ViewRecord.php new file mode 100644 index 0000000..6b635ed --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Record/ViewRecord.php @@ -0,0 +1,62 @@ + embeds + * @method Carbon indexedAt + */ +class ViewRecord implements DefinitionContract +{ + use Castable; + use BaseObject; + + public function __construct($value) + { + $this->content = $value; + } + + protected function casts(): array + { + return [ + 'labels' => Label::class, + 'author' => ProfileViewBasic::class, + 'embeds' => FieldType::array(FieldType::union([ + ImagesView::class, + VideoView::class, + ExternalView::class, + RecordView::class, + RecordWithMediaView::class, + ])), + 'indexedAt' => DatetimeObject::class, + ]; + } + + public static function nsid(): string + { + return 'app.bsky.embed.record#viewRecord'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/RecordWithMedia/View.php b/src/Lexicons/App/Bsky/Embed/RecordWithMedia/View.php new file mode 100644 index 0000000..9a3c2bc --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/RecordWithMedia/View.php @@ -0,0 +1,43 @@ + ExternalView::class, + 'media' => FieldType::union([ + ImagesView::class, + VideoView::class, + ExternalView::class, + ]) + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.embed.recordWithMedia#view'; + } +} diff --git a/src/Lexicons/App/Bsky/Embed/Video/View.php b/src/Lexicons/App/Bsky/Embed/Video/View.php new file mode 100644 index 0000000..6bb23dc --- /dev/null +++ b/src/Lexicons/App/Bsky/Embed/Video/View.php @@ -0,0 +1,38 @@ + AspectRatio::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.embed.video#view'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/BlockedAuthor.php b/src/Lexicons/App/Bsky/Feed/Defs/BlockedAuthor.php new file mode 100644 index 0000000..ea48ce1 --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/BlockedAuthor.php @@ -0,0 +1,35 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'viewer' => ViewerState::class, + ]; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#blockedAuthor'; + } +} \ No newline at end of file diff --git a/src/Lexicons/App/Bsky/Feed/Defs/BlockedPost.php b/src/Lexicons/App/Bsky/Feed/Defs/BlockedPost.php new file mode 100644 index 0000000..e567d00 --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/BlockedPost.php @@ -0,0 +1,35 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'author' => BlockedAuthor::class, + ]; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#blockedPost'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/FeedViewPost.php b/src/Lexicons/App/Bsky/Feed/Defs/FeedViewPost.php new file mode 100644 index 0000000..5b767a7 --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/FeedViewPost.php @@ -0,0 +1,41 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'post' => PostView::class, + 'replyRef' => ReplyRef::class, + 'reason' => FieldType::union([ + ReasonRepost::class, + ReasonPin::class, + ]), + ]; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#feedViewPost'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/GeneratorView.php b/src/Lexicons/App/Bsky/Feed/Defs/GeneratorView.php new file mode 100644 index 0000000..61827ad --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/GeneratorView.php @@ -0,0 +1,55 @@ + ProfileView::class, + 'descriptionFacets' => FacetsObject::class, + 'labels' => Label::class, + 'viewer' => GeneratorViewerState::class, + 'indexedAt' => DatetimeObject::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#generatorView'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/GeneratorViewerState.php b/src/Lexicons/App/Bsky/Feed/Defs/GeneratorViewerState.php new file mode 100644 index 0000000..847b97f --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/GeneratorViewerState.php @@ -0,0 +1,24 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#generatorViewerState'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/NotFoundPost.php b/src/Lexicons/App/Bsky/Feed/Defs/NotFoundPost.php new file mode 100644 index 0000000..55d0fb3 --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/NotFoundPost.php @@ -0,0 +1,26 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#notFoundPost'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/PostView.php b/src/Lexicons/App/Bsky/Feed/Defs/PostView.php new file mode 100644 index 0000000..d6321c1 --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/PostView.php @@ -0,0 +1,67 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'author' => ProfileViewBasic::class, + 'indexedAt' => DatetimeObject::class, + 'embed' => FieldType::union([ + ImagesView::class, + VideoView::class, + ExternalView::class, + RecordView::class, + RecordWithMediaView::class, + ]), + 'viewer' => ViewerState::class, + 'labels' => Label::class, + 'threadgate' => ThreadgateView::class + ]; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#postView'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/ReasonPin.php b/src/Lexicons/App/Bsky/Feed/Defs/ReasonPin.php new file mode 100644 index 0000000..9ee3e9c --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/ReasonPin.php @@ -0,0 +1,22 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#reasonPin'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/ReasonRepost.php b/src/Lexicons/App/Bsky/Feed/Defs/ReasonRepost.php new file mode 100644 index 0000000..23aaa3a --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/ReasonRepost.php @@ -0,0 +1,25 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#reasonRepost'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/ReplyRef.php b/src/Lexicons/App/Bsky/Feed/Defs/ReplyRef.php new file mode 100644 index 0000000..46c4e43 --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/ReplyRef.php @@ -0,0 +1,47 @@ +content = $value; + } + + protected function casts(): array + { + return [ + 'root' => FieldType::union([ + PostView::class, + NotFoundPost::class, + BlockedPost::class, + ]), + 'parent' => FieldType::union([ + PostView::class, + NotFoundPost::class, + BlockedPost::class, + ]), + 'grandparentAuthor' => ProfileViewBasic::class, + ]; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#replyRef'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/ThreadgateView.php b/src/Lexicons/App/Bsky/Feed/Defs/ThreadgateView.php new file mode 100644 index 0000000..2bfc00d --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/ThreadgateView.php @@ -0,0 +1,37 @@ + ListViewBasic::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#threadgateView'; + } +} diff --git a/src/Lexicons/App/Bsky/Feed/Defs/ViewerState.php b/src/Lexicons/App/Bsky/Feed/Defs/ViewerState.php new file mode 100644 index 0000000..a6f3ffa --- /dev/null +++ b/src/Lexicons/App/Bsky/Feed/Defs/ViewerState.php @@ -0,0 +1,29 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.feed.defs#viewerState'; + } +} diff --git a/src/Lexicons/App/Bsky/Graph/Defs/ListView.php b/src/Lexicons/App/Bsky/Graph/Defs/ListView.php new file mode 100644 index 0000000..447eeb9 --- /dev/null +++ b/src/Lexicons/App/Bsky/Graph/Defs/ListView.php @@ -0,0 +1,53 @@ + ProfileView::class, + 'descriptionFacets' => FacetsObject::class, + 'labels' => Label::class, + 'viewer' => ListViewerState::class, + 'indexedAt' => DatetimeObject::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.graph.defs#listView'; + } +} diff --git a/src/Lexicons/App/Bsky/Graph/Defs/ListViewBasic.php b/src/Lexicons/App/Bsky/Graph/Defs/ListViewBasic.php new file mode 100644 index 0000000..e0136df --- /dev/null +++ b/src/Lexicons/App/Bsky/Graph/Defs/ListViewBasic.php @@ -0,0 +1,46 @@ + Label::class, + 'viewer' => ListViewerState::class, + 'indexedAt' => DatetimeObject::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.graph.defs#listViewBasic'; + } +} diff --git a/src/Lexicons/App/Bsky/Graph/Defs/ListViewerState.php b/src/Lexicons/App/Bsky/Graph/Defs/ListViewerState.php new file mode 100644 index 0000000..4fb2f95 --- /dev/null +++ b/src/Lexicons/App/Bsky/Graph/Defs/ListViewerState.php @@ -0,0 +1,25 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.graph.defs#listViewerState'; + } +} diff --git a/src/Lexicons/App/Bsky/Graph/Defs/StarterPackViewBasic.php b/src/Lexicons/App/Bsky/Graph/Defs/StarterPackViewBasic.php new file mode 100644 index 0000000..0c0a664 --- /dev/null +++ b/src/Lexicons/App/Bsky/Graph/Defs/StarterPackViewBasic.php @@ -0,0 +1,47 @@ + ProfileViewBasic::class, + 'labels' => Label::class, + 'indexedAt' => DatetimeObject::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.graph.defs#starterPackViewBasic'; + } +} diff --git a/src/Lexicons/App/Bsky/Labeler/Defs/LabelerView.php b/src/Lexicons/App/Bsky/Labeler/Defs/LabelerView.php new file mode 100644 index 0000000..a57aa0c --- /dev/null +++ b/src/Lexicons/App/Bsky/Labeler/Defs/LabelerView.php @@ -0,0 +1,46 @@ + ProfileView::class, + 'viewer' => LabelerViewerState::class, + 'indexedAt' => DatetimeObject::class, + 'labels' => Label::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.labeler.defs#labelerView'; + } +} diff --git a/src/Lexicons/App/Bsky/Labeler/Defs/LabelerViewerState.php b/src/Lexicons/App/Bsky/Labeler/Defs/LabelerViewerState.php new file mode 100644 index 0000000..63b10cb --- /dev/null +++ b/src/Lexicons/App/Bsky/Labeler/Defs/LabelerViewerState.php @@ -0,0 +1,24 @@ +content = $value; + } + + public static function nsid(): string + { + return 'app.bsky.labeler.defs#labelerViewerState'; + } +} diff --git a/src/Lexicons/Com/Atproto/Label/Defs/Label.php b/src/Lexicons/Com/Atproto/Label/Defs/Label.php new file mode 100644 index 0000000..ef3d7b2 --- /dev/null +++ b/src/Lexicons/Com/Atproto/Label/Defs/Label.php @@ -0,0 +1,44 @@ + DatetimeObject::class, + 'exp' => DatetimeObject::class, + ]; + } + + public function __construct($value) + { + $this->content = $value; + } + + public static function nsid(): string + { + return 'com.atproto.label.defs#label'; + } +} diff --git a/src/Responses/App/Bsky/Feed/GetTimelineResponse.php b/src/Responses/App/Bsky/Feed/GetTimelineResponse.php index 5d2306c..75bbbd6 100644 --- a/src/Responses/App/Bsky/Feed/GetTimelineResponse.php +++ b/src/Responses/App/Bsky/Feed/GetTimelineResponse.php @@ -3,24 +3,25 @@ namespace Atproto\Responses\App\Bsky\Feed; use Atproto\Contracts\Resources\ResponseContract; +use Atproto\FieldTypes\FieldType; +use Atproto\Lexicons\App\Bsky\Feed\Defs\FeedViewPost; use Atproto\Responses\BaseResponse; use Atproto\Responses\Objects\FeedObject; use Atproto\Traits\Castable; /** * @method string cursor() - * @method FeedObject feed() + * @method array feed() */ class GetTimelineResponse implements ResponseContract { use BaseResponse; use Castable; - protected function casts(): array { return [ - 'feed' => FeedObject::class + 'feed' => FieldType::array(FieldType::object(FeedViewPost::class)) ]; } } diff --git a/src/Responses/Objects/FeedItemObject.php b/src/Responses/Objects/FeedItemObject.php deleted file mode 100644 index 3c13c21..0000000 --- a/src/Responses/Objects/FeedItemObject.php +++ /dev/null @@ -1,26 +0,0 @@ -content = $content; - } - - protected function casts(): array - { - return [ - 'post' => PostObject::class, - 'reply' => ReplyObject::class, - 'reason' => ReasonObject::class, - ]; - } -} \ No newline at end of file diff --git a/src/Responses/Objects/FeedObject.php b/src/Responses/Objects/FeedObject.php deleted file mode 100644 index 02c9109..0000000 --- a/src/Responses/Objects/FeedObject.php +++ /dev/null @@ -1,26 +0,0 @@ - $value instanceof FeedItemObject; - } -} \ No newline at end of file diff --git a/src/Responses/Objects/ReasonObject.php b/src/Responses/Objects/ReasonObject.php deleted file mode 100644 index ea29937..0000000 --- a/src/Responses/Objects/ReasonObject.php +++ /dev/null @@ -1,21 +0,0 @@ - AuthorObject::class, - 'indexedAt' => DatetimeObject::class, - ]; - } -} \ No newline at end of file diff --git a/tests/Feature/Lexicons/App/Bsky/Feed/GetTimelineTest.php b/tests/Feature/Lexicons/App/Bsky/Feed/GetTimelineTest.php index b7a66d5..e72ed58 100644 --- a/tests/Feature/Lexicons/App/Bsky/Feed/GetTimelineTest.php +++ b/tests/Feature/Lexicons/App/Bsky/Feed/GetTimelineTest.php @@ -3,7 +3,9 @@ namespace Tests\Feature\Lexicons\App\Bsky\Feed; use Atproto\Client; -use Atproto\Responses\Objects\PostObject; +use Atproto\Exceptions\InvalidArgumentException; +use Atproto\Lexicons\App\Bsky\Embed\External\View; +use Atproto\Lexicons\App\Bsky\Feed\Defs\PostView; use PHPUnit\Framework\TestCase; class GetTimelineTest extends TestCase @@ -29,8 +31,44 @@ public function testGetTimeline(): void $this->assertNotEmpty($feed = $response->feed()); foreach($feed as $entry) { - $this->assertInstanceOf(PostObject::class, $post = $entry->post()); + $this->assertInstanceOf(PostView::class, $post = $entry->post()); $this->assertSame($client->authenticated()->handle(), $post->author()->handle()); } } + + public function testGetPostEmbeds(): void + { + $client = static::$client; + + $getTimeline = bskyFacade($client)->getTimeline()->limit(50); + $response = $getTimeline->send(); + + /** @var array $feed */ + $feed = $response->feed(); + + $this->assertIsIterable($feed); + + foreach($feed as $entry) { + /** @var PostView $post */ + $post = $entry->post(); + + if (! $post->has('embed')) { + continue; + } + + $embed = $post->resolve('embed'); + + if ($embed->resolve('$type') === View::nsid()) { + $embed = $embed->resolve('external'); + + $this->assertTrue($embed->has('uri')); + $this->assertTrue($embed->has('title')); + $this->assertTrue($embed->has('description')); + + $this->assertIsString($embed->resolve('uri')); + $this->assertIsString($embed->resolve('title')); + $this->assertIsString($embed->resolve('description')); + } + } + } } diff --git a/tests/Unit/Lexicons/App/Bsky/Actor/Defs/KnownFollowersTest.php b/tests/Unit/Lexicons/App/Bsky/Actor/Defs/KnownFollowersTest.php new file mode 100644 index 0000000..bbed0e3 --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Actor/Defs/KnownFollowersTest.php @@ -0,0 +1,35 @@ +assertSame( + 'app.bsky.actor.defs#knownFollowers', + KnownFollowers::nsid() + ); + + $this->assertSame( + 'app.bsky.actor.defs#knownFollowers', + (new KnownFollowers([]))->nsid() + ); + } + + public function test_followers_can_cast_data_correctly(): void + { + $data = ['followers' => [['did' => 'foo', 'handle' => 'bar']]]; + $followers = (new KnownFollowers($data))->followers(); + + $this->assertNotEmpty($followers); + $this->assertIsIterable($followers); + $this->assertInstanceOf(ProfileViewBasic::class, $followers[0]); + $this->assertSame($followers[0]->handle(), 'bar'); + } +} diff --git a/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewDetachedTest.php b/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewDetachedTest.php new file mode 100644 index 0000000..421b216 --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewDetachedTest.php @@ -0,0 +1,35 @@ +assertSame( + 'app.bsky.embed.record#viewDetached', + ViewDetached::nsid() + ); + + $this->assertSame( + 'app.bsky.embed.record#viewDetached', + (new ViewDetached([]))->nsid() + ); + } + + public function test_detached_casts_to_true(): void + { + $data = [ + '$type' => 'app.bsky.embed.record#viewDetached', + 'detached' => true + ]; + + $viewDetached = new ViewDetached($data); + + $this->assertTrue(is_bool($viewDetached->detached())); + $this->assertTrue($viewDetached->detached()); + } +} diff --git a/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewRecordTest.php b/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewRecordTest.php new file mode 100644 index 0000000..f2a2378 --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewRecordTest.php @@ -0,0 +1,97 @@ +assertSame( + 'app.bsky.embed.record#viewRecord', + ViewRecord::nsid() + ); + + $this->assertSame( + 'app.bsky.embed.record#viewRecord', + (new ViewRecord([]))->nsid() + ); + } + + public function test_scalar_and_object_fields_cast_correctly(): void + { + $now = Carbon::now()->toISOString(); + + $data = [ + 'uri' => 'at://foo/bar', + 'cid' => 'bafy123', + 'value' => 'Hello world', + 'replyCount' => 2, + 'repostCount' => 1, + 'likeCount' => 5, + 'quoteCount' => 0, + 'author' => ['did' => 'did:plc:abc', 'handle' => 'user.bsky.social'], + 'labels' => ['val' => 'test'], + 'indexedAt' => $now, + 'embeds' => [] + ]; + + $view = new ViewRecord($data); + + $this->assertEquals('at://foo/bar', $view->uri()); + $this->assertEquals('bafy123', $view->cid()); + $this->assertEquals('Hello world', $view->value()); + $this->assertEquals(2, $view->replyCount()); + $this->assertInstanceOf(ProfileViewBasic::class, $view->author()); + $this->assertInstanceOf(Label::class, $view->labels()); + $this->assertInstanceOf(Carbon::class, $view->indexedAt()); + } + + public function test_embeds_casts_correctly_to_multiple_view_types(): void + { + $data = [ + 'embeds' => [ + [ + '$type' => 'app.bsky.embed.images#view', + 'images' => [], + ], + [ + '$type' => 'app.bsky.embed.record#view', + 'record' => [ + '$type' => 'app.bsky.embed.record#viewRecord', + 'value' => 'test' + ] + ] + ] + ]; + + $view = new ViewRecord($data); + $embeds = $view->embeds(); + + $this->assertCount(2, $embeds); + $this->assertInstanceOf(ImagesView::class, $embeds[0]); + $this->assertInstanceOf(RecordView::class, $embeds[1]); + } + + public function test_invalid_embed_type_throws_exception(): void + { + $this->expectException(\InvalidArgumentException::class); + + $data = [ + 'embeds' => [ + [ + '$type' => 'unknown#badView' + ] + ] + ]; + + (new ViewRecord($data))->embeds(); + } +} diff --git a/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewTest.php b/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewTest.php new file mode 100644 index 0000000..5a8ebc0 --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Embed/Record/ViewTest.php @@ -0,0 +1,66 @@ +assertSame( + 'app.bsky.embed.record#view', + View::nsid() + ); + + $this->assertSame( + 'app.bsky.embed.record#view', + (new View([]))->nsid() + ); + } + + public function test_record_casts_to_view_record(): void + { + $data = [ + 'record' => [ + '$type' => 'app.bsky.embed.record#viewRecord', + 'data' => 'record-data' + ] + ]; + + $view = new View($data); + + $this->assertInstanceOf(ViewRecord::class, $view->record()); + } + + public function test_record_casts_to_list_view(): void + { + $data = [ + 'record' => [ + '$type' => 'app.bsky.graph.defs#listView', + 'data' => 'list-data' + ] + ]; + + $view = new View($data); + + $this->assertInstanceOf(ListView::class, $view->record()); + } + + public function test_record_cast_throws_on_invalid_type(): void + { + $this->expectException(\InvalidArgumentException::class); + + $data = [ + 'record' => [ + '$type' => 'unknown.namespace#bogusType', + 'value' => 'fail' + ] + ]; + + (new View($data))->record(); + } +} diff --git a/tests/Unit/Lexicons/App/Bsky/Embed/RecordWithMedia/ViewTest.php b/tests/Unit/Lexicons/App/Bsky/Embed/RecordWithMedia/ViewTest.php new file mode 100644 index 0000000..d10faa7 --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Embed/RecordWithMedia/ViewTest.php @@ -0,0 +1,84 @@ +assertSame( + 'app.bsky.embed.recordWithMedia#view', + View::nsid() + ); + + $this->assertSame( + 'app.bsky.embed.recordWithMedia#view', + (new View([]))->nsid() + ); + } + + public function test_record_casts_to_external_view(): void + { + $data = [ + 'record' => ['uri' => 'at://external/uri'], + 'media' => [ + '$type' => 'app.bsky.embed.external#view', + 'uri' => 'at://media' + ] + ]; + + $view = new View($data); + + $this->assertInstanceOf(ExternalView::class, $view->record()); + } + + public function test_media_casts_to_images_view(): void + { + $data = [ + 'record' => ['uri' => 'at://external/uri'], + 'media' => [ + '$type' => 'app.bsky.embed.images#view', + 'images' => [] + ] + ]; + + $view = new View($data); + + $this->assertInstanceOf(ImagesView::class, $view->media()); + } + + public function test_media_casts_to_video_view(): void + { + $data = [ + 'record' => ['uri' => 'at://external/uri'], + 'media' => [ + '$type' => 'app.bsky.embed.video#view', + 'video' => [] + ] + ]; + + $view = new View($data); + + $this->assertInstanceOf(VideoView::class, $view->media()); + } + + public function test_invalid_media_type_throws_exception(): void + { + $this->expectException(\InvalidArgumentException::class); + + $data = [ + 'record' => ['uri' => 'at://external/uri'], + 'media' => [ + '$type' => 'invalid.type#badView', + ] + ]; + + (new View($data))->media(); + } +} diff --git a/tests/Unit/Lexicons/App/Bsky/Feed/Defs/FeedViewPostTest.php b/tests/Unit/Lexicons/App/Bsky/Feed/Defs/FeedViewPostTest.php new file mode 100644 index 0000000..afd1dab --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Feed/Defs/FeedViewPostTest.php @@ -0,0 +1,83 @@ +assertSame( + 'app.bsky.feed.defs#feedViewPost', + FeedViewPost::nsid() + ); + + $this->assertSame( + 'app.bsky.feed.defs#feedViewPost', + (new FeedViewPost([]))->nsid() + ); + } + + public function test_casts_post_and_reply_ref_correctly(): void + { + $data = [ + 'post' => ['uri' => 'at://foo/post/1'], + 'replyRef' => ['root' => ['uri' => 'at://foo/post/1']] + ]; + + $feed = new FeedViewPost($data); + + $this->assertInstanceOf(PostView::class, $feed->post()); + $this->assertInstanceOf(ReplyRef::class, $feed->replyRef()); + } + + public function test_casts_reason_as_reason_repost(): void + { + $data = [ + 'post' => ['uri' => 'at://foo/post/1'], + 'reason' => [ + '$type' => 'app.bsky.feed.defs#reasonRepost', + 'by' => ['handle' => 'bob.bsky.social'] + ] + ]; + + $feed = new FeedViewPost($data); + + $this->assertInstanceOf(ReasonRepost::class, $feed->reason()); + } + + public function test_casts_reason_as_reason_pin(): void + { + $data = [ + 'post' => ['uri' => 'at://foo/post/1'], + 'reason' => [ + '$type' => 'app.bsky.feed.defs#reasonPin', + 'by' => ['handle' => 'alice.bsky.social'] + ] + ]; + + $feed = new FeedViewPost($data); + + $this->assertInstanceOf(ReasonPin::class, $feed->reason()); + } + + public function test_casts_reason_invalid_type_throws(): void + { + $this->expectException(\InvalidArgumentException::class); + + $data = [ + 'post' => ['uri' => 'at://foo/post/1'], + 'reason' => [ + '$type' => 'invalid.defs#unknownReason' + ] + ]; + + (new FeedViewPost($data))->reason(); + } +} diff --git a/tests/Unit/Lexicons/App/Bsky/Feed/Defs/PostViewTest.php b/tests/Unit/Lexicons/App/Bsky/Feed/Defs/PostViewTest.php new file mode 100644 index 0000000..72355e7 --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Feed/Defs/PostViewTest.php @@ -0,0 +1,113 @@ +assertSame( + 'app.bsky.feed.defs#postView', + PostView::nsid() + ); + + $this->assertSame( + 'app.bsky.feed.defs#postView', + (new PostView([]))->nsid() + ); + } + + public function test_casts_basic_fields_correctly(): void + { + $now = Carbon::now()->toISOString(); + + $data = [ + 'uri' => 'at://foo/post/1', + 'cid' => 'bafy123', + 'record' => 'some-record-ref', + 'replyCount' => 3, + 'repostCount' => 1, + 'likeCount' => 7, + 'quoteCount' => 2, + 'indexedAt' => $now, + 'author' => ['handle' => 'john.bsky.social'], + 'viewer' => ['muted' => false], + 'labels' => ['val' => 'tag'], + 'threadgate' => ['uri' => 'at://foo/thread'] + ]; + + $post = new PostView($data); + + $this->assertEquals('at://foo/post/1', $post->uri()); + $this->assertEquals('bafy123', $post->cid()); + $this->assertEquals('some-record-ref', $post->record()); + $this->assertEquals(3, $post->replyCount()); + $this->assertEquals(1, $post->repostCount()); + $this->assertEquals(7, $post->likeCount()); + $this->assertEquals(2, $post->quoteCount()); + + $this->assertInstanceOf(ProfileViewBasic::class, $post->author()); + $this->assertInstanceOf(ViewerState::class, $post->viewer()); + $this->assertInstanceOf(Label::class, $post->labels()); + $this->assertInstanceOf(ThreadgateView::class, $post->threadgate()); + $this->assertInstanceOf(Carbon::class, $post->indexedAt()); + } + + public function test_embed_casts_to_images_view(): void + { + $data = [ + 'embed' => [ + '$type' => 'app.bsky.embed.images#view', + 'images' => [], + ] + ]; + + $post = new PostView($data); + + $this->assertInstanceOf(ImagesView::class, $post->embed()); + } + + public function test_embed_casts_to_record_with_media(): void + { + $data = [ + 'embed' => [ + '$type' => 'app.bsky.embed.recordWithMedia#view', + 'record' => ['uri' => 'at://foo'], + 'media' => [ + '$type' => 'app.bsky.embed.external#view', + 'uri' => 'at://bar' + ] + ] + ]; + + $post = new PostView($data); + + $this->assertInstanceOf(RecordWithMediaView::class, $post->embed()); + } + + public function test_invalid_embed_type_throws_exception(): void + { + $this->expectException(\InvalidArgumentException::class); + + $data = [ + 'embed' => [ + '$type' => 'app.unknown#invalid', + ] + ]; + + (new PostView($data))->embed(); + } +} diff --git a/tests/Unit/Lexicons/App/Bsky/Feed/Defs/ReplyRefTest.php b/tests/Unit/Lexicons/App/Bsky/Feed/Defs/ReplyRefTest.php new file mode 100644 index 0000000..6cb0e32 --- /dev/null +++ b/tests/Unit/Lexicons/App/Bsky/Feed/Defs/ReplyRefTest.php @@ -0,0 +1,82 @@ +assertSame( + 'app.bsky.feed.defs#replyRef', + ReplyRef::nsid() + ); + + $this->assertSame( + 'app.bsky.feed.defs#replyRef', + (new ReplyRef([]))->nsid() + ); + } + + public function test_casts_root_as_post_view(): void + { + $data = [ + 'root' => [ + '$type' => 'app.bsky.feed.defs#postView', + 'uri' => 'at://post' + ] + ]; + + $ref = new ReplyRef($data); + + $this->assertInstanceOf(PostView::class, $ref->root()); + } + + public function test_casts_parent_as_blocked_post(): void + { + $data = [ + 'parent' => [ + '$type' => 'app.bsky.feed.defs#blockedPost', + 'uri' => 'at://parent' + ] + ]; + + $ref = new ReplyRef($data); + + $this->assertInstanceOf(BlockedPost::class, $ref->parent()); + } + + public function test_casts_grandparent_author_correctly(): void + { + $data = [ + 'grandparentAuthor' => [ + 'handle' => 'gma.bsky.social', + 'did' => 'did:plc:abc123' + ] + ]; + + $ref = new ReplyRef($data); + + $this->assertInstanceOf(ProfileViewBasic::class, $ref->grandparentAuthor()); + $this->assertEquals('gma.bsky.social', $ref->grandparentAuthor()->handle()); + } + + public function test_invalid_union_type_throws(): void + { + $this->expectException(\InvalidArgumentException::class); + + $data = [ + 'root' => [ + '$type' => 'unknown#badType' + ] + ]; + + (new ReplyRef($data))->root(); + } +}