+3
-5
composer.json
+3
-5
composer.json
···
45
45
}
46
46
},
47
47
"scripts": {
48
-
"test": "vendor/bin/pest",
49
-
"test-coverage": "vendor/bin/pest --coverage",
48
+
"test": "vendor/bin/phpunit",
49
+
"test-coverage": "vendor/bin/phpunit --coverage-html coverage",
50
50
"format": "vendor/bin/php-cs-fixer fix"
51
51
},
52
52
"extra": {
···
62
62
"minimum-stability": "dev",
63
63
"prefer-stable": true,
64
64
"config": {
65
-
"allow-plugins": {
66
-
"pestphp/pest-plugin": false
67
-
}
65
+
"sort-packages": true
68
66
}
69
67
}
+41
-38
docs/extensions.md
+41
-38
docs/extensions.md
···
14
14
| `AtpClient::hasDomainExtension($domain, $name)` | Check if a request client extension is registered |
15
15
| `AtpClient::flushExtensions()` | Clear all extensions (useful for testing) |
16
16
17
-
The same methods are available on `AtpPublicClient` for unauthenticated extensions.
18
-
19
17
### Extension Types
20
18
21
19
| Type | Access Pattern | Use Case |
···
30
28
```bash
31
29
# Create a domain client extension
32
30
php artisan make:atp-client AnalyticsClient
33
-
34
-
# Create a public domain client extension
35
-
php artisan make:atp-client DiscoverClient --public
36
31
37
32
# Create a request client extension for an existing domain
38
33
php artisan make:atp-request MetricsClient --domain=bsky
39
-
40
-
# Create a public request client extension
41
-
php artisan make:atp-request TrendingClient --domain=bsky --public
42
34
```
43
35
44
36
The generated files are placed in configurable directories. You can customize these paths in `config/client.php`:
···
46
38
```php
47
39
'generators' => [
48
40
'client_path' => 'app/Services/Clients',
49
-
'client_public_path' => 'app/Services/Clients/Public',
50
41
'request_path' => 'app/Services/Clients/Requests',
51
-
'request_public_path' => 'app/Services/Clients/Public/Requests',
52
42
],
53
43
```
54
44
···
225
215
$authorMetrics = $client->bsky->metrics->getAuthorMetrics('someone.bsky.social');
226
216
```
227
217
228
-
## Public Client Extensions
218
+
## Public vs Authenticated Mode
229
219
230
-
The `AtpPublicClient` supports the same extension system for unauthenticated API access:
220
+
The `AtpClient` class works in both public and authenticated modes. Both `Atp::public()` and `Atp::as()` return the same `AtpClient` class:
231
221
232
222
```php
233
-
use SocialDept\AtpClient\Client\Public\AtpPublicClient;
223
+
// Public mode - no authentication
224
+
$publicClient = Atp::public('https://public.api.bsky.app');
225
+
$publicClient->bsky->actor->getProfile('someone.bsky.social');
234
226
235
-
// Domain client extension
236
-
AtpPublicClient::extend('discover', fn($atp) => new DiscoverClient($atp));
237
-
238
-
// Request client extension on existing domain
239
-
AtpPublicClient::extendDomain('bsky', 'trending', fn($bsky) => new TrendingClient($bsky));
227
+
// Authenticated mode - with session
228
+
$authClient = Atp::as('did:plc:xxx');
229
+
$authClient->bsky->actor->getProfile('someone.bsky.social');
240
230
```
241
231
242
-
For public request clients, extend `PublicRequest` instead of `Request`:
243
-
244
-
```php
245
-
<?php
246
-
247
-
namespace App\Atp;
248
-
249
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
250
-
251
-
class TrendingPublicClient extends PublicRequest
252
-
{
253
-
public function getPopularFeeds(int $limit = 10): array
254
-
{
255
-
return $this->atp->bsky->feed->getPopularFeedGenerators($limit)->feeds;
256
-
}
257
-
}
258
-
```
232
+
Extensions registered on `AtpClient` work in both modes. The underlying HTTP layer automatically handles authentication based on whether a session is present.
259
233
260
234
## Registering Multiple Extensions
261
235
···
272
246
AtpClient::extendDomain('bsky', 'metrics', fn($bsky) => new BskyMetricsClient($bsky));
273
247
AtpClient::extendDomain('bsky', 'lists', fn($bsky) => new BskyListsClient($bsky));
274
248
AtpClient::extendDomain('atproto', 'backup', fn($atproto) => new RepoBackupClient($atproto));
275
-
276
-
// Public client extensions
277
-
AtpPublicClient::extend('discover', fn($atp) => new DiscoverClient($atp));
278
249
}
279
250
```
280
251
···
451
422
}
452
423
}
453
424
```
425
+
426
+
### Documenting Scope Requirements
427
+
428
+
Use the `#[ScopedEndpoint]` and `#[PublicEndpoint]` attributes to document the authentication requirements of your extension methods:
429
+
430
+
```php
431
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
432
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
433
+
use SocialDept\AtpClient\Client\Requests\Request;
434
+
use SocialDept\AtpClient\Enums\Scope;
435
+
436
+
class BskyMetricsClient extends Request
437
+
{
438
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getTimeline')]
439
+
public function getTimelineMetrics(): array
440
+
{
441
+
$timeline = $this->atp->bsky->feed->getTimeline();
442
+
// Process and return metrics...
443
+
}
444
+
445
+
#[PublicEndpoint]
446
+
public function getPublicPostMetrics(string $uri): array
447
+
{
448
+
$thread = $this->atp->bsky->feed->getPostThread($uri);
449
+
// Process and return metrics...
450
+
}
451
+
}
452
+
```
453
+
454
+
> **Note:** These attributes currently serve as documentation only. Runtime scope enforcement will be implemented in a future release. Using them correctly now ensures forward compatibility.
455
+
456
+
Methods with `#[ScopedEndpoint]` indicate they require authentication, while methods with `#[PublicEndpoint]` work without authentication. See [scopes.md](scopes.md) for full documentation on scope handling.
454
457
455
458
## Available Domains
456
459
+408
docs/scopes.md
+408
docs/scopes.md
···
1
+
# OAuth Scopes
2
+
3
+
The AT Protocol uses OAuth scopes to control what actions an application can perform on behalf of a user. AtpClient provides attributes for documenting scope requirements on endpoints.
4
+
5
+
> **Note:** The `#[ScopedEndpoint]` and `#[PublicEndpoint]` attributes currently serve as documentation only. Runtime scope validation and enforcement will be implemented in a future release. Using these attributes correctly now ensures forward compatibility.
6
+
7
+
## Quick Reference
8
+
9
+
### Scope Enum
10
+
11
+
```php
12
+
use SocialDept\AtpClient\Enums\Scope;
13
+
14
+
// Transition scopes (current AT Protocol scopes)
15
+
Scope::Atproto // 'atproto' - Full access
16
+
Scope::TransitionGeneric // 'transition:generic' - General API access
17
+
Scope::TransitionEmail // 'transition:email' - Email access
18
+
Scope::TransitionChat // 'transition:chat.bsky' - Chat access
19
+
20
+
// Granular scope builders (future AT Protocol scopes)
21
+
Scope::repo('app.bsky.feed.post', ['create', 'delete']) // Record operations
22
+
Scope::rpc('app.bsky.feed.getTimeline') // RPC endpoint access
23
+
Scope::blob('image/*') // Blob upload access
24
+
Scope::account('email') // Account attribute access
25
+
Scope::identity('handle') // Identity attribute access
26
+
```
27
+
28
+
### ScopedEndpoint Attribute
29
+
30
+
```php
31
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
32
+
use SocialDept\AtpClient\Enums\Scope;
33
+
34
+
#[ScopedEndpoint(Scope::TransitionGeneric)]
35
+
public function getTimeline(): GetTimelineResponse
36
+
{
37
+
// Method implementation
38
+
}
39
+
```
40
+
41
+
## Understanding AT Protocol Scopes
42
+
43
+
### Current Transition Scopes
44
+
45
+
The AT Protocol is currently in a transition period where broad "transition scopes" are used:
46
+
47
+
| Scope | Description |
48
+
|-------|-------------|
49
+
| `atproto` | Full access to the AT Protocol |
50
+
| `transition:generic` | General API access for most operations |
51
+
| `transition:email` | Access to email-related operations |
52
+
| `transition:chat.bsky` | Access to Bluesky chat features |
53
+
54
+
### Future Granular Scopes
55
+
56
+
The AT Protocol is moving toward granular scopes that provide fine-grained access control:
57
+
58
+
```php
59
+
// Record operations
60
+
'repo:app.bsky.feed.post' // All operations on posts
61
+
'repo:app.bsky.feed.post?action=create' // Only create posts
62
+
'repo:app.bsky.feed.like?action=create&action=delete' // Create or delete likes
63
+
'repo:*' // All collections, all actions
64
+
65
+
// RPC endpoint access
66
+
'rpc:app.bsky.feed.getTimeline' // Access to timeline endpoint
67
+
'rpc:app.bsky.feed.*' // All feed endpoints
68
+
69
+
// Blob operations
70
+
'blob:image/*' // Upload images
71
+
'blob:*/*' // Upload any blob type
72
+
73
+
// Account and identity
74
+
'account:email' // Access email
75
+
'identity:handle' // Manage handle
76
+
```
77
+
78
+
## The ScopedEndpoint Attribute
79
+
80
+
The `#[ScopedEndpoint]` attribute documents scope requirements on methods that require authentication.
81
+
82
+
### Basic Usage
83
+
84
+
```php
85
+
<?php
86
+
87
+
namespace App\Atp;
88
+
89
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
90
+
use SocialDept\AtpClient\Client\Requests\Request;
91
+
use SocialDept\AtpClient\Enums\Scope;
92
+
93
+
class CustomClient extends Request
94
+
{
95
+
#[ScopedEndpoint(Scope::TransitionGeneric)]
96
+
public function getTimeline(): array
97
+
{
98
+
return $this->atp->client->get('app.bsky.feed.getTimeline')->json();
99
+
}
100
+
}
101
+
```
102
+
103
+
### With Granular Scope
104
+
105
+
Document the future granular scope that will replace the transition scope:
106
+
107
+
```php
108
+
#[ScopedEndpoint(
109
+
Scope::TransitionGeneric,
110
+
granular: 'rpc:app.bsky.feed.getTimeline'
111
+
)]
112
+
public function getTimeline(): GetTimelineResponse
113
+
{
114
+
// ...
115
+
}
116
+
```
117
+
118
+
### With Description
119
+
120
+
Add a human-readable description for documentation:
121
+
122
+
```php
123
+
#[ScopedEndpoint(
124
+
Scope::TransitionGeneric,
125
+
granular: 'rpc:app.bsky.feed.getTimeline',
126
+
description: 'Access to the user\'s home timeline'
127
+
)]
128
+
public function getTimeline(): GetTimelineResponse
129
+
{
130
+
// ...
131
+
}
132
+
```
133
+
134
+
### Multiple Scopes (AND Logic)
135
+
136
+
When a method requires multiple scopes, all must be present:
137
+
138
+
```php
139
+
#[ScopedEndpoint([Scope::TransitionGeneric, Scope::TransitionEmail])]
140
+
public function getEmailPreferences(): array
141
+
{
142
+
// Requires BOTH scopes
143
+
}
144
+
```
145
+
146
+
### Multiple Attributes (OR Logic)
147
+
148
+
Use multiple attributes for alternative scope requirements:
149
+
150
+
```php
151
+
#[ScopedEndpoint(Scope::Atproto)]
152
+
#[ScopedEndpoint(Scope::TransitionGeneric)]
153
+
public function getProfile(string $actor): ProfileViewDetailed
154
+
{
155
+
// Either scope satisfies the requirement
156
+
}
157
+
```
158
+
159
+
## Scope Enforcement (Planned)
160
+
161
+
> **Coming Soon:** Runtime scope enforcement is not yet implemented. The following documentation describes planned functionality for a future release.
162
+
163
+
### Configuration
164
+
165
+
Configure scope enforcement in `config/client.php` or via environment variables:
166
+
167
+
```php
168
+
'scope_enforcement' => ScopeEnforcementLevel::Permissive,
169
+
```
170
+
171
+
| Level | Behavior |
172
+
|-------|----------|
173
+
| `Strict` | Throws `MissingScopeException` if required scopes are missing |
174
+
| `Permissive` | Logs a warning but attempts the request anyway |
175
+
176
+
Set via environment variable:
177
+
178
+
```env
179
+
ATP_SCOPE_ENFORCEMENT=strict
180
+
```
181
+
182
+
### Programmatic Scope Checking
183
+
184
+
Check scopes programmatically using the `ScopeChecker`:
185
+
186
+
```php
187
+
use SocialDept\AtpClient\Auth\ScopeChecker;
188
+
use SocialDept\AtpClient\Facades\Atp;
189
+
190
+
$checker = app(ScopeChecker::class);
191
+
$session = Atp::as($did)->client->session();
192
+
193
+
// Check if session has a scope
194
+
if ($checker->hasScope($session, Scope::TransitionGeneric)) {
195
+
// Session has the scope
196
+
}
197
+
198
+
// Check multiple scopes
199
+
if ($checker->check($session, [Scope::TransitionGeneric, Scope::TransitionEmail])) {
200
+
// Session has ALL required scopes
201
+
}
202
+
203
+
// Check and fail if missing (respects enforcement level)
204
+
$checker->checkOrFail($session, [Scope::TransitionGeneric]);
205
+
206
+
// Check repo scope for specific action
207
+
if ($checker->checkRepoScope($session, 'app.bsky.feed.post', 'create')) {
208
+
// Can create posts
209
+
}
210
+
```
211
+
212
+
### Granular Pattern Matching
213
+
214
+
The scope checker supports wildcard patterns:
215
+
216
+
```php
217
+
// Check if session can access any feed endpoint
218
+
$checker->matchesGranular($session, 'rpc:app.bsky.feed.*');
219
+
220
+
// Check if session can upload images
221
+
$checker->matchesGranular($session, 'blob:image/*');
222
+
223
+
// Check if session has any repo access
224
+
$checker->matchesGranular($session, 'repo:*');
225
+
```
226
+
227
+
## Route Middleware (Planned)
228
+
229
+
> **Coming Soon:** Route middleware is not yet implemented. The following documentation describes planned functionality for a future release.
230
+
231
+
Protect Laravel routes based on ATP session scopes:
232
+
233
+
```php
234
+
use Illuminate\Support\Facades\Route;
235
+
236
+
// Single scope
237
+
Route::get('/timeline', TimelineController::class)
238
+
->middleware('atp.scope:transition:generic');
239
+
240
+
// Multiple scopes (AND logic)
241
+
Route::get('/email-settings', EmailSettingsController::class)
242
+
->middleware('atp.scope:transition:generic,transition:email');
243
+
```
244
+
245
+
### Middleware Configuration
246
+
247
+
Configure middleware behavior in `config/client.php`:
248
+
249
+
```php
250
+
'scope_authorization' => [
251
+
// What to do when scope check fails
252
+
'failure_action' => ScopeAuthorizationFailure::Abort, // abort, redirect, or exception
253
+
254
+
// Where to redirect (when failure_action is 'redirect')
255
+
'redirect_to' => '/login',
256
+
],
257
+
```
258
+
259
+
| Failure Action | Behavior |
260
+
|----------------|----------|
261
+
| `Abort` | Returns 403 Forbidden response |
262
+
| `Redirect` | Redirects to configured URL |
263
+
| `Exception` | Throws `ScopeAuthorizationException` |
264
+
265
+
Set via environment variables:
266
+
267
+
```env
268
+
ATP_SCOPE_FAILURE_ACTION=redirect
269
+
ATP_SCOPE_REDIRECT=/auth/login
270
+
```
271
+
272
+
### User Model Integration
273
+
274
+
For the middleware to work, your User model must implement `HasAtpSession`:
275
+
276
+
```php
277
+
<?php
278
+
279
+
namespace App\Models;
280
+
281
+
use Illuminate\Foundation\Auth\User as Authenticatable;
282
+
use SocialDept\AtpClient\Contracts\HasAtpSession;
283
+
284
+
class User extends Authenticatable implements HasAtpSession
285
+
{
286
+
public function getAtpDid(): ?string
287
+
{
288
+
return $this->atp_did;
289
+
}
290
+
}
291
+
```
292
+
293
+
## Public Mode and Scopes
294
+
295
+
Methods marked with `#[PublicEndpoint]` can be called without authentication using `Atp::public()`:
296
+
297
+
```php
298
+
// Public mode - no authentication required
299
+
$client = Atp::public('https://public.api.bsky.app');
300
+
$client->bsky->actor->getProfile('someone.bsky.social'); // Works without auth
301
+
302
+
// Authenticated mode - for endpoints requiring scopes
303
+
$client = Atp::as($did);
304
+
$client->bsky->feed->getTimeline(); // Requires transition:generic scope
305
+
```
306
+
307
+
Methods with `#[PublicEndpoint]` work in both modes, while methods with `#[ScopedEndpoint]` require authentication.
308
+
309
+
## Exception Handling (Planned)
310
+
311
+
> **Coming Soon:** These exceptions will be thrown when scope enforcement is implemented in a future release.
312
+
313
+
### MissingScopeException
314
+
315
+
Will be thrown when required scopes are missing and enforcement is strict:
316
+
317
+
```php
318
+
use SocialDept\AtpClient\Exceptions\MissingScopeException;
319
+
320
+
try {
321
+
$timeline = $client->bsky->feed->getTimeline();
322
+
} catch (MissingScopeException $e) {
323
+
$missing = $e->getMissingScopes(); // Scopes that are missing
324
+
$granted = $e->getGrantedScopes(); // Scopes the session has
325
+
326
+
// Handle missing scope
327
+
}
328
+
```
329
+
330
+
### ScopeAuthorizationException
331
+
332
+
Will be thrown by middleware when route access is denied:
333
+
334
+
```php
335
+
use SocialDept\AtpClient\Exceptions\ScopeAuthorizationException;
336
+
337
+
try {
338
+
// Route protected by atp.scope middleware
339
+
} catch (ScopeAuthorizationException $e) {
340
+
$required = $e->getRequiredScopes();
341
+
$granted = $e->getGrantedScopes();
342
+
$message = $e->getMessage();
343
+
}
344
+
```
345
+
346
+
## Best Practices
347
+
348
+
### 1. Document All Scope Requirements
349
+
350
+
Always add `#[ScopedEndpoint]` to methods that require authentication:
351
+
352
+
```php
353
+
#[ScopedEndpoint(
354
+
Scope::TransitionGeneric,
355
+
granular: 'rpc:app.bsky.feed.getTimeline',
356
+
description: 'Fetches the authenticated user\'s home timeline'
357
+
)]
358
+
public function getTimeline(): GetTimelineResponse
359
+
```
360
+
361
+
### 2. Use the Scope Enum
362
+
363
+
Prefer the `Scope` enum over string literals for type safety:
364
+
365
+
```php
366
+
// Good
367
+
#[ScopedEndpoint(Scope::TransitionGeneric)]
368
+
369
+
// Avoid
370
+
#[ScopedEndpoint('transition:generic')]
371
+
```
372
+
373
+
### 3. Request Minimal Scopes
374
+
375
+
When implementing OAuth, request only the scopes your application needs:
376
+
377
+
```php
378
+
$authUrl = Atp::oauth()->getAuthorizationUrl([
379
+
'scope' => 'atproto transition:generic',
380
+
]);
381
+
```
382
+
383
+
### 4. Handle Missing Scopes Gracefully
384
+
385
+
Check for scope availability before attempting operations:
386
+
387
+
```php
388
+
$checker = app(ScopeChecker::class);
389
+
$session = $client->client->session();
390
+
391
+
if ($checker->hasScope($session, Scope::TransitionChat)) {
392
+
$conversations = $client->chat->getConversations();
393
+
} else {
394
+
// Inform user they need to re-authorize with chat scope
395
+
}
396
+
```
397
+
398
+
### 5. Use Permissive Mode in Development
399
+
400
+
Start with permissive enforcement during development, then switch to strict for production:
401
+
402
+
```env
403
+
# .env.local
404
+
ATP_SCOPE_ENFORCEMENT=permissive
405
+
406
+
# .env.production
407
+
ATP_SCOPE_ENFORCEMENT=strict
408
+
```
+14
-4
src/AtpClient.php
+14
-4
src/AtpClient.php
···
13
13
class AtpClient
14
14
{
15
15
use HasExtensions;
16
+
16
17
/**
17
18
* Raw API communication/networking class
18
19
*/
···
39
40
public OzoneClient $ozone;
40
41
41
42
public function __construct(
42
-
SessionManager $sessions,
43
-
string $did,
43
+
?SessionManager $sessions = null,
44
+
?string $did = null,
45
+
?string $serviceUrl = null,
44
46
) {
45
-
// Load the network client
46
-
$this->client = new Client($this, $sessions, $did);
47
+
// Load the network client (supports both public and authenticated modes)
48
+
$this->client = new Client($this, $sessions, $did, $serviceUrl);
47
49
48
50
// Load all function collections
49
51
$this->bsky = new BskyClient($this);
50
52
$this->atproto = new AtprotoClient($this);
51
53
$this->chat = new ChatClient($this);
52
54
$this->ozone = new OzoneClient($this);
55
+
}
56
+
57
+
/**
58
+
* Check if client is in public mode (no authentication).
59
+
*/
60
+
public function isPublicMode(): bool
61
+
{
62
+
return $this->client->isPublicMode();
53
63
}
54
64
}
+5
-4
src/AtpClientServiceProvider.php
+5
-4
src/AtpClientServiceProvider.php
···
25
25
use SocialDept\AtpClient\Http\DPoPClient;
26
26
use SocialDept\AtpClient\Session\SessionManager;
27
27
use SocialDept\AtpClient\Storage\EncryptedFileKeyStore;
28
-
use SocialDept\AtpClient\Client\Public\AtpPublicClient;
29
28
30
29
class AtpClientServiceProvider extends ServiceProvider
31
30
{
···
120
119
$this->app->instance(CredentialProvider::class, $provider);
121
120
}
122
121
123
-
public function public(?string $service = null): AtpPublicClient
122
+
public function public(?string $service = null): AtpClient
124
123
{
125
-
return new AtpPublicClient(
126
-
$service ?? config('atp-client.public.service_url', 'https://public.api.bsky.app')
124
+
return new AtpClient(
125
+
sessions: null,
126
+
did: null,
127
+
serviceUrl: $service ?? config('atp-client.public.service_url', 'https://public.api.bsky.app')
127
128
);
128
129
}
129
130
};
+43
src/Attributes/PublicEndpoint.php
+43
src/Attributes/PublicEndpoint.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Attributes;
4
+
5
+
use Attribute;
6
+
7
+
/**
8
+
* Documents that a method is a public endpoint that does not require authentication.
9
+
*
10
+
* This attribute currently serves as documentation to indicate which AT Protocol
11
+
* endpoints can be called without an authenticated session. It helps developers
12
+
* understand which endpoints work with `Atp::public()` against public API endpoints
13
+
* like `https://public.api.bsky.app`.
14
+
*
15
+
* While this attribute does not currently perform runtime enforcement, scope
16
+
* validation will be implemented in a future release. Correctly attributing
17
+
* endpoints now ensures forward compatibility when enforcement is enabled.
18
+
*
19
+
* Public endpoints typically include operations like:
20
+
* - Reading public profiles and posts
21
+
* - Searching actors and content
22
+
* - Resolving handles to DIDs
23
+
* - Accessing repository data (sync endpoints)
24
+
* - Describing servers and feed generators
25
+
*
26
+
* @example Basic usage
27
+
* ```php
28
+
* #[PublicEndpoint]
29
+
* public function getProfile(string $actor): ProfileViewDetailed
30
+
* ```
31
+
*
32
+
* @see \SocialDept\AtpClient\Attributes\ScopedEndpoint For endpoints that require authentication
33
+
*/
34
+
#[Attribute(Attribute::TARGET_METHOD)]
35
+
class PublicEndpoint
36
+
{
37
+
/**
38
+
* @param string $description Human-readable description of the endpoint
39
+
*/
40
+
public function __construct(
41
+
public readonly string $description = '',
42
+
) {}
43
+
}
-37
src/Attributes/RequiresScope.php
-37
src/Attributes/RequiresScope.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Attributes;
4
-
5
-
use Attribute;
6
-
use SocialDept\AtpClient\Enums\Scope;
7
-
8
-
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
9
-
class RequiresScope
10
-
{
11
-
public array $scopes;
12
-
13
-
/**
14
-
* @param string|Scope|array<string|Scope> $scopes Required scope(s) for this method
15
-
* @param string|null $granular Future granular scope equivalent
16
-
* @param string $description Human-readable description of scope requirement
17
-
*/
18
-
public function __construct(
19
-
string|Scope|array $scopes,
20
-
public readonly ?string $granular = null,
21
-
public readonly string $description = '',
22
-
) {
23
-
$this->scopes = $this->normalizeScopes($scopes);
24
-
}
25
-
26
-
protected function normalizeScopes(string|Scope|array $scopes): array
27
-
{
28
-
if (! is_array($scopes)) {
29
-
$scopes = [$scopes];
30
-
}
31
-
32
-
return array_map(
33
-
fn ($scope) => $scope instanceof Scope ? $scope->value : $scope,
34
-
$scopes
35
-
);
36
-
}
37
-
}
+67
src/Attributes/ScopedEndpoint.php
+67
src/Attributes/ScopedEndpoint.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Attributes;
4
+
5
+
use Attribute;
6
+
use SocialDept\AtpClient\Enums\Scope;
7
+
8
+
/**
9
+
* Documents that a method requires authentication with specific OAuth scopes.
10
+
*
11
+
* This attribute currently serves as documentation to indicate which AT Protocol
12
+
* endpoints require authentication and what scopes they need. It helps developers
13
+
* understand scope requirements when building applications.
14
+
*
15
+
* While this attribute does not currently perform runtime enforcement, scope
16
+
* validation will be implemented in a future release. Correctly attributing
17
+
* endpoints now ensures forward compatibility when enforcement is enabled.
18
+
*
19
+
* The AT Protocol currently uses "transition scopes" (like `transition:generic`) while
20
+
* moving toward more granular scopes. The `granular` parameter allows documenting the
21
+
* future granular scope that will replace the transition scope.
22
+
*
23
+
* @example Basic usage with a transition scope
24
+
* ```php
25
+
* #[ScopedEndpoint(Scope::TransitionGeneric)]
26
+
* public function getTimeline(): GetTimelineResponse
27
+
* ```
28
+
*
29
+
* @example With future granular scope documented
30
+
* ```php
31
+
* #[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getTimeline')]
32
+
* public function getTimeline(): GetTimelineResponse
33
+
* ```
34
+
*
35
+
* @see \SocialDept\AtpClient\Attributes\PublicEndpoint For endpoints that don't require authentication
36
+
* @see \SocialDept\AtpClient\Enums\Scope For available scope values
37
+
*/
38
+
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
39
+
class ScopedEndpoint
40
+
{
41
+
public array $scopes;
42
+
43
+
/**
44
+
* @param string|Scope|array<string|Scope> $scopes Required scope(s) for this method
45
+
* @param string|null $granular Future granular scope equivalent
46
+
* @param string $description Human-readable description of scope requirement
47
+
*/
48
+
public function __construct(
49
+
string|Scope|array $scopes,
50
+
public readonly ?string $granular = null,
51
+
public readonly string $description = '',
52
+
) {
53
+
$this->scopes = $this->normalizeScopes($scopes);
54
+
}
55
+
56
+
protected function normalizeScopes(string|Scope|array $scopes): array
57
+
{
58
+
if (! is_array($scopes)) {
59
+
$scopes = [$scopes];
60
+
}
61
+
62
+
return array_map(
63
+
fn ($scope) => $scope instanceof Scope ? $scope->value : $scope,
64
+
$scopes
65
+
);
66
+
}
67
+
}
+5
-2
src/Auth/ScopeChecker.php
+5
-2
src/Auth/ScopeChecker.php
···
2
2
3
3
namespace SocialDept\AtpClient\Auth;
4
4
5
+
use BackedEnum;
5
6
use Illuminate\Support\Facades\Log;
6
7
use SocialDept\AtpClient\Enums\Scope;
7
8
use SocialDept\AtpClient\Enums\ScopeEnforcementLevel;
···
197
198
/**
198
199
* Check if the session has repo access for a specific collection and action.
199
200
*/
200
-
public function checkRepoScope(Session $session, string $collection, string $action): bool
201
+
public function checkRepoScope(Session $session, string|BackedEnum $collection, string $action): bool
201
202
{
203
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
202
204
$required = "repo:{$collection}?action={$action}";
203
205
204
206
return $this->sessionHasScope($session, $required);
···
209
211
*
210
212
* @throws MissingScopeException
211
213
*/
212
-
public function checkRepoScopeOrFail(Session $session, string $collection, string $action): void
214
+
public function checkRepoScopeOrFail(Session $session, string|BackedEnum $collection, string $action): void
213
215
{
216
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
214
217
$required = "repo:{$collection}?action={$action}";
215
218
216
219
$this->checkOrFail($session, [$required]);
+197
src/Builders/Concerns/BuildsRichText.php
+197
src/Builders/Concerns/BuildsRichText.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Builders\Concerns;
4
+
5
+
use SocialDept\AtpClient\RichText\FacetDetector;
6
+
use SocialDept\AtpResolver\Facades\Resolver;
7
+
8
+
trait BuildsRichText
9
+
{
10
+
protected string $text = '';
11
+
12
+
protected array $facets = [];
13
+
14
+
/**
15
+
* Add plain text
16
+
*/
17
+
public function text(string $text): self
18
+
{
19
+
$this->text .= $text;
20
+
21
+
return $this;
22
+
}
23
+
24
+
/**
25
+
* Add one or more new lines
26
+
*/
27
+
public function newLine(int $count = 1): self
28
+
{
29
+
$this->text .= str_repeat("\n", $count);
30
+
31
+
return $this;
32
+
}
33
+
34
+
/**
35
+
* Add mention (@handle)
36
+
*/
37
+
public function mention(string $handle, ?string $did = null): self
38
+
{
39
+
$handle = ltrim($handle, '@');
40
+
$start = $this->getBytePosition();
41
+
$this->text .= '@'.$handle;
42
+
$end = $this->getBytePosition();
43
+
44
+
if (! $did) {
45
+
try {
46
+
$did = Resolver::handleToDid($handle);
47
+
} catch (\Exception $e) {
48
+
return $this;
49
+
}
50
+
}
51
+
52
+
$this->facets[] = [
53
+
'index' => [
54
+
'byteStart' => $start,
55
+
'byteEnd' => $end,
56
+
],
57
+
'features' => [
58
+
[
59
+
'$type' => 'app.bsky.richtext.facet#mention',
60
+
'did' => $did,
61
+
],
62
+
],
63
+
];
64
+
65
+
return $this;
66
+
}
67
+
68
+
/**
69
+
* Add link with custom display text
70
+
*/
71
+
public function link(string $text, string $uri): self
72
+
{
73
+
$start = $this->getBytePosition();
74
+
$this->text .= $text;
75
+
$end = $this->getBytePosition();
76
+
77
+
$this->facets[] = [
78
+
'index' => [
79
+
'byteStart' => $start,
80
+
'byteEnd' => $end,
81
+
],
82
+
'features' => [
83
+
[
84
+
'$type' => 'app.bsky.richtext.facet#link',
85
+
'uri' => $uri,
86
+
],
87
+
],
88
+
];
89
+
90
+
return $this;
91
+
}
92
+
93
+
/**
94
+
* Add a URL (displayed as-is)
95
+
*/
96
+
public function url(string $url): self
97
+
{
98
+
return $this->link($url, $url);
99
+
}
100
+
101
+
/**
102
+
* Add hashtag
103
+
*/
104
+
public function tag(string $tag): self
105
+
{
106
+
$tag = ltrim($tag, '#');
107
+
108
+
$start = $this->getBytePosition();
109
+
$this->text .= '#'.$tag;
110
+
$end = $this->getBytePosition();
111
+
112
+
$this->facets[] = [
113
+
'index' => [
114
+
'byteStart' => $start,
115
+
'byteEnd' => $end,
116
+
],
117
+
'features' => [
118
+
[
119
+
'$type' => 'app.bsky.richtext.facet#tag',
120
+
'tag' => $tag,
121
+
],
122
+
],
123
+
];
124
+
125
+
return $this;
126
+
}
127
+
128
+
/**
129
+
* Auto-detect and add facets from plain text
130
+
*/
131
+
public function autoDetect(string $text): self
132
+
{
133
+
$start = $this->getBytePosition();
134
+
$this->text .= $text;
135
+
136
+
$detected = FacetDetector::detect($text);
137
+
138
+
foreach ($detected as $facet) {
139
+
$facet['index']['byteStart'] += $start;
140
+
$facet['index']['byteEnd'] += $start;
141
+
$this->facets[] = $facet;
142
+
}
143
+
144
+
return $this;
145
+
}
146
+
147
+
/**
148
+
* Get current byte position (UTF-8 byte offset)
149
+
*/
150
+
protected function getBytePosition(): int
151
+
{
152
+
return strlen($this->text);
153
+
}
154
+
155
+
/**
156
+
* Get the text content
157
+
*/
158
+
public function getText(): string
159
+
{
160
+
return $this->text;
161
+
}
162
+
163
+
/**
164
+
* Get the facets
165
+
*/
166
+
public function getFacets(): array
167
+
{
168
+
return $this->facets;
169
+
}
170
+
171
+
/**
172
+
* Get text and facets as array
173
+
*/
174
+
protected function getTextAndFacets(): array
175
+
{
176
+
return [
177
+
'text' => $this->text,
178
+
'facets' => $this->facets,
179
+
];
180
+
}
181
+
182
+
/**
183
+
* Get grapheme count (closest to what AT Protocol uses for limits)
184
+
*/
185
+
public function getGraphemeCount(): int
186
+
{
187
+
return grapheme_strlen($this->text);
188
+
}
189
+
190
+
/**
191
+
* Check if text exceeds AT Protocol post limit (300 graphemes)
192
+
*/
193
+
public function exceedsLimit(int $limit = 300): bool
194
+
{
195
+
return $this->getGraphemeCount() > $limit;
196
+
}
197
+
}
+93
src/Builders/Embeds/ImagesBuilder.php
+93
src/Builders/Embeds/ImagesBuilder.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Builders\Embeds;
4
+
5
+
class ImagesBuilder
6
+
{
7
+
protected array $images = [];
8
+
9
+
/**
10
+
* Create a new images builder instance
11
+
*/
12
+
public static function make(): self
13
+
{
14
+
return new self;
15
+
}
16
+
17
+
/**
18
+
* Add an image to the embed
19
+
*
20
+
* @param mixed $blob BlobReference or blob array
21
+
* @param string $alt Alt text for the image
22
+
* @param array|null $aspectRatio [width, height] aspect ratio
23
+
*/
24
+
public function add(mixed $blob, string $alt, ?array $aspectRatio = null): self
25
+
{
26
+
$image = [
27
+
'image' => $this->normalizeBlob($blob),
28
+
'alt' => $alt,
29
+
];
30
+
31
+
if ($aspectRatio !== null) {
32
+
$image['aspectRatio'] = [
33
+
'width' => $aspectRatio[0],
34
+
'height' => $aspectRatio[1],
35
+
];
36
+
}
37
+
38
+
$this->images[] = $image;
39
+
40
+
return $this;
41
+
}
42
+
43
+
/**
44
+
* Get all images
45
+
*/
46
+
public function getImages(): array
47
+
{
48
+
return $this->images;
49
+
}
50
+
51
+
/**
52
+
* Check if builder has images
53
+
*/
54
+
public function hasImages(): bool
55
+
{
56
+
return ! empty($this->images);
57
+
}
58
+
59
+
/**
60
+
* Get the count of images
61
+
*/
62
+
public function count(): int
63
+
{
64
+
return count($this->images);
65
+
}
66
+
67
+
/**
68
+
* Convert to embed array format
69
+
*/
70
+
public function toArray(): array
71
+
{
72
+
return [
73
+
'$type' => 'app.bsky.embed.images',
74
+
'images' => $this->images,
75
+
];
76
+
}
77
+
78
+
/**
79
+
* Normalize blob to array format
80
+
*/
81
+
protected function normalizeBlob(mixed $blob): array
82
+
{
83
+
if (is_array($blob)) {
84
+
return $blob;
85
+
}
86
+
87
+
if (method_exists($blob, 'toArray')) {
88
+
return $blob->toArray();
89
+
}
90
+
91
+
return (array) $blob;
92
+
}
93
+
}
+257
src/Builders/PostBuilder.php
+257
src/Builders/PostBuilder.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Builders;
4
+
5
+
use Closure;
6
+
use DateTimeInterface;
7
+
use SocialDept\AtpClient\Builders\Concerns\BuildsRichText;
8
+
use SocialDept\AtpClient\Builders\Embeds\ImagesBuilder;
9
+
use SocialDept\AtpClient\Client\Records\PostRecordClient;
10
+
use SocialDept\AtpClient\Contracts\Recordable;
11
+
use SocialDept\AtpClient\Data\StrongRef;
12
+
use SocialDept\AtpClient\Enums\Nsid\BskyFeed;
13
+
14
+
class PostBuilder implements Recordable
15
+
{
16
+
use BuildsRichText;
17
+
18
+
protected ?array $embed = null;
19
+
20
+
protected ?array $reply = null;
21
+
22
+
protected ?array $langs = null;
23
+
24
+
protected ?DateTimeInterface $createdAt = null;
25
+
26
+
protected ?PostRecordClient $client = null;
27
+
28
+
/**
29
+
* Create a new post builder instance
30
+
*/
31
+
public static function make(): self
32
+
{
33
+
return new self;
34
+
}
35
+
36
+
/**
37
+
* Add images embed
38
+
*
39
+
* @param Closure|array $images Closure receiving ImagesBuilder, or array of image data
40
+
*/
41
+
public function images(Closure|array $images): self
42
+
{
43
+
if ($images instanceof Closure) {
44
+
$builder = ImagesBuilder::make();
45
+
$images($builder);
46
+
$this->embed = $builder->toArray();
47
+
} else {
48
+
$this->embed = [
49
+
'$type' => 'app.bsky.embed.images',
50
+
'images' => array_map(fn ($img) => $this->normalizeImageData($img), $images),
51
+
];
52
+
}
53
+
54
+
return $this;
55
+
}
56
+
57
+
/**
58
+
* Add external link embed (link card)
59
+
*
60
+
* @param string $uri URL of the external content
61
+
* @param string $title Title of the link card
62
+
* @param string $description Description text
63
+
* @param mixed|null $thumb Optional thumbnail blob
64
+
*/
65
+
public function external(string $uri, string $title, string $description, mixed $thumb = null): self
66
+
{
67
+
$external = [
68
+
'uri' => $uri,
69
+
'title' => $title,
70
+
'description' => $description,
71
+
];
72
+
73
+
if ($thumb !== null) {
74
+
$external['thumb'] = $this->normalizeBlob($thumb);
75
+
}
76
+
77
+
$this->embed = [
78
+
'$type' => 'app.bsky.embed.external',
79
+
'external' => $external,
80
+
];
81
+
82
+
return $this;
83
+
}
84
+
85
+
/**
86
+
* Add video embed
87
+
*
88
+
* @param mixed $blob Video blob reference
89
+
* @param string|null $alt Alt text for the video
90
+
* @param array|null $captions Optional captions array
91
+
*/
92
+
public function video(mixed $blob, ?string $alt = null, ?array $captions = null): self
93
+
{
94
+
$video = [
95
+
'$type' => 'app.bsky.embed.video',
96
+
'video' => $this->normalizeBlob($blob),
97
+
];
98
+
99
+
if ($alt !== null) {
100
+
$video['alt'] = $alt;
101
+
}
102
+
103
+
if ($captions !== null) {
104
+
$video['captions'] = $captions;
105
+
}
106
+
107
+
$this->embed = $video;
108
+
109
+
return $this;
110
+
}
111
+
112
+
/**
113
+
* Add quote embed (embed another post)
114
+
*/
115
+
public function quote(StrongRef $post): self
116
+
{
117
+
$this->embed = [
118
+
'$type' => 'app.bsky.embed.record',
119
+
'record' => $post->toArray(),
120
+
];
121
+
122
+
return $this;
123
+
}
124
+
125
+
/**
126
+
* Set as a reply to another post
127
+
*
128
+
* @param StrongRef $parent The post being replied to
129
+
* @param StrongRef|null $root The root post of the thread (defaults to parent if not provided)
130
+
*/
131
+
public function replyTo(StrongRef $parent, ?StrongRef $root = null): self
132
+
{
133
+
$this->reply = [
134
+
'parent' => $parent->toArray(),
135
+
'root' => ($root ?? $parent)->toArray(),
136
+
];
137
+
138
+
return $this;
139
+
}
140
+
141
+
/**
142
+
* Set the post languages
143
+
*
144
+
* @param array $langs Array of BCP-47 language codes
145
+
*/
146
+
public function langs(array $langs): self
147
+
{
148
+
$this->langs = $langs;
149
+
150
+
return $this;
151
+
}
152
+
153
+
/**
154
+
* Set the creation timestamp
155
+
*/
156
+
public function createdAt(DateTimeInterface $date): self
157
+
{
158
+
$this->createdAt = $date;
159
+
160
+
return $this;
161
+
}
162
+
163
+
/**
164
+
* Bind to a PostRecordClient for creating the post
165
+
*/
166
+
public function for(PostRecordClient $client): self
167
+
{
168
+
$this->client = $client;
169
+
170
+
return $this;
171
+
}
172
+
173
+
/**
174
+
* Create the post (requires client binding via for() or build())
175
+
*
176
+
* @throws \RuntimeException If no client is bound
177
+
*/
178
+
public function create(): StrongRef
179
+
{
180
+
if ($this->client === null) {
181
+
throw new \RuntimeException(
182
+
'No client bound. Use ->for($client) or create via $client->bsky->post->build()'
183
+
);
184
+
}
185
+
186
+
return $this->client->create($this);
187
+
}
188
+
189
+
/**
190
+
* Convert to array for XRPC (implements Recordable)
191
+
*/
192
+
public function toArray(): array
193
+
{
194
+
$record = $this->getTextAndFacets();
195
+
196
+
if ($this->embed !== null) {
197
+
$record['embed'] = $this->embed;
198
+
}
199
+
200
+
if ($this->reply !== null) {
201
+
$record['reply'] = $this->reply;
202
+
}
203
+
204
+
if ($this->langs !== null) {
205
+
$record['langs'] = $this->langs;
206
+
}
207
+
208
+
$record['createdAt'] = ($this->createdAt ?? now())->format('c');
209
+
$record['$type'] = $this->getType();
210
+
211
+
return $record;
212
+
}
213
+
214
+
/**
215
+
* Get the record type (implements Recordable)
216
+
*/
217
+
public function getType(): string
218
+
{
219
+
return BskyFeed::Post->value;
220
+
}
221
+
222
+
/**
223
+
* Normalize image data from array format
224
+
*/
225
+
protected function normalizeImageData(array $data): array
226
+
{
227
+
$image = [
228
+
'image' => $this->normalizeBlob($data['blob'] ?? $data['image']),
229
+
'alt' => $data['alt'] ?? '',
230
+
];
231
+
232
+
if (isset($data['aspectRatio'])) {
233
+
$ratio = $data['aspectRatio'];
234
+
$image['aspectRatio'] = is_array($ratio) && isset($ratio['width'])
235
+
? $ratio
236
+
: ['width' => $ratio[0], 'height' => $ratio[1]];
237
+
}
238
+
239
+
return $image;
240
+
}
241
+
242
+
/**
243
+
* Normalize blob to array format
244
+
*/
245
+
protected function normalizeBlob(mixed $blob): array
246
+
{
247
+
if (is_array($blob)) {
248
+
return $blob;
249
+
}
250
+
251
+
if (method_exists($blob, 'toArray')) {
252
+
return $blob->toArray();
253
+
}
254
+
255
+
return (array) $blob;
256
+
}
257
+
}
+13
src/Client/BskyClient.php
+13
src/Client/BskyClient.php
···
13
13
class BskyClient
14
14
{
15
15
use HasDomainExtensions;
16
+
16
17
/**
17
18
* The parent AtpClient instance
18
19
*/
···
29
30
public Bsky\ActorRequestClient $actor;
30
31
31
32
/**
33
+
* Graph operations (app.bsky.graph.*)
34
+
*/
35
+
public Bsky\GraphRequestClient $graph;
36
+
37
+
/**
38
+
* Labeler operations (app.bsky.labeler.*)
39
+
*/
40
+
public Bsky\LabelerRequestClient $labeler;
41
+
42
+
/**
32
43
* Post record client
33
44
*/
34
45
public PostRecordClient $post;
···
53
64
$this->atp = $parent;
54
65
$this->feed = new Bsky\FeedRequestClient($this);
55
66
$this->actor = new Bsky\ActorRequestClient($this);
67
+
$this->graph = new Bsky\GraphRequestClient($this);
68
+
$this->labeler = new Bsky\LabelerRequestClient($this);
56
69
$this->post = new PostRecordClient($this);
57
70
$this->profile = new ProfileRecordClient($this);
58
71
$this->like = new LikeRecordClient($this);
+93
-4
src/Client/Client.php
+93
-4
src/Client/Client.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client;
4
4
5
+
use BackedEnum;
6
+
use Illuminate\Support\Facades\Http;
5
7
use SocialDept\AtpClient\AtpClient;
8
+
use SocialDept\AtpClient\Exceptions\AtpResponseException;
6
9
use SocialDept\AtpClient\Http\DPoPClient;
7
10
use SocialDept\AtpClient\Http\HasHttp;
11
+
use SocialDept\AtpClient\Http\Response;
8
12
use SocialDept\AtpClient\Session\Session;
9
13
use SocialDept\AtpClient\Session\SessionManager;
10
14
11
15
class Client
12
16
{
13
-
use HasHttp;
17
+
use HasHttp {
18
+
call as authenticatedCall;
19
+
postBlob as authenticatedPostBlob;
20
+
}
14
21
15
22
/**
16
23
* The parent AtpClient instance we belong to
17
24
*/
18
25
protected AtpClient $atp;
19
26
27
+
/**
28
+
* Service URL for public mode
29
+
*/
30
+
protected ?string $serviceUrl;
31
+
20
32
public function __construct(
21
33
AtpClient $parent,
22
-
SessionManager $sessions,
23
-
string $did,
34
+
?SessionManager $sessions = null,
35
+
?string $did = null,
36
+
?string $serviceUrl = null,
24
37
) {
25
38
$this->atp = $parent;
26
39
$this->sessions = $sessions;
27
40
$this->did = $did;
28
-
$this->dpopClient = app(DPoPClient::class);
41
+
$this->serviceUrl = $serviceUrl;
42
+
43
+
if (! $this->isPublicMode()) {
44
+
$this->dpopClient = app(DPoPClient::class);
45
+
}
46
+
}
47
+
48
+
/**
49
+
* Check if client is in public mode (no authentication).
50
+
*/
51
+
public function isPublicMode(): bool
52
+
{
53
+
return $this->sessions === null || $this->did === null;
29
54
}
30
55
31
56
/**
···
34
59
public function session(): Session
35
60
{
36
61
return $this->sessions->session($this->did);
62
+
}
63
+
64
+
/**
65
+
* Get the service URL.
66
+
*/
67
+
public function serviceUrl(): string
68
+
{
69
+
return $this->serviceUrl;
70
+
}
71
+
72
+
/**
73
+
* Make XRPC call - routes to public or authenticated based on mode.
74
+
*/
75
+
protected function call(
76
+
string|BackedEnum $endpoint,
77
+
string $method,
78
+
?array $params = null,
79
+
?array $body = null
80
+
): Response {
81
+
if ($this->isPublicMode()) {
82
+
return $this->publicCall($endpoint, $method, $params, $body);
83
+
}
84
+
85
+
return $this->authenticatedCall($endpoint, $method, $params, $body);
86
+
}
87
+
88
+
/**
89
+
* Make public XRPC call (no authentication).
90
+
*/
91
+
protected function publicCall(
92
+
string|BackedEnum $endpoint,
93
+
string $method,
94
+
?array $params = null,
95
+
?array $body = null
96
+
): Response {
97
+
$endpoint = $endpoint instanceof BackedEnum ? $endpoint->value : $endpoint;
98
+
$url = rtrim($this->serviceUrl, '/') . '/xrpc/' . $endpoint;
99
+
$params = array_filter($params ?? [], fn ($v) => ! is_null($v));
100
+
101
+
$response = match ($method) {
102
+
'GET' => Http::get($url, $params),
103
+
'POST' => Http::post($url, $body ?? $params),
104
+
'DELETE' => Http::delete($url, $params),
105
+
default => throw new \InvalidArgumentException("Unsupported method: {$method}"),
106
+
};
107
+
108
+
if ($response->failed() || isset($response->json()['error'])) {
109
+
throw AtpResponseException::fromResponse($response, $endpoint);
110
+
}
111
+
112
+
return new Response($response);
113
+
}
114
+
115
+
/**
116
+
* Make POST request with raw binary body (for blob uploads).
117
+
* Only works in authenticated mode.
118
+
*/
119
+
public function postBlob(string|BackedEnum $endpoint, string $data, string $mimeType): Response
120
+
{
121
+
if ($this->isPublicMode()) {
122
+
throw new \RuntimeException('Blob uploads require authentication.');
123
+
}
124
+
125
+
return $this->authenticatedPostBlob($endpoint, $data, $mimeType);
37
126
}
38
127
}
-20
src/Client/Public/AtpPublicClient.php
-20
src/Client/Public/AtpPublicClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public;
4
-
5
-
use SocialDept\AtpClient\Concerns\HasExtensions;
6
-
7
-
class AtpPublicClient
8
-
{
9
-
use HasExtensions;
10
-
public PublicClient $client;
11
-
public BskyPublicClient $bsky;
12
-
public AtprotoPublicClient $atproto;
13
-
14
-
public function __construct(string $serviceUrl)
15
-
{
16
-
$this->client = new PublicClient($serviceUrl);
17
-
$this->bsky = new BskyPublicClient($this);
18
-
$this->atproto = new AtprotoPublicClient($this);
19
-
}
20
-
}
-38
src/Client/Public/AtprotoPublicClient.php
-38
src/Client/Public/AtprotoPublicClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\Atproto\IdentityPublicRequestClient;
6
-
use SocialDept\AtpClient\Client\Public\Requests\Atproto\RepoPublicRequestClient;
7
-
use SocialDept\AtpClient\Concerns\HasDomainExtensions;
8
-
9
-
class AtprotoPublicClient
10
-
{
11
-
use HasDomainExtensions;
12
-
13
-
protected AtpPublicClient $atp;
14
-
public IdentityPublicRequestClient $identity;
15
-
public RepoPublicRequestClient $repo;
16
-
17
-
public function __construct(AtpPublicClient $parent)
18
-
{
19
-
$this->atp = $parent;
20
-
$this->identity = new IdentityPublicRequestClient($this);
21
-
$this->repo = new RepoPublicRequestClient($this);
22
-
}
23
-
24
-
protected function getDomainName(): string
25
-
{
26
-
return 'atproto';
27
-
}
28
-
29
-
protected function getRootClientClass(): string
30
-
{
31
-
return AtpPublicClient::class;
32
-
}
33
-
34
-
public function root(): AtpPublicClient
35
-
{
36
-
return $this->atp;
37
-
}
38
-
}
-44
src/Client/Public/BskyPublicClient.php
-44
src/Client/Public/BskyPublicClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\Bsky\ActorPublicRequestClient;
6
-
use SocialDept\AtpClient\Client\Public\Requests\Bsky\FeedPublicRequestClient;
7
-
use SocialDept\AtpClient\Client\Public\Requests\Bsky\GraphPublicRequestClient;
8
-
use SocialDept\AtpClient\Client\Public\Requests\Bsky\LabelerPublicRequestClient;
9
-
use SocialDept\AtpClient\Concerns\HasDomainExtensions;
10
-
11
-
class BskyPublicClient
12
-
{
13
-
use HasDomainExtensions;
14
-
15
-
protected AtpPublicClient $atp;
16
-
public ActorPublicRequestClient $actor;
17
-
public FeedPublicRequestClient $feed;
18
-
public GraphPublicRequestClient $graph;
19
-
public LabelerPublicRequestClient $labeler;
20
-
21
-
public function __construct(AtpPublicClient $parent)
22
-
{
23
-
$this->atp = $parent;
24
-
$this->actor = new ActorPublicRequestClient($this);
25
-
$this->feed = new FeedPublicRequestClient($this);
26
-
$this->graph = new GraphPublicRequestClient($this);
27
-
$this->labeler = new LabelerPublicRequestClient($this);
28
-
}
29
-
30
-
protected function getDomainName(): string
31
-
{
32
-
return 'bsky';
33
-
}
34
-
35
-
protected function getRootClientClass(): string
36
-
{
37
-
return AtpPublicClient::class;
38
-
}
39
-
40
-
public function root(): AtpPublicClient
41
-
{
42
-
return $this->atp;
43
-
}
44
-
}
-35
src/Client/Public/PublicClient.php
-35
src/Client/Public/PublicClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public;
4
-
5
-
use BackedEnum;
6
-
use Illuminate\Support\Facades\Http;
7
-
use SocialDept\AtpClient\Exceptions\AtpResponseException;
8
-
use SocialDept\AtpClient\Http\Response;
9
-
10
-
class PublicClient
11
-
{
12
-
public function __construct(
13
-
protected string $serviceUrl
14
-
) {}
15
-
16
-
public function get(string|BackedEnum $endpoint, array $params = []): Response
17
-
{
18
-
$endpoint = $endpoint instanceof BackedEnum ? $endpoint->value : $endpoint;
19
-
$url = rtrim($this->serviceUrl, '/') . '/xrpc/' . $endpoint;
20
-
$params = array_filter($params, fn ($v) => !is_null($v));
21
-
22
-
$response = Http::get($url, $params);
23
-
24
-
if ($response->failed() || isset($response->json()['error'])) {
25
-
throw AtpResponseException::fromResponse($response, $endpoint);
26
-
}
27
-
28
-
return new Response($response);
29
-
}
30
-
31
-
public function serviceUrl(): string
32
-
{
33
-
return $this->serviceUrl;
34
-
}
35
-
}
-19
src/Client/Public/Requests/Atproto/IdentityPublicRequestClient.php
-19
src/Client/Public/Requests/Atproto/IdentityPublicRequestClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public\Requests\Atproto;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
6
-
use SocialDept\AtpClient\Enums\Nsid\AtprotoIdentity;
7
-
8
-
class IdentityPublicRequestClient extends PublicRequest
9
-
{
10
-
public function resolveHandle(string $handle): string
11
-
{
12
-
$response = $this->atp->client->get(
13
-
endpoint: AtprotoIdentity::ResolveHandle,
14
-
params: compact('handle')
15
-
);
16
-
17
-
return $response->json()['did'];
18
-
}
19
-
}
-66
src/Client/Public/Requests/Atproto/RepoPublicRequestClient.php
-66
src/Client/Public/Requests/Atproto/RepoPublicRequestClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public\Requests\Atproto;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
6
-
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\DescribeRepoResponse;
7
-
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\GetRecordResponse;
8
-
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\ListRecordsResponse;
9
-
use SocialDept\AtpClient\Enums\Nsid\AtprotoRepo;
10
-
11
-
class RepoPublicRequestClient extends PublicRequest
12
-
{
13
-
/**
14
-
* Get a record
15
-
*
16
-
* @see https://docs.bsky.app/docs/api/com-atproto-repo-get-record
17
-
*/
18
-
public function getRecord(
19
-
string $repo,
20
-
string $collection,
21
-
string $rkey,
22
-
?string $cid = null
23
-
): GetRecordResponse {
24
-
$response = $this->atp->client->get(
25
-
endpoint: AtprotoRepo::GetRecord,
26
-
params: compact('repo', 'collection', 'rkey', 'cid')
27
-
);
28
-
29
-
return GetRecordResponse::fromArray($response->json());
30
-
}
31
-
32
-
/**
33
-
* List records in a collection
34
-
*
35
-
* @see https://docs.bsky.app/docs/api/com-atproto-repo-list-records
36
-
*/
37
-
public function listRecords(
38
-
string $repo,
39
-
string $collection,
40
-
int $limit = 50,
41
-
?string $cursor = null,
42
-
bool $reverse = false
43
-
): ListRecordsResponse {
44
-
$response = $this->atp->client->get(
45
-
endpoint: AtprotoRepo::ListRecords,
46
-
params: compact('repo', 'collection', 'limit', 'cursor', 'reverse')
47
-
);
48
-
49
-
return ListRecordsResponse::fromArray($response->json());
50
-
}
51
-
52
-
/**
53
-
* Describe the repository
54
-
*
55
-
* @see https://docs.bsky.app/docs/api/com-atproto-repo-describe-repo
56
-
*/
57
-
public function describeRepo(string $repo): DescribeRepoResponse
58
-
{
59
-
$response = $this->atp->client->get(
60
-
endpoint: AtprotoRepo::DescribeRepo,
61
-
params: compact('repo')
62
-
);
63
-
64
-
return DescribeRepoResponse::fromArray($response->json());
65
-
}
66
-
}
-64
src/Client/Public/Requests/Bsky/ActorPublicRequestClient.php
-64
src/Client/Public/Requests/Bsky/ActorPublicRequestClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public\Requests\Bsky;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
6
-
use SocialDept\AtpClient\Enums\Nsid\BskyActor;
7
-
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\GetProfilesResponse;
8
-
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\GetSuggestionsResponse;
9
-
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\SearchActorsResponse;
10
-
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\SearchActorsTypeaheadResponse;
11
-
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileViewDetailed;
12
-
13
-
class ActorPublicRequestClient extends PublicRequest
14
-
{
15
-
public function getProfile(string $actor): ProfileViewDetailed
16
-
{
17
-
$response = $this->atp->client->get(
18
-
endpoint: BskyActor::GetProfile,
19
-
params: compact('actor')
20
-
);
21
-
22
-
return ProfileViewDetailed::fromArray($response->json());
23
-
}
24
-
25
-
public function getProfiles(array $actors): GetProfilesResponse
26
-
{
27
-
$response = $this->atp->client->get(
28
-
endpoint: BskyActor::GetProfiles,
29
-
params: compact('actors')
30
-
);
31
-
32
-
return GetProfilesResponse::fromArray($response->json());
33
-
}
34
-
35
-
public function getSuggestions(int $limit = 50, ?string $cursor = null): GetSuggestionsResponse
36
-
{
37
-
$response = $this->atp->client->get(
38
-
endpoint: BskyActor::GetSuggestions,
39
-
params: compact('limit', 'cursor')
40
-
);
41
-
42
-
return GetSuggestionsResponse::fromArray($response->json());
43
-
}
44
-
45
-
public function searchActors(string $q, int $limit = 25, ?string $cursor = null): SearchActorsResponse
46
-
{
47
-
$response = $this->atp->client->get(
48
-
endpoint: BskyActor::SearchActors,
49
-
params: compact('q', 'limit', 'cursor')
50
-
);
51
-
52
-
return SearchActorsResponse::fromArray($response->json());
53
-
}
54
-
55
-
public function searchActorsTypeahead(string $q, int $limit = 10): SearchActorsTypeaheadResponse
56
-
{
57
-
$response = $this->atp->client->get(
58
-
endpoint: BskyActor::SearchActorsTypeahead,
59
-
params: compact('q', 'limit')
60
-
);
61
-
62
-
return SearchActorsTypeaheadResponse::fromArray($response->json());
63
-
}
64
-
}
-162
src/Client/Public/Requests/Bsky/FeedPublicRequestClient.php
-162
src/Client/Public/Requests/Bsky/FeedPublicRequestClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public\Requests\Bsky;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
6
-
use SocialDept\AtpClient\Enums\Nsid\BskyFeed;
7
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\DescribeFeedGeneratorResponse;
8
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetActorFeedsResponse;
9
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetActorLikesResponse;
10
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetAuthorFeedResponse;
11
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetFeedGeneratorResponse;
12
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetFeedGeneratorsResponse;
13
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetFeedResponse;
14
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetLikesResponse;
15
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetPostsResponse;
16
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetPostThreadResponse;
17
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetQuotesResponse;
18
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetRepostedByResponse;
19
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetSuggestedFeedsResponse;
20
-
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\SearchPostsResponse;
21
-
22
-
class FeedPublicRequestClient extends PublicRequest
23
-
{
24
-
public function describeFeedGenerator(): DescribeFeedGeneratorResponse
25
-
{
26
-
$response = $this->atp->client->get(
27
-
endpoint: BskyFeed::DescribeFeedGenerator
28
-
);
29
-
30
-
return DescribeFeedGeneratorResponse::fromArray($response->json());
31
-
}
32
-
33
-
public function getAuthorFeed(string $actor, int $limit = 50, ?string $cursor = null, ?string $filter = null): GetAuthorFeedResponse
34
-
{
35
-
$response = $this->atp->client->get(
36
-
endpoint: BskyFeed::GetAuthorFeed,
37
-
params: compact('actor', 'limit', 'cursor', 'filter')
38
-
);
39
-
40
-
return GetAuthorFeedResponse::fromArray($response->json());
41
-
}
42
-
43
-
public function getActorFeeds(string $actor, int $limit = 50, ?string $cursor = null): GetActorFeedsResponse
44
-
{
45
-
$response = $this->atp->client->get(
46
-
endpoint: BskyFeed::GetActorFeeds,
47
-
params: compact('actor', 'limit', 'cursor')
48
-
);
49
-
50
-
return GetActorFeedsResponse::fromArray($response->json());
51
-
}
52
-
53
-
public function getActorLikes(string $actor, int $limit = 50, ?string $cursor = null): GetActorLikesResponse
54
-
{
55
-
$response = $this->atp->client->get(
56
-
endpoint: BskyFeed::GetActorLikes,
57
-
params: compact('actor', 'limit', 'cursor')
58
-
);
59
-
60
-
return GetActorLikesResponse::fromArray($response->json());
61
-
}
62
-
63
-
public function getFeed(string $feed, int $limit = 50, ?string $cursor = null): GetFeedResponse
64
-
{
65
-
$response = $this->atp->client->get(
66
-
endpoint: BskyFeed::GetFeed,
67
-
params: compact('feed', 'limit', 'cursor')
68
-
);
69
-
70
-
return GetFeedResponse::fromArray($response->json());
71
-
}
72
-
73
-
public function getFeedGenerator(string $feed): GetFeedGeneratorResponse
74
-
{
75
-
$response = $this->atp->client->get(
76
-
endpoint: BskyFeed::GetFeedGenerator,
77
-
params: compact('feed')
78
-
);
79
-
80
-
return GetFeedGeneratorResponse::fromArray($response->json());
81
-
}
82
-
83
-
public function getFeedGenerators(array $feeds): GetFeedGeneratorsResponse
84
-
{
85
-
$response = $this->atp->client->get(
86
-
endpoint: BskyFeed::GetFeedGenerators,
87
-
params: compact('feeds')
88
-
);
89
-
90
-
return GetFeedGeneratorsResponse::fromArray($response->json());
91
-
}
92
-
93
-
public function getLikes(string $uri, int $limit = 50, ?string $cursor = null, ?string $cid = null): GetLikesResponse
94
-
{
95
-
$response = $this->atp->client->get(
96
-
endpoint: BskyFeed::GetLikes,
97
-
params: compact('uri', 'limit', 'cursor', 'cid')
98
-
);
99
-
100
-
return GetLikesResponse::fromArray($response->json());
101
-
}
102
-
103
-
public function getPostThread(string $uri, int $depth = 6, int $parentHeight = 80): GetPostThreadResponse
104
-
{
105
-
$response = $this->atp->client->get(
106
-
endpoint: BskyFeed::GetPostThread,
107
-
params: compact('uri', 'depth', 'parentHeight')
108
-
);
109
-
110
-
return GetPostThreadResponse::fromArray($response->json());
111
-
}
112
-
113
-
public function getPosts(array $uris): GetPostsResponse
114
-
{
115
-
$response = $this->atp->client->get(
116
-
endpoint: BskyFeed::GetPosts,
117
-
params: compact('uris')
118
-
);
119
-
120
-
return GetPostsResponse::fromArray($response->json());
121
-
}
122
-
123
-
public function getQuotes(string $uri, int $limit = 50, ?string $cursor = null, ?string $cid = null): GetQuotesResponse
124
-
{
125
-
$response = $this->atp->client->get(
126
-
endpoint: BskyFeed::GetQuotes,
127
-
params: compact('uri', 'limit', 'cursor', 'cid')
128
-
);
129
-
130
-
return GetQuotesResponse::fromArray($response->json());
131
-
}
132
-
133
-
public function getRepostedBy(string $uri, int $limit = 50, ?string $cursor = null, ?string $cid = null): GetRepostedByResponse
134
-
{
135
-
$response = $this->atp->client->get(
136
-
endpoint: BskyFeed::GetRepostedBy,
137
-
params: compact('uri', 'limit', 'cursor', 'cid')
138
-
);
139
-
140
-
return GetRepostedByResponse::fromArray($response->json());
141
-
}
142
-
143
-
public function getSuggestedFeeds(int $limit = 50, ?string $cursor = null): GetSuggestedFeedsResponse
144
-
{
145
-
$response = $this->atp->client->get(
146
-
endpoint: BskyFeed::GetSuggestedFeeds,
147
-
params: compact('limit', 'cursor')
148
-
);
149
-
150
-
return GetSuggestedFeedsResponse::fromArray($response->json());
151
-
}
152
-
153
-
public function searchPosts(string $q, int $limit = 25, ?string $cursor = null, ?string $sort = null): SearchPostsResponse
154
-
{
155
-
$response = $this->atp->client->get(
156
-
endpoint: BskyFeed::SearchPosts,
157
-
params: compact('q', 'limit', 'cursor', 'sort')
158
-
);
159
-
160
-
return SearchPostsResponse::fromArray($response->json());
161
-
}
162
-
}
-108
src/Client/Public/Requests/Bsky/GraphPublicRequestClient.php
-108
src/Client/Public/Requests/Bsky/GraphPublicRequestClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public\Requests\Bsky;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
6
-
use SocialDept\AtpClient\Enums\Nsid\BskyGraph;
7
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetFollowersResponse;
8
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetFollowsResponse;
9
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetKnownFollowersResponse;
10
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetListResponse;
11
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetListsResponse;
12
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetRelationshipsResponse;
13
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetStarterPacksResponse;
14
-
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetSuggestedFollowsByActorResponse;
15
-
use SocialDept\AtpSchema\Generated\App\Bsky\Graph\Defs\StarterPackView;
16
-
17
-
class GraphPublicRequestClient extends PublicRequest
18
-
{
19
-
public function getFollowers(string $actor, int $limit = 50, ?string $cursor = null): GetFollowersResponse
20
-
{
21
-
$response = $this->atp->client->get(
22
-
endpoint: BskyGraph::GetFollowers,
23
-
params: compact('actor', 'limit', 'cursor')
24
-
);
25
-
26
-
return GetFollowersResponse::fromArray($response->json());
27
-
}
28
-
29
-
public function getFollows(string $actor, int $limit = 50, ?string $cursor = null): GetFollowsResponse
30
-
{
31
-
$response = $this->atp->client->get(
32
-
endpoint: BskyGraph::GetFollows,
33
-
params: compact('actor', 'limit', 'cursor')
34
-
);
35
-
36
-
return GetFollowsResponse::fromArray($response->json());
37
-
}
38
-
39
-
public function getKnownFollowers(string $actor, int $limit = 50, ?string $cursor = null): GetKnownFollowersResponse
40
-
{
41
-
$response = $this->atp->client->get(
42
-
endpoint: BskyGraph::GetKnownFollowers,
43
-
params: compact('actor', 'limit', 'cursor')
44
-
);
45
-
46
-
return GetKnownFollowersResponse::fromArray($response->json());
47
-
}
48
-
49
-
public function getList(string $list, int $limit = 50, ?string $cursor = null): GetListResponse
50
-
{
51
-
$response = $this->atp->client->get(
52
-
endpoint: BskyGraph::GetList,
53
-
params: compact('list', 'limit', 'cursor')
54
-
);
55
-
56
-
return GetListResponse::fromArray($response->json());
57
-
}
58
-
59
-
public function getLists(string $actor, int $limit = 50, ?string $cursor = null): GetListsResponse
60
-
{
61
-
$response = $this->atp->client->get(
62
-
endpoint: BskyGraph::GetLists,
63
-
params: compact('actor', 'limit', 'cursor')
64
-
);
65
-
66
-
return GetListsResponse::fromArray($response->json());
67
-
}
68
-
69
-
public function getRelationships(string $actor, array $others = []): GetRelationshipsResponse
70
-
{
71
-
$response = $this->atp->client->get(
72
-
endpoint: BskyGraph::GetRelationships,
73
-
params: compact('actor', 'others')
74
-
);
75
-
76
-
return GetRelationshipsResponse::fromArray($response->json());
77
-
}
78
-
79
-
public function getStarterPack(string $starterPack): StarterPackView
80
-
{
81
-
$response = $this->atp->client->get(
82
-
endpoint: BskyGraph::GetStarterPack,
83
-
params: compact('starterPack')
84
-
);
85
-
86
-
return StarterPackView::fromArray($response->json()['starterPack']);
87
-
}
88
-
89
-
public function getStarterPacks(array $uris): GetStarterPacksResponse
90
-
{
91
-
$response = $this->atp->client->get(
92
-
endpoint: BskyGraph::GetStarterPacks,
93
-
params: compact('uris')
94
-
);
95
-
96
-
return GetStarterPacksResponse::fromArray($response->json());
97
-
}
98
-
99
-
public function getSuggestedFollowsByActor(string $actor): GetSuggestedFollowsByActorResponse
100
-
{
101
-
$response = $this->atp->client->get(
102
-
endpoint: BskyGraph::GetSuggestedFollowsByActor,
103
-
params: compact('actor')
104
-
);
105
-
106
-
return GetSuggestedFollowsByActorResponse::fromArray($response->json());
107
-
}
108
-
}
-20
src/Client/Public/Requests/Bsky/LabelerPublicRequestClient.php
-20
src/Client/Public/Requests/Bsky/LabelerPublicRequestClient.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public\Requests\Bsky;
4
-
5
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
6
-
use SocialDept\AtpClient\Data\Responses\Bsky\Labeler\GetServicesResponse;
7
-
use SocialDept\AtpClient\Enums\Nsid\BskyLabeler;
8
-
9
-
class LabelerPublicRequestClient extends PublicRequest
10
-
{
11
-
public function getServices(array $dids, bool $detailed = false): GetServicesResponse
12
-
{
13
-
$response = $this->atp->client->get(
14
-
endpoint: BskyLabeler::GetServices,
15
-
params: compact('dids', 'detailed')
16
-
);
17
-
18
-
return GetServicesResponse::fromArray($response->json(), $detailed);
19
-
}
20
-
}
-15
src/Client/Public/Requests/PublicRequest.php
-15
src/Client/Public/Requests/PublicRequest.php
···
1
-
<?php
2
-
3
-
namespace SocialDept\AtpClient\Client\Public\Requests;
4
-
5
-
use SocialDept\AtpClient\Client\Public\AtpPublicClient;
6
-
7
-
class PublicRequest
8
-
{
9
-
protected AtpPublicClient $atp;
10
-
11
-
public function __construct($parent)
12
-
{
13
-
$this->atp = $parent->root();
14
-
}
15
-
}
+15
-17
src/Client/Records/FollowRecordClient.php
+15
-17
src/Client/Records/FollowRecordClient.php
···
3
3
namespace SocialDept\AtpClient\Client\Records;
4
4
5
5
use DateTimeInterface;
6
-
use SocialDept\AtpClient\Attributes\RequiresScope;
6
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
7
7
use SocialDept\AtpClient\Client\Requests\Request;
8
-
use SocialDept\AtpClient\Data\StrongRef;
8
+
use SocialDept\AtpClient\Data\Record;
9
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\CreateRecordResponse;
10
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\DeleteRecordResponse;
9
11
use SocialDept\AtpClient\Enums\Nsid\BskyGraph;
10
12
use SocialDept\AtpClient\Enums\Scope;
11
13
···
16
18
*
17
19
* @requires transition:generic OR (rpc:com.atproto.repo.createRecord AND repo:app.bsky.graph.follow?action=create)
18
20
*/
19
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
20
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.graph.follow?action=create')]
21
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
22
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.graph.follow?action=create')]
21
23
public function create(
22
24
string $subject,
23
25
?DateTimeInterface $createdAt = null
24
-
): StrongRef {
26
+
): CreateRecordResponse {
25
27
$record = [
26
28
'$type' => BskyGraph::Follow->value,
27
29
'subject' => $subject, // DID
28
30
'createdAt' => ($createdAt ?? now())->format('c'),
29
31
];
30
32
31
-
$response = $this->atp->atproto->repo->createRecord(
32
-
repo: $this->atp->client->session()->did(),
33
+
return $this->atp->atproto->repo->createRecord(
33
34
collection: BskyGraph::Follow,
34
35
record: $record
35
36
);
36
-
37
-
return StrongRef::fromResponse($response->json());
38
37
}
39
38
40
39
/**
···
42
41
*
43
42
* @requires transition:generic OR (rpc:com.atproto.repo.deleteRecord AND repo:app.bsky.graph.follow?action=delete)
44
43
*/
45
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.deleteRecord')]
46
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.graph.follow?action=delete')]
47
-
public function delete(string $rkey): void
44
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.deleteRecord')]
45
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.graph.follow?action=delete')]
46
+
public function delete(string $rkey): DeleteRecordResponse
48
47
{
49
-
$this->atp->atproto->repo->deleteRecord(
50
-
repo: $this->atp->client->session()->did(),
48
+
return $this->atp->atproto->repo->deleteRecord(
51
49
collection: BskyGraph::Follow,
52
50
rkey: $rkey
53
51
);
···
58
56
*
59
57
* @requires transition:generic (rpc:com.atproto.repo.getRecord)
60
58
*/
61
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
62
-
public function get(string $rkey, ?string $cid = null): array
59
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
60
+
public function get(string $rkey, ?string $cid = null): Record
63
61
{
64
62
$response = $this->atp->atproto->repo->getRecord(
65
63
repo: $this->atp->client->session()->did(),
···
68
66
cid: $cid
69
67
);
70
68
71
-
return $response->json('value');
69
+
return Record::fromArrayRaw($response->toArray());
72
70
}
73
71
}
+15
-16
src/Client/Records/LikeRecordClient.php
+15
-16
src/Client/Records/LikeRecordClient.php
···
3
3
namespace SocialDept\AtpClient\Client\Records;
4
4
5
5
use DateTimeInterface;
6
-
use SocialDept\AtpClient\Attributes\RequiresScope;
6
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
7
7
use SocialDept\AtpClient\Client\Requests\Request;
8
+
use SocialDept\AtpClient\Data\Record;
9
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\CreateRecordResponse;
10
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\DeleteRecordResponse;
8
11
use SocialDept\AtpClient\Data\StrongRef;
9
12
use SocialDept\AtpClient\Enums\Nsid\BskyFeed;
10
13
use SocialDept\AtpClient\Enums\Scope;
···
16
19
*
17
20
* @requires transition:generic OR (rpc:com.atproto.repo.createRecord AND repo:app.bsky.feed.like?action=create)
18
21
*/
19
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
20
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.like?action=create')]
22
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
23
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.like?action=create')]
21
24
public function create(
22
25
StrongRef $subject,
23
26
?DateTimeInterface $createdAt = null
24
-
): StrongRef {
27
+
): CreateRecordResponse {
25
28
$record = [
26
29
'$type' => BskyFeed::Like->value,
27
30
'subject' => $subject->toArray(),
28
31
'createdAt' => ($createdAt ?? now())->format('c'),
29
32
];
30
33
31
-
$response = $this->atp->atproto->repo->createRecord(
32
-
repo: $this->atp->client->session()->did(),
34
+
return $this->atp->atproto->repo->createRecord(
33
35
collection: BskyFeed::Like,
34
36
record: $record
35
37
);
36
-
37
-
return StrongRef::fromResponse($response->json());
38
38
}
39
39
40
40
/**
···
42
42
*
43
43
* @requires transition:generic OR (rpc:com.atproto.repo.deleteRecord AND repo:app.bsky.feed.like?action=delete)
44
44
*/
45
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.deleteRecord')]
46
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.like?action=delete')]
47
-
public function delete(string $rkey): void
45
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.deleteRecord')]
46
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.like?action=delete')]
47
+
public function delete(string $rkey): DeleteRecordResponse
48
48
{
49
-
$this->atp->atproto->repo->deleteRecord(
50
-
repo: $this->atp->client->session()->did(),
49
+
return $this->atp->atproto->repo->deleteRecord(
51
50
collection: BskyFeed::Like,
52
51
rkey: $rkey
53
52
);
···
58
57
*
59
58
* @requires transition:generic (rpc:com.atproto.repo.getRecord)
60
59
*/
61
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
62
-
public function get(string $rkey, ?string $cid = null): array
60
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
61
+
public function get(string $rkey, ?string $cid = null): Record
63
62
{
64
63
$response = $this->atp->atproto->repo->getRecord(
65
64
repo: $this->atp->client->session()->did(),
···
68
67
cid: $cid
69
68
);
70
69
71
-
return $response->json('value');
70
+
return Record::fromArrayRaw($response->toArray());
72
71
}
73
72
}
+42
-159
src/Client/Records/PostRecordClient.php
+42
-159
src/Client/Records/PostRecordClient.php
···
3
3
namespace SocialDept\AtpClient\Client\Records;
4
4
5
5
use DateTimeInterface;
6
-
use SocialDept\AtpClient\Attributes\RequiresScope;
6
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
7
+
use SocialDept\AtpClient\Builders\PostBuilder;
7
8
use SocialDept\AtpClient\Client\Requests\Request;
8
9
use SocialDept\AtpClient\Contracts\Recordable;
10
+
use SocialDept\AtpClient\Data\Record;
11
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\CreateRecordResponse;
12
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\DeleteRecordResponse;
13
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\PutRecordResponse;
9
14
use SocialDept\AtpClient\Data\StrongRef;
10
15
use SocialDept\AtpClient\Enums\Nsid\BskyFeed;
11
16
use SocialDept\AtpClient\Enums\Scope;
12
17
use SocialDept\AtpClient\RichText\TextBuilder;
18
+
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\PostView;
13
19
14
20
class PostRecordClient extends Request
15
21
{
16
22
/**
23
+
* Create a new post builder bound to this client
24
+
*/
25
+
public function build(): PostBuilder
26
+
{
27
+
return PostBuilder::make()->for($this);
28
+
}
29
+
30
+
/**
17
31
* Create a post
18
32
*
19
33
* @requires transition:generic OR (rpc:com.atproto.repo.createRecord AND repo:app.bsky.feed.post?action=create)
20
34
*/
21
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
22
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=create')]
35
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
36
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=create')]
23
37
public function create(
24
38
string|array|Recordable $content,
25
39
?array $facets = null,
···
27
41
?array $reply = null,
28
42
?array $langs = null,
29
43
?DateTimeInterface $createdAt = null
30
-
): StrongRef {
44
+
): CreateRecordResponse {
31
45
// Handle different input types
32
46
if (is_string($content)) {
33
47
$record = [
···
40
54
$record = $content;
41
55
}
42
56
43
-
// Add optional fields
44
-
if ($embed) {
45
-
$record['embed'] = $embed;
57
+
// Add optional fields (only for non-Recordable inputs)
58
+
if (! ($content instanceof Recordable)) {
59
+
if ($embed) {
60
+
$record['embed'] = $embed;
61
+
}
62
+
if ($reply) {
63
+
$record['reply'] = $reply;
64
+
}
65
+
if ($langs) {
66
+
$record['langs'] = $langs;
67
+
}
46
68
}
47
-
if ($reply) {
48
-
$record['reply'] = $reply;
49
-
}
50
-
if ($langs) {
51
-
$record['langs'] = $langs;
52
-
}
69
+
53
70
if (! isset($record['createdAt'])) {
54
71
$record['createdAt'] = ($createdAt ?? now())->format('c');
55
72
}
···
59
76
$record['$type'] = BskyFeed::Post->value;
60
77
}
61
78
62
-
$response = $this->atp->atproto->repo->createRecord(
63
-
repo: $this->atp->client->session()->did(),
79
+
return $this->atp->atproto->repo->createRecord(
64
80
collection: BskyFeed::Post,
65
81
record: $record
66
82
);
67
-
68
-
return StrongRef::fromResponse($response->json());
69
83
}
70
84
71
85
/**
···
73
87
*
74
88
* @requires transition:generic OR (rpc:com.atproto.repo.putRecord AND repo:app.bsky.feed.post?action=update)
75
89
*/
76
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
77
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=update')]
78
-
public function update(string $rkey, array $record): StrongRef
90
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
91
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=update')]
92
+
public function update(string $rkey, array $record): PutRecordResponse
79
93
{
80
94
// Ensure $type is set
81
95
if (! isset($record['$type'])) {
82
96
$record['$type'] = BskyFeed::Post->value;
83
97
}
84
98
85
-
$response = $this->atp->atproto->repo->putRecord(
86
-
repo: $this->atp->client->session()->did(),
99
+
return $this->atp->atproto->repo->putRecord(
87
100
collection: BskyFeed::Post,
88
101
rkey: $rkey,
89
102
record: $record
90
103
);
91
-
92
-
return StrongRef::fromResponse($response->json());
93
104
}
94
105
95
106
/**
···
97
108
*
98
109
* @requires transition:generic OR (rpc:com.atproto.repo.deleteRecord AND repo:app.bsky.feed.post?action=delete)
99
110
*/
100
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.deleteRecord')]
101
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=delete')]
102
-
public function delete(string $rkey): void
111
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.deleteRecord')]
112
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=delete')]
113
+
public function delete(string $rkey): DeleteRecordResponse
103
114
{
104
-
$this->atp->atproto->repo->deleteRecord(
105
-
repo: $this->atp->client->session()->did(),
115
+
return $this->atp->atproto->repo->deleteRecord(
106
116
collection: BskyFeed::Post,
107
117
rkey: $rkey
108
118
);
···
113
123
*
114
124
* @requires transition:generic (rpc:com.atproto.repo.getRecord)
115
125
*/
116
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
117
-
public function get(string $rkey, ?string $cid = null): array
126
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
127
+
public function get(string $rkey, ?string $cid = null): Record
118
128
{
119
129
$response = $this->atp->atproto->repo->getRecord(
120
130
repo: $this->atp->client->session()->did(),
···
123
133
cid: $cid
124
134
);
125
135
126
-
return $response->json('value');
136
+
return Record::fromArrayRaw($response->toArray());
127
137
}
128
138
129
-
/**
130
-
* Create a reply to another post
131
-
*
132
-
* @requires transition:generic OR (rpc:com.atproto.repo.createRecord AND repo:app.bsky.feed.post?action=create)
133
-
*/
134
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
135
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=create')]
136
-
public function reply(
137
-
StrongRef $parent,
138
-
StrongRef $root,
139
-
string|array|Recordable $content,
140
-
?array $facets = null,
141
-
?array $embed = null,
142
-
?array $langs = null,
143
-
?DateTimeInterface $createdAt = null
144
-
): StrongRef {
145
-
$reply = [
146
-
'parent' => $parent->toArray(),
147
-
'root' => $root->toArray(),
148
-
];
149
-
150
-
return $this->create(
151
-
content: $content,
152
-
facets: $facets,
153
-
embed: $embed,
154
-
reply: $reply,
155
-
langs: $langs,
156
-
createdAt: $createdAt
157
-
);
158
-
}
159
-
160
-
/**
161
-
* Create a quote post (post with embedded post)
162
-
*
163
-
* @requires transition:generic OR (rpc:com.atproto.repo.createRecord AND repo:app.bsky.feed.post?action=create)
164
-
*/
165
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
166
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=create')]
167
-
public function quote(
168
-
StrongRef $quotedPost,
169
-
string|array|Recordable $content,
170
-
?array $facets = null,
171
-
?array $langs = null,
172
-
?DateTimeInterface $createdAt = null
173
-
): StrongRef {
174
-
$embed = [
175
-
'$type' => 'app.bsky.embed.record',
176
-
'record' => $quotedPost->toArray(),
177
-
];
178
-
179
-
return $this->create(
180
-
content: $content,
181
-
facets: $facets,
182
-
embed: $embed,
183
-
langs: $langs,
184
-
createdAt: $createdAt
185
-
);
186
-
}
187
-
188
-
/**
189
-
* Create a post with images
190
-
*
191
-
* @requires transition:generic OR (rpc:com.atproto.repo.createRecord AND repo:app.bsky.feed.post?action=create)
192
-
*/
193
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
194
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=create')]
195
-
public function withImages(
196
-
string|array|Recordable $content,
197
-
array $images,
198
-
?array $facets = null,
199
-
?array $langs = null,
200
-
?DateTimeInterface $createdAt = null
201
-
): StrongRef {
202
-
$embed = [
203
-
'$type' => 'app.bsky.embed.images',
204
-
'images' => $images,
205
-
];
206
-
207
-
return $this->create(
208
-
content: $content,
209
-
facets: $facets,
210
-
embed: $embed,
211
-
langs: $langs,
212
-
createdAt: $createdAt
213
-
);
214
-
}
215
-
216
-
/**
217
-
* Create a post with external link embed
218
-
*
219
-
* @requires transition:generic OR (rpc:com.atproto.repo.createRecord AND repo:app.bsky.feed.post?action=create)
220
-
*/
221
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.createRecord')]
222
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.feed.post?action=create')]
223
-
public function withLink(
224
-
string|array|Recordable $content,
225
-
string $uri,
226
-
string $title,
227
-
string $description,
228
-
?string $thumbBlob = null,
229
-
?array $facets = null,
230
-
?array $langs = null,
231
-
?DateTimeInterface $createdAt = null
232
-
): StrongRef {
233
-
$external = [
234
-
'uri' => $uri,
235
-
'title' => $title,
236
-
'description' => $description,
237
-
];
238
-
239
-
if ($thumbBlob) {
240
-
$external['thumb'] = $thumbBlob;
241
-
}
242
-
243
-
$embed = [
244
-
'$type' => 'app.bsky.embed.external',
245
-
'external' => $external,
246
-
];
247
-
248
-
return $this->create(
249
-
content: $content,
250
-
facets: $facets,
251
-
embed: $embed,
252
-
langs: $langs,
253
-
createdAt: $createdAt
254
-
);
255
-
}
256
139
}
+23
-25
src/Client/Records/ProfileRecordClient.php
+23
-25
src/Client/Records/ProfileRecordClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Records;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
6
use SocialDept\AtpClient\Client\Requests\Request;
7
-
use SocialDept\AtpClient\Data\StrongRef;
7
+
use SocialDept\AtpClient\Data\Record;
8
+
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\PutRecordResponse;
8
9
use SocialDept\AtpClient\Enums\Nsid\BskyActor;
9
10
use SocialDept\AtpClient\Enums\Scope;
10
11
···
15
16
*
16
17
* @requires transition:generic OR (rpc:com.atproto.repo.putRecord AND repo:app.bsky.actor.profile?action=update)
17
18
*/
18
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
19
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
20
-
public function update(array $profile): StrongRef
19
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
20
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
21
+
public function update(array $profile): PutRecordResponse
21
22
{
22
23
// Ensure $type is set
23
24
if (! isset($profile['$type'])) {
24
25
$profile['$type'] = BskyActor::Profile->value;
25
26
}
26
27
27
-
$response = $this->atp->atproto->repo->putRecord(
28
-
repo: $this->atp->client->session()->did(),
28
+
return $this->atp->atproto->repo->putRecord(
29
29
collection: BskyActor::Profile,
30
30
rkey: 'self', // Profile records always use 'self' as rkey
31
31
record: $profile
32
32
);
33
-
34
-
return StrongRef::fromResponse($response->json());
35
33
}
36
34
37
35
/**
···
39
37
*
40
38
* @requires transition:generic (rpc:com.atproto.repo.getRecord)
41
39
*/
42
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
43
-
public function get(): array
40
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
41
+
public function get(): Record
44
42
{
45
43
$response = $this->atp->atproto->repo->getRecord(
46
44
repo: $this->atp->client->session()->did(),
···
48
46
rkey: 'self'
49
47
);
50
48
51
-
return $response->json('value');
49
+
return Record::fromArrayRaw($response->toArray());
52
50
}
53
51
54
52
/**
···
56
54
*
57
55
* @requires transition:generic OR (rpc:com.atproto.repo.putRecord AND repo:app.bsky.actor.profile?action=update)
58
56
*/
59
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
60
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
61
-
public function updateDisplayName(string $displayName): StrongRef
57
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
58
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
59
+
public function updateDisplayName(string $displayName): PutRecordResponse
62
60
{
63
61
$profile = $this->getOrCreateProfile();
64
62
$profile['displayName'] = $displayName;
···
71
69
*
72
70
* @requires transition:generic OR (rpc:com.atproto.repo.putRecord AND repo:app.bsky.actor.profile?action=update)
73
71
*/
74
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
75
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
76
-
public function updateDescription(string $description): StrongRef
72
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
73
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
74
+
public function updateDescription(string $description): PutRecordResponse
77
75
{
78
76
$profile = $this->getOrCreateProfile();
79
77
$profile['description'] = $description;
···
86
84
*
87
85
* @requires transition:generic OR (rpc:com.atproto.repo.putRecord AND repo:app.bsky.actor.profile?action=update)
88
86
*/
89
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
90
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
91
-
public function updateAvatar(array $avatarBlob): StrongRef
87
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
88
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
89
+
public function updateAvatar(array $avatarBlob): PutRecordResponse
92
90
{
93
91
$profile = $this->getOrCreateProfile();
94
92
$profile['avatar'] = $avatarBlob;
···
101
99
*
102
100
* @requires transition:generic OR (rpc:com.atproto.repo.putRecord AND repo:app.bsky.actor.profile?action=update)
103
101
*/
104
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
105
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
106
-
public function updateBanner(array $bannerBlob): StrongRef
102
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.putRecord')]
103
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'repo:app.bsky.actor.profile?action=update')]
104
+
public function updateBanner(array $bannerBlob): PutRecordResponse
107
105
{
108
106
$profile = $this->getOrCreateProfile();
109
107
$profile['banner'] = $bannerBlob;
···
117
115
protected function getOrCreateProfile(): array
118
116
{
119
117
try {
120
-
return $this->get();
118
+
return $this->get()->value;
121
119
} catch (\Exception $e) {
122
120
// Profile doesn't exist, return empty structure
123
121
return [
+11
-8
src/Client/Requests/Atproto/IdentityRequestClient.php
+11
-8
src/Client/Requests/Atproto/IdentityRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Atproto;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
6
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
7
use SocialDept\AtpClient\Client\Requests\Request;
8
+
use SocialDept\AtpClient\Data\Responses\Atproto\Identity\ResolveHandleResponse;
9
+
use SocialDept\AtpClient\Data\Responses\EmptyResponse;
7
10
use SocialDept\AtpClient\Enums\Nsid\AtprotoIdentity;
8
11
use SocialDept\AtpClient\Enums\Scope;
9
12
···
12
15
/**
13
16
* Resolve handle to DID
14
17
*
15
-
* @requires transition:generic (rpc:com.atproto.identity.resolveHandle)
16
-
*
17
18
* @see https://docs.bsky.app/docs/api/com-atproto-identity-resolve-handle
18
19
*/
19
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.identity.resolveHandle')]
20
-
public function resolveHandle(string $handle): string
20
+
#[PublicEndpoint]
21
+
public function resolveHandle(string $handle): ResolveHandleResponse
21
22
{
22
23
$response = $this->atp->client->get(
23
24
endpoint: AtprotoIdentity::ResolveHandle,
24
25
params: compact('handle')
25
26
);
26
27
27
-
return $response->json()['did'];
28
+
return ResolveHandleResponse::fromArray($response->json());
28
29
}
29
30
30
31
/**
···
34
35
*
35
36
* @see https://docs.bsky.app/docs/api/com-atproto-identity-update-handle
36
37
*/
37
-
#[RequiresScope(Scope::Atproto, granular: 'identity:handle')]
38
-
public function updateHandle(string $handle): void
38
+
#[ScopedEndpoint(Scope::Atproto, granular: 'identity:handle')]
39
+
public function updateHandle(string $handle): EmptyResponse
39
40
{
40
41
$this->atp->client->post(
41
42
endpoint: AtprotoIdentity::UpdateHandle,
42
43
body: compact('handle')
43
44
);
45
+
46
+
return new EmptyResponse;
44
47
}
45
48
}
+23
-22
src/Client/Requests/Atproto/RepoRequestClient.php
+23
-22
src/Client/Requests/Atproto/RepoRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Atproto;
4
4
5
+
use BackedEnum;
5
6
use Illuminate\Http\UploadedFile;
6
7
use InvalidArgumentException;
7
-
use SocialDept\AtpClient\Attributes\RequiresScope;
8
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
9
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
8
10
use SocialDept\AtpClient\Auth\ScopeChecker;
9
11
use SocialDept\AtpClient\Client\Requests\Request;
10
12
use SocialDept\AtpClient\Data\Responses\Atproto\Repo\CreateRecordResponse;
···
28
30
*
29
31
* @see https://docs.bsky.app/docs/api/com-atproto-repo-create-record
30
32
*/
31
-
#[RequiresScope(Scope::TransitionGeneric, description: 'Create records in repository')]
33
+
#[ScopedEndpoint(Scope::TransitionGeneric, description: 'Create records in repository')]
32
34
public function createRecord(
33
-
string $repo,
34
-
string $collection,
35
+
string|BackedEnum $collection,
35
36
array $record,
36
37
?string $rkey = null,
37
38
bool $validate = true,
38
39
?string $swapCommit = null
39
40
): CreateRecordResponse {
41
+
$repo = $this->atp->client->session()->did();
42
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
40
43
$this->checkCollectionScope($collection, 'create');
41
44
42
45
$response = $this->atp->client->post(
···
57
60
*
58
61
* @see https://docs.bsky.app/docs/api/com-atproto-repo-delete-record
59
62
*/
60
-
#[RequiresScope(Scope::TransitionGeneric, description: 'Delete records from repository')]
63
+
#[ScopedEndpoint(Scope::TransitionGeneric, description: 'Delete records from repository')]
61
64
public function deleteRecord(
62
-
string $repo,
63
-
string $collection,
65
+
string|BackedEnum $collection,
64
66
string $rkey,
65
67
?string $swapRecord = null,
66
68
?string $swapCommit = null
67
69
): DeleteRecordResponse {
70
+
$repo = $this->atp->client->session()->did();
71
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
68
72
$this->checkCollectionScope($collection, 'delete');
69
73
70
74
$response = $this->atp->client->post(
···
85
89
*
86
90
* @see https://docs.bsky.app/docs/api/com-atproto-repo-put-record
87
91
*/
88
-
#[RequiresScope(Scope::TransitionGeneric, description: 'Update records in repository')]
92
+
#[ScopedEndpoint(Scope::TransitionGeneric, description: 'Update records in repository')]
89
93
public function putRecord(
90
-
string $repo,
91
-
string $collection,
94
+
string|BackedEnum $collection,
92
95
string $rkey,
93
96
array $record,
94
97
bool $validate = true,
95
98
?string $swapRecord = null,
96
99
?string $swapCommit = null
97
100
): PutRecordResponse {
101
+
$repo = $this->atp->client->session()->did();
102
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
98
103
$this->checkCollectionScope($collection, 'update');
99
104
100
105
$response = $this->atp->client->post(
···
111
116
/**
112
117
* Get a record
113
118
*
114
-
* @requires transition:generic (rpc:com.atproto.repo.getRecord)
115
-
*
116
119
* @see https://docs.bsky.app/docs/api/com-atproto-repo-get-record
117
120
*/
118
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.getRecord')]
121
+
#[PublicEndpoint]
119
122
public function getRecord(
120
123
string $repo,
121
-
string $collection,
124
+
string|BackedEnum $collection,
122
125
string $rkey,
123
126
?string $cid = null
124
127
): GetRecordResponse {
128
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
125
129
$response = $this->atp->client->get(
126
130
endpoint: AtprotoRepo::GetRecord,
127
131
params: compact('repo', 'collection', 'rkey', 'cid')
···
132
136
133
137
/**
134
138
* List records in a collection
135
-
*
136
-
* @requires transition:generic (rpc:com.atproto.repo.listRecords)
137
139
*
138
140
* @see https://docs.bsky.app/docs/api/com-atproto-repo-list-records
139
141
*/
140
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.listRecords')]
142
+
#[PublicEndpoint]
141
143
public function listRecords(
142
144
string $repo,
143
-
string $collection,
145
+
string|BackedEnum $collection,
144
146
int $limit = 50,
145
147
?string $cursor = null,
146
148
bool $reverse = false
147
149
): ListRecordsResponse {
150
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
148
151
$response = $this->atp->client->get(
149
152
endpoint: AtprotoRepo::ListRecords,
150
153
params: compact('repo', 'collection', 'limit', 'cursor', 'reverse')
···
167
170
*
168
171
* @see https://docs.bsky.app/docs/api/com-atproto-repo-upload-blob
169
172
*/
170
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'blob:*/*')]
173
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'blob:*/*')]
171
174
public function uploadBlob(UploadedFile|SplFileInfo|string $file, ?string $mimeType = null): BlobReference
172
175
{
173
176
// Handle different input types
···
194
197
/**
195
198
* Describe the repository
196
199
*
197
-
* @requires transition:generic (rpc:com.atproto.repo.describeRepo)
198
-
*
199
200
* @see https://docs.bsky.app/docs/api/com-atproto-repo-describe-repo
200
201
*/
201
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:com.atproto.repo.describeRepo')]
202
+
#[PublicEndpoint]
202
203
public function describeRepo(string $repo): DescribeRepoResponse
203
204
{
204
205
$response = $this->atp->client->get(
+4
-5
src/Client/Requests/Atproto/ServerRequestClient.php
+4
-5
src/Client/Requests/Atproto/ServerRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Atproto;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
6
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
7
use SocialDept\AtpClient\Client\Requests\Request;
7
8
use SocialDept\AtpClient\Data\Responses\Atproto\Server\DescribeServerResponse;
8
9
use SocialDept\AtpClient\Data\Responses\Atproto\Server\GetSessionResponse;
···
18
19
*
19
20
* @see https://docs.bsky.app/docs/api/com-atproto-server-get-session
20
21
*/
21
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.server.getSession')]
22
+
#[ScopedEndpoint(Scope::Atproto, granular: 'rpc:com.atproto.server.getSession')]
22
23
public function getSession(): GetSessionResponse
23
24
{
24
25
$response = $this->atp->client->get(
···
31
32
/**
32
33
* Describe server
33
34
*
34
-
* @requires atproto (rpc:com.atproto.server.describeServer)
35
-
*
36
35
* @see https://docs.bsky.app/docs/api/com-atproto-server-describe-server
37
36
*/
38
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.server.describeServer')]
37
+
#[PublicEndpoint]
39
38
public function describeServer(): DescribeServerResponse
40
39
{
41
40
$response = $this->atp->client->get(
+35
-27
src/Client/Requests/Atproto/SyncRequestClient.php
+35
-27
src/Client/Requests/Atproto/SyncRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Atproto;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use BackedEnum;
6
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
6
7
use SocialDept\AtpClient\Client\Requests\Request;
7
8
use SocialDept\AtpClient\Data\Responses\Atproto\Sync\GetRepoStatusResponse;
8
9
use SocialDept\AtpClient\Data\Responses\Atproto\Sync\ListBlobsResponse;
10
+
use SocialDept\AtpClient\Data\Responses\Atproto\Sync\ListReposByCollectionResponse;
9
11
use SocialDept\AtpClient\Data\Responses\Atproto\Sync\ListReposResponse;
10
12
use SocialDept\AtpClient\Enums\Nsid\AtprotoSync;
11
-
use SocialDept\AtpClient\Enums\Scope;
12
13
use SocialDept\AtpClient\Http\Response;
13
14
use SocialDept\AtpSchema\Generated\Com\Atproto\Repo\Defs\CommitMeta;
14
15
···
17
18
/**
18
19
* Get a blob associated with a given account
19
20
*
20
-
* @requires atproto (rpc:com.atproto.sync.getBlob)
21
-
*
22
21
* @see https://docs.bsky.app/docs/api/com-atproto-sync-get-blob
23
22
*/
24
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.getBlob')]
23
+
#[PublicEndpoint]
25
24
public function getBlob(string $did, string $cid): Response
26
25
{
27
26
return $this->atp->client->get(
···
33
32
/**
34
33
* Download a repository export as CAR file
35
34
*
36
-
* @requires atproto (rpc:com.atproto.sync.getRepo)
37
-
*
38
35
* @see https://docs.bsky.app/docs/api/com-atproto-sync-get-repo
39
36
*/
40
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.getRepo')]
37
+
#[PublicEndpoint]
41
38
public function getRepo(string $did, ?string $since = null): Response
42
39
{
43
40
return $this->atp->client->get(
···
49
46
/**
50
47
* Enumerates all the DID, rev, and commit CID for all repos hosted by this service
51
48
*
52
-
* @requires atproto (rpc:com.atproto.sync.listRepos)
53
-
*
54
49
* @see https://docs.bsky.app/docs/api/com-atproto-sync-list-repos
55
50
*/
56
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.listRepos')]
51
+
#[PublicEndpoint]
57
52
public function listRepos(int $limit = 500, ?string $cursor = null): ListReposResponse
58
53
{
59
54
$response = $this->atp->client->get(
···
65
60
}
66
61
67
62
/**
68
-
* Get the current commit CID & revision of the specified repo
63
+
* Enumerates all the DIDs with records in a specific collection
69
64
*
70
-
* @requires atproto (rpc:com.atproto.sync.getLatestCommit)
65
+
* @see https://docs.bsky.app/docs/api/com-atproto-sync-list-repos-by-collection
66
+
*/
67
+
#[PublicEndpoint]
68
+
public function listReposByCollection(
69
+
string|BackedEnum $collection,
70
+
int $limit = 500,
71
+
?string $cursor = null
72
+
): ListReposByCollectionResponse {
73
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
74
+
75
+
$response = $this->atp->client->get(
76
+
endpoint: AtprotoSync::ListReposByCollection,
77
+
params: compact('collection', 'limit', 'cursor')
78
+
);
79
+
80
+
return ListReposByCollectionResponse::fromArray($response->json());
81
+
}
82
+
83
+
/**
84
+
* Get the current commit CID & revision of the specified repo
71
85
*
72
86
* @see https://docs.bsky.app/docs/api/com-atproto-sync-get-latest-commit
73
87
*/
74
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.getLatestCommit')]
88
+
#[PublicEndpoint]
75
89
public function getLatestCommit(string $did): CommitMeta
76
90
{
77
91
$response = $this->atp->client->get(
···
85
99
/**
86
100
* Get data blocks needed to prove the existence or non-existence of record
87
101
*
88
-
* @requires atproto (rpc:com.atproto.sync.getRecord)
89
-
*
90
102
* @see https://docs.bsky.app/docs/api/com-atproto-sync-get-record
91
103
*/
92
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.getRecord')]
93
-
public function getRecord(string $did, string $collection, string $rkey): Response
104
+
#[PublicEndpoint]
105
+
public function getRecord(string $did, string|BackedEnum $collection, string $rkey): Response
94
106
{
107
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
108
+
95
109
return $this->atp->client->get(
96
110
endpoint: AtprotoSync::GetRecord,
97
111
params: compact('did', 'collection', 'rkey')
···
101
115
/**
102
116
* List blob CIDs for an account, since some repo revision
103
117
*
104
-
* @requires atproto (rpc:com.atproto.sync.listBlobs)
105
-
*
106
118
* @see https://docs.bsky.app/docs/api/com-atproto-sync-list-blobs
107
119
*/
108
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.listBlobs')]
120
+
#[PublicEndpoint]
109
121
public function listBlobs(
110
122
string $did,
111
123
?string $since = null,
···
123
135
/**
124
136
* Get data blocks from a given repo, by CID
125
137
*
126
-
* @requires atproto (rpc:com.atproto.sync.getBlocks)
127
-
*
128
138
* @see https://docs.bsky.app/docs/api/com-atproto-sync-get-blocks
129
139
*/
130
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.getBlocks')]
140
+
#[PublicEndpoint]
131
141
public function getBlocks(string $did, array $cids): Response
132
142
{
133
143
return $this->atp->client->get(
···
139
149
/**
140
150
* Get the hosting status for a repository, on this server
141
151
*
142
-
* @requires atproto (rpc:com.atproto.sync.getRepoStatus)
143
-
*
144
152
* @see https://docs.bsky.app/docs/api/com-atproto-sync-get-repo-status
145
153
*/
146
-
#[RequiresScope(Scope::Atproto, granular: 'rpc:com.atproto.sync.getRepoStatus')]
154
+
#[PublicEndpoint]
147
155
public function getRepoStatus(string $did): GetRepoStatusResponse
148
156
{
149
157
$response = $this->atp->client->get(
+71
-6
src/Client/Requests/Bsky/ActorRequestClient.php
+71
-6
src/Client/Requests/Bsky/ActorRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Bsky;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
6
6
use SocialDept\AtpClient\Client\Requests\Request;
7
+
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\GetProfilesResponse;
8
+
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\GetSuggestionsResponse;
9
+
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\SearchActorsResponse;
10
+
use SocialDept\AtpClient\Data\Responses\Bsky\Actor\SearchActorsTypeaheadResponse;
7
11
use SocialDept\AtpClient\Enums\Nsid\BskyActor;
8
-
use SocialDept\AtpClient\Enums\Scope;
9
12
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileViewDetailed;
10
13
11
14
class ActorRequestClient extends Request
···
13
16
/**
14
17
* Get actor profile
15
18
*
16
-
* @requires transition:generic (rpc:app.bsky.actor.getProfile)
17
-
*
18
19
* @see https://docs.bsky.app/docs/api/app-bsky-actor-get-profile
19
20
*/
20
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:app.bsky.actor.getProfile')]
21
+
#[PublicEndpoint]
21
22
public function getProfile(string $actor): ProfileViewDetailed
22
23
{
23
24
$response = $this->atp->client->get(
···
25
26
params: compact('actor')
26
27
);
27
28
28
-
return ProfileViewDetailed::fromArray($response->json());
29
+
return ProfileViewDetailed::fromArray($response->toArray());
30
+
}
31
+
32
+
/**
33
+
* Get multiple actor profiles
34
+
*
35
+
* @see https://docs.bsky.app/docs/api/app-bsky-actor-get-profiles
36
+
*/
37
+
#[PublicEndpoint]
38
+
public function getProfiles(array $actors): GetProfilesResponse
39
+
{
40
+
$response = $this->atp->client->get(
41
+
endpoint: BskyActor::GetProfiles,
42
+
params: compact('actors')
43
+
);
44
+
45
+
return GetProfilesResponse::fromArray($response->json());
46
+
}
47
+
48
+
/**
49
+
* Get suggestions for actors to follow
50
+
*
51
+
* @see https://docs.bsky.app/docs/api/app-bsky-actor-get-suggestions
52
+
*/
53
+
#[PublicEndpoint]
54
+
public function getSuggestions(int $limit = 50, ?string $cursor = null): GetSuggestionsResponse
55
+
{
56
+
$response = $this->atp->client->get(
57
+
endpoint: BskyActor::GetSuggestions,
58
+
params: compact('limit', 'cursor')
59
+
);
60
+
61
+
return GetSuggestionsResponse::fromArray($response->json());
62
+
}
63
+
64
+
/**
65
+
* Search for actors
66
+
*
67
+
* @see https://docs.bsky.app/docs/api/app-bsky-actor-search-actors
68
+
*/
69
+
#[PublicEndpoint]
70
+
public function searchActors(string $q, int $limit = 25, ?string $cursor = null): SearchActorsResponse
71
+
{
72
+
$response = $this->atp->client->get(
73
+
endpoint: BskyActor::SearchActors,
74
+
params: compact('q', 'limit', 'cursor')
75
+
);
76
+
77
+
return SearchActorsResponse::fromArray($response->json());
78
+
}
79
+
80
+
/**
81
+
* Search for actors matching a prefix (typeahead/autocomplete)
82
+
*
83
+
* @see https://docs.bsky.app/docs/api/app-bsky-actor-search-actors-typeahead
84
+
*/
85
+
#[PublicEndpoint]
86
+
public function searchActorsTypeahead(string $q, int $limit = 10): SearchActorsTypeaheadResponse
87
+
{
88
+
$response = $this->atp->client->get(
89
+
endpoint: BskyActor::SearchActorsTypeahead,
90
+
params: compact('q', 'limit')
91
+
);
92
+
93
+
return SearchActorsTypeaheadResponse::fromArray($response->json());
29
94
}
30
95
}
+186
-37
src/Client/Requests/Bsky/FeedRequestClient.php
+186
-37
src/Client/Requests/Bsky/FeedRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Bsky;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
6
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
7
use SocialDept\AtpClient\Client\Requests\Request;
8
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\DescribeFeedGeneratorResponse;
9
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetActorFeedsResponse;
10
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetActorLikesResponse;
7
11
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetAuthorFeedResponse;
12
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetFeedGeneratorResponse;
13
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetFeedGeneratorsResponse;
14
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetFeedResponse;
8
15
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetLikesResponse;
16
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetPostsResponse;
9
17
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetPostThreadResponse;
18
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetQuotesResponse;
10
19
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetRepostedByResponse;
20
+
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetSuggestedFeedsResponse;
11
21
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\GetTimelineResponse;
12
22
use SocialDept\AtpClient\Data\Responses\Bsky\Feed\SearchPostsResponse;
13
23
use SocialDept\AtpClient\Enums\Nsid\BskyFeed;
···
16
26
class FeedRequestClient extends Request
17
27
{
18
28
/**
19
-
* Get timeline feed
29
+
* Describe feed generator
20
30
*
21
-
* @requires transition:generic (rpc:app.bsky.feed.getTimeline)
31
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-describe-feed-generator
32
+
*/
33
+
#[PublicEndpoint]
34
+
public function describeFeedGenerator(): DescribeFeedGeneratorResponse
35
+
{
36
+
$response = $this->atp->client->get(
37
+
endpoint: BskyFeed::DescribeFeedGenerator
38
+
);
39
+
40
+
return DescribeFeedGeneratorResponse::fromArray($response->json());
41
+
}
42
+
43
+
/**
44
+
* Get timeline feed (requires authentication)
22
45
*
23
46
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-timeline
24
47
*/
25
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getTimeline')]
48
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getTimeline')]
26
49
public function getTimeline(int $limit = 50, ?string $cursor = null): GetTimelineResponse
27
50
{
28
51
$response = $this->atp->client->get(
···
36
59
/**
37
60
* Get author feed
38
61
*
39
-
* @requires transition:generic (rpc:app.bsky.feed.getAuthorFeed)
40
-
*
41
62
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-author-feed
42
63
*/
43
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getAuthorFeed')]
64
+
#[PublicEndpoint]
44
65
public function getAuthorFeed(
45
66
string $actor,
46
67
int $limit = 50,
47
-
?string $cursor = null
68
+
?string $cursor = null,
69
+
?string $filter = null
48
70
): GetAuthorFeedResponse {
49
71
$response = $this->atp->client->get(
50
72
endpoint: BskyFeed::GetAuthorFeed,
73
+
params: compact('actor', 'limit', 'cursor', 'filter')
74
+
);
75
+
76
+
return GetAuthorFeedResponse::fromArray($response->json());
77
+
}
78
+
79
+
/**
80
+
* Get feeds created by an actor
81
+
*
82
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-actor-feeds
83
+
*/
84
+
#[PublicEndpoint]
85
+
public function getActorFeeds(string $actor, int $limit = 50, ?string $cursor = null): GetActorFeedsResponse
86
+
{
87
+
$response = $this->atp->client->get(
88
+
endpoint: BskyFeed::GetActorFeeds,
51
89
params: compact('actor', 'limit', 'cursor')
52
90
);
53
91
54
-
return GetAuthorFeedResponse::fromArray($response->json());
92
+
return GetActorFeedsResponse::fromArray($response->json());
93
+
}
94
+
95
+
/**
96
+
* Get posts liked by an actor
97
+
*
98
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-actor-likes
99
+
*/
100
+
#[PublicEndpoint]
101
+
public function getActorLikes(string $actor, int $limit = 50, ?string $cursor = null): GetActorLikesResponse
102
+
{
103
+
$response = $this->atp->client->get(
104
+
endpoint: BskyFeed::GetActorLikes,
105
+
params: compact('actor', 'limit', 'cursor')
106
+
);
107
+
108
+
return GetActorLikesResponse::fromArray($response->json());
109
+
}
110
+
111
+
/**
112
+
* Get a feed
113
+
*
114
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-feed
115
+
*/
116
+
#[PublicEndpoint]
117
+
public function getFeed(string $feed, int $limit = 50, ?string $cursor = null): GetFeedResponse
118
+
{
119
+
$response = $this->atp->client->get(
120
+
endpoint: BskyFeed::GetFeed,
121
+
params: compact('feed', 'limit', 'cursor')
122
+
);
123
+
124
+
return GetFeedResponse::fromArray($response->json());
125
+
}
126
+
127
+
/**
128
+
* Get a feed generator
129
+
*
130
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-feed-generator
131
+
*/
132
+
#[PublicEndpoint]
133
+
public function getFeedGenerator(string $feed): GetFeedGeneratorResponse
134
+
{
135
+
$response = $this->atp->client->get(
136
+
endpoint: BskyFeed::GetFeedGenerator,
137
+
params: compact('feed')
138
+
);
139
+
140
+
return GetFeedGeneratorResponse::fromArray($response->json());
141
+
}
142
+
143
+
/**
144
+
* Get multiple feed generators
145
+
*
146
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-feed-generators
147
+
*/
148
+
#[PublicEndpoint]
149
+
public function getFeedGenerators(array $feeds): GetFeedGeneratorsResponse
150
+
{
151
+
$response = $this->atp->client->get(
152
+
endpoint: BskyFeed::GetFeedGenerators,
153
+
params: compact('feeds')
154
+
);
155
+
156
+
return GetFeedGeneratorsResponse::fromArray($response->json());
55
157
}
56
158
57
159
/**
58
160
* Get post thread
59
161
*
60
-
* @requires transition:generic (rpc:app.bsky.feed.getPostThread)
61
-
*
62
162
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-post-thread
63
163
*/
64
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getPostThread')]
65
-
public function getPostThread(string $uri, int $depth = 6): GetPostThreadResponse
164
+
#[PublicEndpoint]
165
+
public function getPostThread(string $uri, int $depth = 6, int $parentHeight = 80): GetPostThreadResponse
66
166
{
67
167
$response = $this->atp->client->get(
68
168
endpoint: BskyFeed::GetPostThread,
69
-
params: compact('uri', 'depth')
169
+
params: compact('uri', 'depth', 'parentHeight')
70
170
);
71
171
72
172
return GetPostThreadResponse::fromArray($response->json());
73
173
}
74
174
75
175
/**
76
-
* Search posts
176
+
* Get multiple posts by URI
77
177
*
78
-
* @requires transition:generic (rpc:app.bsky.feed.searchPosts)
79
-
*
80
-
* @see https://docs.bsky.app/docs/api/app-bsky-feed-search-posts
178
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-posts
81
179
*/
82
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.searchPosts')]
83
-
public function searchPosts(
84
-
string $q,
85
-
int $limit = 25,
86
-
?string $cursor = null
87
-
): SearchPostsResponse {
180
+
#[PublicEndpoint]
181
+
public function getPosts(array $uris): GetPostsResponse
182
+
{
88
183
$response = $this->atp->client->get(
89
-
endpoint: BskyFeed::SearchPosts,
90
-
params: compact('q', 'limit', 'cursor')
184
+
endpoint: BskyFeed::GetPosts,
185
+
params: compact('uris')
91
186
);
92
187
93
-
return SearchPostsResponse::fromArray($response->json());
188
+
return GetPostsResponse::fromArray($response->json());
94
189
}
95
190
96
191
/**
97
192
* Get likes for a post
98
-
*
99
-
* @requires transition:generic (rpc:app.bsky.feed.getLikes)
100
193
*
101
194
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-likes
102
195
*/
103
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getLikes')]
196
+
#[PublicEndpoint]
104
197
public function getLikes(
105
198
string $uri,
106
199
int $limit = 50,
107
-
?string $cursor = null
200
+
?string $cursor = null,
201
+
?string $cid = null
108
202
): GetLikesResponse {
109
203
$response = $this->atp->client->get(
110
204
endpoint: BskyFeed::GetLikes,
111
-
params: compact('uri', 'limit', 'cursor')
205
+
params: compact('uri', 'limit', 'cursor', 'cid')
112
206
);
113
207
114
208
return GetLikesResponse::fromArray($response->json());
115
209
}
116
210
117
211
/**
212
+
* Get quotes of a post
213
+
*
214
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-quotes
215
+
*/
216
+
#[PublicEndpoint]
217
+
public function getQuotes(
218
+
string $uri,
219
+
int $limit = 50,
220
+
?string $cursor = null,
221
+
?string $cid = null
222
+
): GetQuotesResponse {
223
+
$response = $this->atp->client->get(
224
+
endpoint: BskyFeed::GetQuotes,
225
+
params: compact('uri', 'limit', 'cursor', 'cid')
226
+
);
227
+
228
+
return GetQuotesResponse::fromArray($response->json());
229
+
}
230
+
231
+
/**
118
232
* Get reposts for a post
119
233
*
120
-
* @requires transition:generic (rpc:app.bsky.feed.getRepostedBy)
121
-
*
122
234
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-reposted-by
123
235
*/
124
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getRepostedBy')]
236
+
#[PublicEndpoint]
125
237
public function getRepostedBy(
126
238
string $uri,
127
239
int $limit = 50,
128
-
?string $cursor = null
240
+
?string $cursor = null,
241
+
?string $cid = null
129
242
): GetRepostedByResponse {
130
243
$response = $this->atp->client->get(
131
244
endpoint: BskyFeed::GetRepostedBy,
132
-
params: compact('uri', 'limit', 'cursor')
245
+
params: compact('uri', 'limit', 'cursor', 'cid')
133
246
);
134
247
135
248
return GetRepostedByResponse::fromArray($response->json());
249
+
}
250
+
251
+
/**
252
+
* Get suggested feeds
253
+
*
254
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-get-suggested-feeds
255
+
*/
256
+
#[PublicEndpoint]
257
+
public function getSuggestedFeeds(int $limit = 50, ?string $cursor = null): GetSuggestedFeedsResponse
258
+
{
259
+
$response = $this->atp->client->get(
260
+
endpoint: BskyFeed::GetSuggestedFeeds,
261
+
params: compact('limit', 'cursor')
262
+
);
263
+
264
+
return GetSuggestedFeedsResponse::fromArray($response->json());
265
+
}
266
+
267
+
/**
268
+
* Search posts
269
+
*
270
+
* @see https://docs.bsky.app/docs/api/app-bsky-feed-search-posts
271
+
*/
272
+
#[PublicEndpoint]
273
+
public function searchPosts(
274
+
string $q,
275
+
int $limit = 25,
276
+
?string $cursor = null,
277
+
?string $sort = null
278
+
): SearchPostsResponse {
279
+
$response = $this->atp->client->get(
280
+
endpoint: BskyFeed::SearchPosts,
281
+
params: compact('q', 'limit', 'cursor', 'sort')
282
+
);
283
+
284
+
return SearchPostsResponse::fromArray($response->json());
136
285
}
137
286
}
+163
src/Client/Requests/Bsky/GraphRequestClient.php
+163
src/Client/Requests/Bsky/GraphRequestClient.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Client\Requests\Bsky;
4
+
5
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
6
+
use SocialDept\AtpClient\Client\Requests\Request;
7
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetFollowersResponse;
8
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetFollowsResponse;
9
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetKnownFollowersResponse;
10
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetListResponse;
11
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetListsResponse;
12
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetRelationshipsResponse;
13
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetStarterPacksResponse;
14
+
use SocialDept\AtpClient\Data\Responses\Bsky\Graph\GetSuggestedFollowsByActorResponse;
15
+
use SocialDept\AtpClient\Enums\Nsid\BskyGraph;
16
+
use SocialDept\AtpSchema\Generated\App\Bsky\Graph\Defs\StarterPackView;
17
+
18
+
class GraphRequestClient extends Request
19
+
{
20
+
/**
21
+
* Get followers of an actor
22
+
*
23
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-followers
24
+
*/
25
+
#[PublicEndpoint]
26
+
public function getFollowers(string $actor, int $limit = 50, ?string $cursor = null): GetFollowersResponse
27
+
{
28
+
$response = $this->atp->client->get(
29
+
endpoint: BskyGraph::GetFollowers,
30
+
params: compact('actor', 'limit', 'cursor')
31
+
);
32
+
33
+
return GetFollowersResponse::fromArray($response->json());
34
+
}
35
+
36
+
/**
37
+
* Get accounts that an actor follows
38
+
*
39
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-follows
40
+
*/
41
+
#[PublicEndpoint]
42
+
public function getFollows(string $actor, int $limit = 50, ?string $cursor = null): GetFollowsResponse
43
+
{
44
+
$response = $this->atp->client->get(
45
+
endpoint: BskyGraph::GetFollows,
46
+
params: compact('actor', 'limit', 'cursor')
47
+
);
48
+
49
+
return GetFollowsResponse::fromArray($response->json());
50
+
}
51
+
52
+
/**
53
+
* Get followers of an actor that you also follow
54
+
*
55
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-known-followers
56
+
*/
57
+
#[PublicEndpoint]
58
+
public function getKnownFollowers(string $actor, int $limit = 50, ?string $cursor = null): GetKnownFollowersResponse
59
+
{
60
+
$response = $this->atp->client->get(
61
+
endpoint: BskyGraph::GetKnownFollowers,
62
+
params: compact('actor', 'limit', 'cursor')
63
+
);
64
+
65
+
return GetKnownFollowersResponse::fromArray($response->json());
66
+
}
67
+
68
+
/**
69
+
* Get a list by URI
70
+
*
71
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-list
72
+
*/
73
+
#[PublicEndpoint]
74
+
public function getList(string $list, int $limit = 50, ?string $cursor = null): GetListResponse
75
+
{
76
+
$response = $this->atp->client->get(
77
+
endpoint: BskyGraph::GetList,
78
+
params: compact('list', 'limit', 'cursor')
79
+
);
80
+
81
+
return GetListResponse::fromArray($response->json());
82
+
}
83
+
84
+
/**
85
+
* Get lists created by an actor
86
+
*
87
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-lists
88
+
*/
89
+
#[PublicEndpoint]
90
+
public function getLists(string $actor, int $limit = 50, ?string $cursor = null): GetListsResponse
91
+
{
92
+
$response = $this->atp->client->get(
93
+
endpoint: BskyGraph::GetLists,
94
+
params: compact('actor', 'limit', 'cursor')
95
+
);
96
+
97
+
return GetListsResponse::fromArray($response->json());
98
+
}
99
+
100
+
/**
101
+
* Get relationships between actors
102
+
*
103
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-relationships
104
+
*/
105
+
#[PublicEndpoint]
106
+
public function getRelationships(string $actor, array $others = []): GetRelationshipsResponse
107
+
{
108
+
$response = $this->atp->client->get(
109
+
endpoint: BskyGraph::GetRelationships,
110
+
params: compact('actor', 'others')
111
+
);
112
+
113
+
return GetRelationshipsResponse::fromArray($response->json());
114
+
}
115
+
116
+
/**
117
+
* Get a starter pack by URI
118
+
*
119
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-starter-pack
120
+
*/
121
+
#[PublicEndpoint]
122
+
public function getStarterPack(string $starterPack): StarterPackView
123
+
{
124
+
$response = $this->atp->client->get(
125
+
endpoint: BskyGraph::GetStarterPack,
126
+
params: compact('starterPack')
127
+
);
128
+
129
+
return StarterPackView::fromArray($response->json()['starterPack']);
130
+
}
131
+
132
+
/**
133
+
* Get multiple starter packs
134
+
*
135
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-starter-packs
136
+
*/
137
+
#[PublicEndpoint]
138
+
public function getStarterPacks(array $uris): GetStarterPacksResponse
139
+
{
140
+
$response = $this->atp->client->get(
141
+
endpoint: BskyGraph::GetStarterPacks,
142
+
params: compact('uris')
143
+
);
144
+
145
+
return GetStarterPacksResponse::fromArray($response->json());
146
+
}
147
+
148
+
/**
149
+
* Get suggested follows based on an actor
150
+
*
151
+
* @see https://docs.bsky.app/docs/api/app-bsky-graph-get-suggested-follows-by-actor
152
+
*/
153
+
#[PublicEndpoint]
154
+
public function getSuggestedFollowsByActor(string $actor): GetSuggestedFollowsByActorResponse
155
+
{
156
+
$response = $this->atp->client->get(
157
+
endpoint: BskyGraph::GetSuggestedFollowsByActor,
158
+
params: compact('actor')
159
+
);
160
+
161
+
return GetSuggestedFollowsByActorResponse::fromArray($response->json());
162
+
}
163
+
}
+27
src/Client/Requests/Bsky/LabelerRequestClient.php
+27
src/Client/Requests/Bsky/LabelerRequestClient.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Client\Requests\Bsky;
4
+
5
+
use SocialDept\AtpClient\Attributes\PublicEndpoint;
6
+
use SocialDept\AtpClient\Client\Requests\Request;
7
+
use SocialDept\AtpClient\Data\Responses\Bsky\Labeler\GetServicesResponse;
8
+
use SocialDept\AtpClient\Enums\Nsid\BskyLabeler;
9
+
10
+
class LabelerRequestClient extends Request
11
+
{
12
+
/**
13
+
* Get labeler services
14
+
*
15
+
* @see https://docs.bsky.app/docs/api/app-bsky-labeler-get-services
16
+
*/
17
+
#[PublicEndpoint]
18
+
public function getServices(array $dids, bool $detailed = false): GetServicesResponse
19
+
{
20
+
$response = $this->atp->client->get(
21
+
endpoint: BskyLabeler::GetServices,
22
+
params: compact('dids', 'detailed')
23
+
);
24
+
25
+
return GetServicesResponse::fromArray($response->json(), $detailed);
26
+
}
27
+
}
+8
-5
src/Client/Requests/Chat/ActorRequestClient.php
+8
-5
src/Client/Requests/Chat/ActorRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Chat;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
6
use SocialDept\AtpClient\Client\Requests\Request;
7
+
use SocialDept\AtpClient\Data\Responses\EmptyResponse;
7
8
use SocialDept\AtpClient\Enums\Nsid\ChatActor;
8
9
use SocialDept\AtpClient\Enums\Scope;
9
10
use SocialDept\AtpClient\Http\Response;
···
17
18
*
18
19
* @see https://docs.bsky.app/docs/api/chat-bsky-actor-export-account-data
19
20
*/
20
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.actor.getActorMetadata')]
21
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.actor.getActorMetadata')]
21
22
public function getActorMetadata(): Response
22
23
{
23
24
return $this->atp->client->get(
···
32
33
*
33
34
* @see https://docs.bsky.app/docs/api/chat-bsky-actor-export-account-data
34
35
*/
35
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.actor.exportAccountData')]
36
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.actor.exportAccountData')]
36
37
public function exportAccountData(): Response
37
38
{
38
39
return $this->atp->client->get(
···
47
48
*
48
49
* @see https://docs.bsky.app/docs/api/chat-bsky-actor-delete-account
49
50
*/
50
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.actor.deleteAccount')]
51
-
public function deleteAccount(): void
51
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.actor.deleteAccount')]
52
+
public function deleteAccount(): EmptyResponse
52
53
{
53
54
$this->atp->client->post(
54
55
endpoint: ChatActor::DeleteAccount
55
56
);
57
+
58
+
return new EmptyResponse;
56
59
}
57
60
}
+13
-13
src/Client/Requests/Chat/ConvoRequestClient.php
+13
-13
src/Client/Requests/Chat/ConvoRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Chat;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
6
use SocialDept\AtpClient\Client\Requests\Request;
7
7
use SocialDept\AtpClient\Data\Responses\Chat\Convo\GetLogResponse;
8
8
use SocialDept\AtpClient\Data\Responses\Chat\Convo\GetMessagesResponse;
···
24
24
*
25
25
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-get-convo
26
26
*/
27
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getConvo')]
27
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getConvo')]
28
28
public function getConvo(string $convoId): ConvoView
29
29
{
30
30
$response = $this->atp->client->get(
···
42
42
*
43
43
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-get-convo-for-members
44
44
*/
45
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getConvoForMembers')]
45
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getConvoForMembers')]
46
46
public function getConvoForMembers(array $members): ConvoView
47
47
{
48
48
$response = $this->atp->client->get(
···
60
60
*
61
61
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-list-convos
62
62
*/
63
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.listConvos')]
63
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.listConvos')]
64
64
public function listConvos(int $limit = 50, ?string $cursor = null): ListConvosResponse
65
65
{
66
66
$response = $this->atp->client->get(
···
78
78
*
79
79
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-get-messages
80
80
*/
81
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getMessages')]
81
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getMessages')]
82
82
public function getMessages(
83
83
string $convoId,
84
84
int $limit = 50,
···
99
99
*
100
100
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-send-message
101
101
*/
102
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.sendMessage')]
102
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.sendMessage')]
103
103
public function sendMessage(string $convoId, array $message): MessageView
104
104
{
105
105
$response = $this->atp->client->post(
···
117
117
*
118
118
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-send-message-batch
119
119
*/
120
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.sendMessageBatch')]
120
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.sendMessageBatch')]
121
121
public function sendMessageBatch(array $items): SendMessageBatchResponse
122
122
{
123
123
$response = $this->atp->client->post(
···
135
135
*
136
136
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-delete-message-for-self
137
137
*/
138
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.deleteMessageForSelf')]
138
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.deleteMessageForSelf')]
139
139
public function deleteMessageForSelf(string $convoId, string $messageId): DeletedMessageView
140
140
{
141
141
$response = $this->atp->client->post(
···
153
153
*
154
154
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-update-read
155
155
*/
156
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.updateRead')]
156
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.updateRead')]
157
157
public function updateRead(string $convoId, ?string $messageId = null): ConvoView
158
158
{
159
159
$response = $this->atp->client->post(
···
171
171
*
172
172
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-mute-convo
173
173
*/
174
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.muteConvo')]
174
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.muteConvo')]
175
175
public function muteConvo(string $convoId): ConvoView
176
176
{
177
177
$response = $this->atp->client->post(
···
189
189
*
190
190
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-unmute-convo
191
191
*/
192
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.unmuteConvo')]
192
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.unmuteConvo')]
193
193
public function unmuteConvo(string $convoId): ConvoView
194
194
{
195
195
$response = $this->atp->client->post(
···
207
207
*
208
208
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-leave-convo
209
209
*/
210
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.leaveConvo')]
210
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.leaveConvo')]
211
211
public function leaveConvo(string $convoId): LeaveConvoResponse
212
212
{
213
213
$response = $this->atp->client->post(
···
225
225
*
226
226
* @see https://docs.bsky.app/docs/api/chat-bsky-convo-get-log
227
227
*/
228
-
#[RequiresScope(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getLog')]
228
+
#[ScopedEndpoint(Scope::TransitionChat, granular: 'rpc:chat.bsky.convo.getLog')]
229
229
public function getLog(?string $cursor = null): GetLogResponse
230
230
{
231
231
$response = $this->atp->client->get(
+9
-9
src/Client/Requests/Ozone/ModerationRequestClient.php
+9
-9
src/Client/Requests/Ozone/ModerationRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Ozone;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
6
use SocialDept\AtpClient\Client\Requests\Request;
7
7
use SocialDept\AtpClient\Data\Responses\Ozone\Moderation\QueryEventsResponse;
8
8
use SocialDept\AtpClient\Data\Responses\Ozone\Moderation\QueryStatusesResponse;
···
24
24
*
25
25
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-get-event
26
26
*/
27
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getEvent')]
27
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getEvent')]
28
28
public function getModerationEvent(int $id): ModEventViewDetail
29
29
{
30
30
$response = $this->atp->client->get(
···
42
42
*
43
43
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-query-events
44
44
*/
45
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getEvents')]
45
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getEvents')]
46
46
public function getModerationEvents(
47
47
?string $subject = null,
48
48
?array $types = null,
···
66
66
*
67
67
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-get-record
68
68
*/
69
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getRecord')]
69
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getRecord')]
70
70
public function getRecord(string $uri, ?string $cid = null): RecordViewDetail
71
71
{
72
72
$response = $this->atp->client->get(
···
84
84
*
85
85
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-get-repo
86
86
*/
87
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getRepo')]
87
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.getRepo')]
88
88
public function getRepo(string $did): RepoViewDetail
89
89
{
90
90
$response = $this->atp->client->get(
···
102
102
*
103
103
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-query-events
104
104
*/
105
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.queryEvents')]
105
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.queryEvents')]
106
106
public function queryEvents(
107
107
?array $types = null,
108
108
?string $createdBy = null,
···
129
129
*
130
130
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-query-statuses
131
131
*/
132
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.queryStatuses')]
132
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.queryStatuses')]
133
133
public function queryStatuses(
134
134
?string $subject = null,
135
135
?array $tags = null,
···
155
155
*
156
156
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-search-repos
157
157
*/
158
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.searchRepos')]
158
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.searchRepos')]
159
159
public function searchRepos(
160
160
?string $term = null,
161
161
?string $invitedBy = null,
···
180
180
*
181
181
* @see https://docs.bsky.app/docs/api/tools-ozone-moderation-emit-event
182
182
*/
183
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.emitEvent')]
183
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.moderation.emitEvent')]
184
184
public function emitEvent(
185
185
array $event,
186
186
string $subject,
+3
-3
src/Client/Requests/Ozone/ServerRequestClient.php
+3
-3
src/Client/Requests/Ozone/ServerRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Ozone;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
6
use SocialDept\AtpClient\Client\Requests\Request;
7
7
use SocialDept\AtpClient\Data\Responses\Ozone\Server\GetConfigResponse;
8
8
use SocialDept\AtpClient\Enums\Nsid\OzoneServer;
···
18
18
*
19
19
* @see https://docs.bsky.app/docs/api/tools-ozone-server-get-config
20
20
*/
21
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.server.getBlob')]
21
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.server.getBlob')]
22
22
public function getBlob(string $did, string $cid): Response
23
23
{
24
24
return $this->atp->client->get(
···
34
34
*
35
35
* @see https://docs.bsky.app/docs/api/tools-ozone-server-get-config
36
36
*/
37
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.server.getConfig')]
37
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.server.getConfig')]
38
38
public function getConfig(): GetConfigResponse
39
39
{
40
40
$response = $this->atp->client->get(
+17
-19
src/Client/Requests/Ozone/TeamRequestClient.php
+17
-19
src/Client/Requests/Ozone/TeamRequestClient.php
···
2
2
3
3
namespace SocialDept\AtpClient\Client\Requests\Ozone;
4
4
5
-
use SocialDept\AtpClient\Attributes\RequiresScope;
5
+
use SocialDept\AtpClient\Attributes\ScopedEndpoint;
6
6
use SocialDept\AtpClient\Client\Requests\Request;
7
+
use SocialDept\AtpClient\Data\Responses\EmptyResponse;
7
8
use SocialDept\AtpClient\Data\Responses\Ozone\Team\ListMembersResponse;
9
+
use SocialDept\AtpClient\Data\Responses\Ozone\Team\MemberResponse;
8
10
use SocialDept\AtpClient\Enums\Nsid\OzoneTeam;
9
11
use SocialDept\AtpClient\Enums\Scope;
10
12
···
15
17
*
16
18
* @requires transition:generic (rpc:tools.ozone.team.getMember)
17
19
*
18
-
* @return array<string, mixed> Team member object
19
-
*
20
20
* @see https://docs.bsky.app/docs/api/tools-ozone-team-list-members
21
21
*/
22
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.getMember')]
23
-
public function getTeamMember(string $did): array
22
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.getMember')]
23
+
public function getTeamMember(string $did): MemberResponse
24
24
{
25
25
$response = $this->atp->client->get(
26
26
endpoint: OzoneTeam::GetMember,
27
27
params: compact('did')
28
28
);
29
29
30
-
return $response->json();
30
+
return MemberResponse::fromArray($response->json());
31
31
}
32
32
33
33
/**
···
37
37
*
38
38
* @see https://docs.bsky.app/docs/api/tools-ozone-team-list-members
39
39
*/
40
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.listMembers')]
40
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.listMembers')]
41
41
public function listTeamMembers(int $limit = 50, ?string $cursor = null): ListMembersResponse
42
42
{
43
43
$response = $this->atp->client->get(
···
53
53
*
54
54
* @requires transition:generic (rpc:tools.ozone.team.addMember)
55
55
*
56
-
* @return array<string, mixed> Team member object
57
-
*
58
56
* @see https://docs.bsky.app/docs/api/tools-ozone-team-add-member
59
57
*/
60
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.addMember')]
61
-
public function addTeamMember(string $did, string $role): array
58
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.addMember')]
59
+
public function addTeamMember(string $did, string $role): MemberResponse
62
60
{
63
61
$response = $this->atp->client->post(
64
62
endpoint: OzoneTeam::AddMember,
65
63
body: compact('did', 'role')
66
64
);
67
65
68
-
return $response->json();
66
+
return MemberResponse::fromArray($response->json());
69
67
}
70
68
71
69
/**
···
73
71
*
74
72
* @requires transition:generic (rpc:tools.ozone.team.updateMember)
75
73
*
76
-
* @return array<string, mixed> Team member object
77
-
*
78
74
* @see https://docs.bsky.app/docs/api/tools-ozone-team-update-member
79
75
*/
80
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.updateMember')]
76
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.updateMember')]
81
77
public function updateTeamMember(
82
78
string $did,
83
79
?bool $disabled = null,
84
80
?string $role = null
85
-
): array {
81
+
): MemberResponse {
86
82
$response = $this->atp->client->post(
87
83
endpoint: OzoneTeam::UpdateMember,
88
84
body: array_filter(
···
91
87
)
92
88
);
93
89
94
-
return $response->json();
90
+
return MemberResponse::fromArray($response->json());
95
91
}
96
92
97
93
/**
···
101
97
*
102
98
* @see https://docs.bsky.app/docs/api/tools-ozone-team-delete-member
103
99
*/
104
-
#[RequiresScope(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.deleteMember')]
105
-
public function deleteTeamMember(string $did): void
100
+
#[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:tools.ozone.team.deleteMember')]
101
+
public function deleteTeamMember(string $did): EmptyResponse
106
102
{
107
103
$this->atp->client->post(
108
104
endpoint: OzoneTeam::DeleteMember,
109
105
body: compact('did')
110
106
);
107
+
108
+
return new EmptyResponse;
111
109
}
112
110
}
+13
-44
src/Console/MakeAtpClientCommand.php
+13
-44
src/Console/MakeAtpClientCommand.php
···
10
10
{
11
11
protected $signature = 'make:atp-client
12
12
{name : The name of the client class}
13
-
{--public : Generate a public client extension instead of authenticated}
14
13
{--force : Overwrite existing file}';
15
14
16
15
protected $description = 'Create a new ATP domain client extension';
···
23
22
public function handle(): int
24
23
{
25
24
$name = $this->argument('name');
26
-
$isPublic = $this->option('public');
27
25
28
26
if (! Str::endsWith($name, 'Client')) {
29
27
$name .= 'Client';
30
28
}
31
29
32
-
$path = $this->getPath($name, $isPublic);
30
+
$path = $this->getPath($name);
33
31
34
32
if ($this->files->exists($path) && ! $this->option('force')) {
35
33
$this->components->error("Client [{$name}] already exists!");
···
39
37
40
38
$this->makeDirectory($path);
41
39
42
-
$stub = $isPublic ? $this->getPublicStub() : $this->getStub();
43
-
$content = $this->populateStub($stub, $name, $isPublic);
40
+
$content = $this->populateStub($this->getStub(), $name);
44
41
45
42
$this->files->put($path, $content);
46
43
47
44
$this->components->info("Client [{$path}] created successfully.");
48
45
49
-
$this->outputRegistrationHint($name, $isPublic);
46
+
$this->outputRegistrationHint($name);
50
47
51
48
return self::SUCCESS;
52
49
}
53
50
54
-
protected function getPath(string $name, bool $isPublic = false): string
51
+
protected function getPath(string $name): string
55
52
{
56
-
$basePath = $isPublic
57
-
? config('client.generators.client_public_path', 'app/Services/Clients/Public')
58
-
: config('client.generators.client_path', 'app/Services/Clients');
53
+
$basePath = config('client.generators.client_path', 'app/Services/Clients');
59
54
60
55
return base_path($basePath.'/'.$name.'.php');
61
56
}
···
67
62
}
68
63
}
69
64
70
-
protected function getNamespace(bool $isPublic = false): string
65
+
protected function getNamespace(): string
71
66
{
72
-
$basePath = $isPublic
73
-
? config('client.generators.client_public_path', 'app/Services/Clients/Public')
74
-
: config('client.generators.client_path', 'app/Services/Clients');
67
+
$basePath = config('client.generators.client_path', 'app/Services/Clients');
75
68
76
69
return Str::of($basePath)
77
70
->replace('/', '\\')
···
80
73
->toString();
81
74
}
82
75
83
-
protected function populateStub(string $stub, string $name, bool $isPublic = false): string
76
+
protected function populateStub(string $stub, string $name): string
84
77
{
85
78
return str_replace(
86
79
['{{ namespace }}', '{{ class }}'],
87
-
[$this->getNamespace($isPublic), $name],
80
+
[$this->getNamespace(), $name],
88
81
$stub
89
82
);
90
83
}
91
84
92
-
protected function outputRegistrationHint(string $name, bool $isPublic): void
85
+
protected function outputRegistrationHint(string $name): void
93
86
{
94
87
$this->newLine();
95
88
$this->components->info('Register the extension in your AppServiceProvider:');
96
89
$this->newLine();
97
90
98
-
$namespace = $this->getNamespace($isPublic);
91
+
$namespace = $this->getNamespace();
99
92
$extensionName = Str::of($name)->before('Client')->camel()->toString();
100
-
$clientClass = $isPublic ? 'AtpPublicClient' : 'AtpClient';
101
93
102
94
$this->line("use {$namespace}\\{$name};");
103
-
$this->line("use SocialDept\\AtpClient\\".($isPublic ? 'Client\\Public\\' : '').$clientClass.';');
95
+
$this->line("use SocialDept\\AtpClient\\AtpClient;");
104
96
$this->newLine();
105
97
$this->line("// In boot() method:");
106
-
$this->line("{$clientClass}::extend('{$extensionName}', fn({$clientClass} \$atp) => new {$name}(\$atp));");
98
+
$this->line("AtpClient::extend('{$extensionName}', fn(AtpClient \$atp) => new {$name}(\$atp));");
107
99
}
108
100
109
101
protected function getStub(): string
···
120
112
protected AtpClient $atp;
121
113
122
114
public function __construct(AtpClient $parent)
123
-
{
124
-
$this->atp = $parent;
125
-
}
126
-
127
-
//
128
-
}
129
-
STUB;
130
-
}
131
-
132
-
protected function getPublicStub(): string
133
-
{
134
-
return <<<'STUB'
135
-
<?php
136
-
137
-
namespace {{ namespace }};
138
-
139
-
use SocialDept\AtpClient\Client\Public\AtpPublicClient;
140
-
141
-
class {{ class }}
142
-
{
143
-
protected AtpPublicClient $atp;
144
-
145
-
public function __construct(AtpPublicClient $parent)
146
115
{
147
116
$this->atp = $parent;
148
117
}
+13
-37
src/Console/MakeAtpRequestCommand.php
+13
-37
src/Console/MakeAtpRequestCommand.php
···
11
11
protected $signature = 'make:atp-request
12
12
{name : The name of the request client class}
13
13
{--domain=bsky : The domain to extend (bsky, atproto, chat, ozone)}
14
-
{--public : Generate a public request client instead of authenticated}
15
14
{--force : Overwrite existing file}';
16
15
17
16
protected $description = 'Create a new ATP request client extension for an existing domain';
···
27
26
{
28
27
$name = $this->argument('name');
29
28
$domain = $this->option('domain');
30
-
$isPublic = $this->option('public');
31
29
32
30
if (! in_array($domain, $this->validDomains)) {
33
31
$this->components->error("Invalid domain [{$domain}]. Valid domains: ".implode(', ', $this->validDomains));
···
39
37
$name .= 'Client';
40
38
}
41
39
42
-
$path = $this->getPath($name, $isPublic);
40
+
$path = $this->getPath($name);
43
41
44
42
if ($this->files->exists($path) && ! $this->option('force')) {
45
43
$this->components->error("Request client [{$name}] already exists!");
···
49
47
50
48
$this->makeDirectory($path);
51
49
52
-
$stub = $isPublic ? $this->getPublicStub() : $this->getStub();
53
-
$content = $this->populateStub($stub, $name, $isPublic);
50
+
$content = $this->populateStub($this->getStub(), $name);
54
51
55
52
$this->files->put($path, $content);
56
53
57
54
$this->components->info("Request client [{$path}] created successfully.");
58
55
59
-
$this->outputRegistrationHint($name, $domain, $isPublic);
56
+
$this->outputRegistrationHint($name, $domain);
60
57
61
58
return self::SUCCESS;
62
59
}
63
60
64
-
protected function getPath(string $name, bool $isPublic = false): string
61
+
protected function getPath(string $name): string
65
62
{
66
-
$basePath = $isPublic
67
-
? config('client.generators.request_public_path', 'app/Services/Clients/Public/Requests')
68
-
: config('client.generators.request_path', 'app/Services/Clients/Requests');
63
+
$basePath = config('client.generators.request_path', 'app/Services/Clients/Requests');
69
64
70
65
return base_path($basePath.'/'.$name.'.php');
71
66
}
···
77
72
}
78
73
}
79
74
80
-
protected function getNamespace(bool $isPublic = false): string
75
+
protected function getNamespace(): string
81
76
{
82
-
$basePath = $isPublic
83
-
? config('client.generators.request_public_path', 'app/Services/Clients/Public/Requests')
84
-
: config('client.generators.request_path', 'app/Services/Clients/Requests');
77
+
$basePath = config('client.generators.request_path', 'app/Services/Clients/Requests');
85
78
86
79
return Str::of($basePath)
87
80
->replace('/', '\\')
···
90
83
->toString();
91
84
}
92
85
93
-
protected function populateStub(string $stub, string $name, bool $isPublic = false): string
86
+
protected function populateStub(string $stub, string $name): string
94
87
{
95
88
return str_replace(
96
89
['{{ namespace }}', '{{ class }}'],
97
-
[$this->getNamespace($isPublic), $name],
90
+
[$this->getNamespace(), $name],
98
91
$stub
99
92
);
100
93
}
101
94
102
-
protected function outputRegistrationHint(string $name, string $domain, bool $isPublic): void
95
+
protected function outputRegistrationHint(string $name, string $domain): void
103
96
{
104
97
$this->newLine();
105
98
$this->components->info('Register the extension in your AppServiceProvider:');
106
99
$this->newLine();
107
100
108
-
$namespace = $this->getNamespace($isPublic);
101
+
$namespace = $this->getNamespace();
109
102
$extensionName = Str::of($name)->before('Client')->camel()->toString();
110
-
$clientClass = $isPublic ? 'AtpPublicClient' : 'AtpClient';
111
103
112
104
$this->line("use {$namespace}\\{$name};");
113
-
$this->line("use SocialDept\\AtpClient\\".($isPublic ? 'Client\\Public\\' : '').$clientClass.';');
105
+
$this->line("use SocialDept\\AtpClient\\AtpClient;");
114
106
$this->newLine();
115
107
$this->line("// In boot() method:");
116
-
$this->line("{$clientClass}::extendDomain('{$domain}', '{$extensionName}', fn(\$domain) => new {$name}(\$domain));");
108
+
$this->line("AtpClient::extendDomain('{$domain}', '{$extensionName}', fn(\$domain) => new {$name}(\$domain));");
117
109
}
118
110
119
111
protected function getStub(): string
···
126
118
use SocialDept\AtpClient\Client\Requests\Request;
127
119
128
120
class {{ class }} extends Request
129
-
{
130
-
//
131
-
}
132
-
STUB;
133
-
}
134
-
135
-
protected function getPublicStub(): string
136
-
{
137
-
return <<<'STUB'
138
-
<?php
139
-
140
-
namespace {{ namespace }};
141
-
142
-
use SocialDept\AtpClient\Client\Public\Requests\PublicRequest;
143
-
144
-
class {{ class }} extends PublicRequest
145
121
{
146
122
//
147
123
}
+61
src/Data/Record.php
+61
src/Data/Record.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Data;
4
+
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* Generic wrapper for AT Protocol records.
9
+
*
10
+
* @template T
11
+
* @implements Arrayable<string, mixed>
12
+
*/
13
+
class Record implements Arrayable
14
+
{
15
+
/**
16
+
* @param T $value
17
+
*/
18
+
public function __construct(
19
+
public readonly string $uri,
20
+
public readonly string $cid,
21
+
public readonly mixed $value,
22
+
) {}
23
+
24
+
/**
25
+
* @template U
26
+
* @param array $data
27
+
* @param callable(array): U $transformer
28
+
* @return self<U>
29
+
*/
30
+
public static function fromArray(array $data, callable $transformer): self
31
+
{
32
+
return new self(
33
+
uri: $data['uri'],
34
+
cid: $data['cid'],
35
+
value: $transformer($data['value']),
36
+
);
37
+
}
38
+
39
+
/**
40
+
* Create without transforming value.
41
+
*/
42
+
public static function fromArrayRaw(array $data): self
43
+
{
44
+
return new self(
45
+
uri: $data['uri'],
46
+
cid: $data['cid'],
47
+
value: $data['value'],
48
+
);
49
+
}
50
+
51
+
public function toArray(): array
52
+
{
53
+
return [
54
+
'uri' => $this->uri,
55
+
'cid' => $this->cid,
56
+
'value' => $this->value instanceof Arrayable
57
+
? $this->value->toArray()
58
+
: $this->value,
59
+
];
60
+
}
61
+
}
+60
src/Data/RecordCollection.php
+60
src/Data/RecordCollection.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Data;
4
+
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
7
+
8
+
/**
9
+
* Collection wrapper for paginated AT Protocol records.
10
+
*
11
+
* @template T
12
+
* @implements Arrayable<string, mixed>
13
+
*/
14
+
class RecordCollection implements Arrayable
15
+
{
16
+
/**
17
+
* @param Collection<int, Record<T>> $records
18
+
*/
19
+
public function __construct(
20
+
public readonly Collection $records,
21
+
public readonly ?string $cursor = null,
22
+
) {}
23
+
24
+
/**
25
+
* @template U
26
+
* @param array $data
27
+
* @param callable(array): U $transformer
28
+
* @return self<U>
29
+
*/
30
+
public static function fromArray(array $data, callable $transformer): self
31
+
{
32
+
return new self(
33
+
records: collect($data['records'] ?? [])->map(
34
+
fn (array $record) => Record::fromArray($record, $transformer)
35
+
),
36
+
cursor: $data['cursor'] ?? null,
37
+
);
38
+
}
39
+
40
+
/**
41
+
* Create without transforming values.
42
+
*/
43
+
public static function fromArrayRaw(array $data): self
44
+
{
45
+
return new self(
46
+
records: collect($data['records'] ?? [])->map(
47
+
fn (array $record) => Record::fromArrayRaw($record)
48
+
),
49
+
cursor: $data['cursor'] ?? null,
50
+
);
51
+
}
52
+
53
+
public function toArray(): array
54
+
{
55
+
return [
56
+
'records' => $this->records->map(fn (Record $r) => $r->toArray())->all(),
57
+
'cursor' => $this->cursor,
58
+
];
59
+
}
60
+
}
+29
src/Data/Responses/Atproto/Identity/ResolveHandleResponse.php
+29
src/Data/Responses/Atproto/Identity/ResolveHandleResponse.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Data\Responses\Atproto\Identity;
4
+
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, string>
9
+
*/
10
+
class ResolveHandleResponse implements Arrayable
11
+
{
12
+
public function __construct(
13
+
public readonly string $did,
14
+
) {}
15
+
16
+
public static function fromArray(array $data): self
17
+
{
18
+
return new self(
19
+
did: $data['did'],
20
+
);
21
+
}
22
+
23
+
public function toArray(): array
24
+
{
25
+
return [
26
+
'did' => $this->did,
27
+
];
28
+
}
29
+
}
+15
-1
src/Data/Responses/Atproto/Repo/CreateRecordResponse.php
+15
-1
src/Data/Responses/Atproto/Repo/CreateRecordResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Repo;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
5
6
use SocialDept\AtpSchema\Generated\Com\Atproto\Repo\Defs\CommitMeta;
6
7
7
-
class CreateRecordResponse
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class CreateRecordResponse implements Arrayable
8
12
{
9
13
public function __construct(
10
14
public readonly string $uri,
···
21
25
commit: isset($data['commit']) ? CommitMeta::fromArray($data['commit']) : null,
22
26
validationStatus: $data['validationStatus'] ?? null,
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'uri' => $this->uri,
34
+
'cid' => $this->cid,
35
+
'commit' => $this->commit?->toArray(),
36
+
'validationStatus' => $this->validationStatus,
37
+
];
24
38
}
25
39
}
+12
-1
src/Data/Responses/Atproto/Repo/DeleteRecordResponse.php
+12
-1
src/Data/Responses/Atproto/Repo/DeleteRecordResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Repo;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
5
6
use SocialDept\AtpSchema\Generated\Com\Atproto\Repo\Defs\CommitMeta;
6
7
7
-
class DeleteRecordResponse
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class DeleteRecordResponse implements Arrayable
8
12
{
9
13
public function __construct(
10
14
public readonly ?CommitMeta $commit = null,
···
15
19
return new self(
16
20
commit: isset($data['commit']) ? CommitMeta::fromArray($data['commit']) : null,
17
21
);
22
+
}
23
+
24
+
public function toArray(): array
25
+
{
26
+
return [
27
+
'commit' => $this->commit?->toArray(),
28
+
];
18
29
}
19
30
}
+17
-1
src/Data/Responses/Atproto/Repo/DescribeRepoResponse.php
+17
-1
src/Data/Responses/Atproto/Repo/DescribeRepoResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Repo;
4
4
5
-
class DescribeRepoResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class DescribeRepoResponse implements Arrayable
6
11
{
7
12
/**
8
13
* @param array<string> $collections
···
24
29
collections: $data['collections'] ?? [],
25
30
handleIsCorrect: $data['handleIsCorrect'],
26
31
);
32
+
}
33
+
34
+
public function toArray(): array
35
+
{
36
+
return [
37
+
'handle' => $this->handle,
38
+
'did' => $this->did,
39
+
'didDoc' => $this->didDoc,
40
+
'collections' => $this->collections,
41
+
'handleIsCorrect' => $this->handleIsCorrect,
42
+
];
27
43
}
28
44
}
+15
-1
src/Data/Responses/Atproto/Repo/GetRecordResponse.php
+15
-1
src/Data/Responses/Atproto/Repo/GetRecordResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Repo;
4
4
5
-
class GetRecordResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class GetRecordResponse implements Arrayable
6
11
{
7
12
public function __construct(
8
13
public readonly string $uri,
···
17
22
value: $data['value'],
18
23
cid: $data['cid'] ?? null,
19
24
);
25
+
}
26
+
27
+
public function toArray(): array
28
+
{
29
+
return [
30
+
'uri' => $this->uri,
31
+
'value' => $this->value,
32
+
'cid' => $this->cid,
33
+
];
20
34
}
21
35
}
+18
-4
src/Data/Responses/Atproto/Repo/ListRecordsResponse.php
+18
-4
src/Data/Responses/Atproto/Repo/ListRecordsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Repo;
4
4
5
-
class ListRecordsResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
7
+
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class ListRecordsResponse implements Arrayable
6
12
{
7
13
/**
8
-
* @param array<array{uri: string, cid: string, value: mixed}> $records
14
+
* @param Collection<int, array{uri: string, cid: string, value: mixed}> $records
9
15
*/
10
16
public function __construct(
11
-
public readonly array $records,
17
+
public readonly Collection $records,
12
18
public readonly ?string $cursor = null,
13
19
) {}
14
20
15
21
public static function fromArray(array $data): self
16
22
{
17
23
return new self(
18
-
records: $data['records'] ?? [],
24
+
records: collect($data['records'] ?? []),
19
25
cursor: $data['cursor'] ?? null,
20
26
);
27
+
}
28
+
29
+
public function toArray(): array
30
+
{
31
+
return [
32
+
'records' => $this->records->all(),
33
+
'cursor' => $this->cursor,
34
+
];
21
35
}
22
36
}
+15
-1
src/Data/Responses/Atproto/Repo/PutRecordResponse.php
+15
-1
src/Data/Responses/Atproto/Repo/PutRecordResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Repo;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
5
6
use SocialDept\AtpSchema\Generated\Com\Atproto\Repo\Defs\CommitMeta;
6
7
7
-
class PutRecordResponse
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class PutRecordResponse implements Arrayable
8
12
{
9
13
public function __construct(
10
14
public readonly string $uri,
···
21
25
commit: isset($data['commit']) ? CommitMeta::fromArray($data['commit']) : null,
22
26
validationStatus: $data['validationStatus'] ?? null,
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'uri' => $this->uri,
34
+
'cid' => $this->cid,
35
+
'commit' => $this->commit?->toArray(),
36
+
'validationStatus' => $this->validationStatus,
37
+
];
24
38
}
25
39
}
+18
-1
src/Data/Responses/Atproto/Server/DescribeServerResponse.php
+18
-1
src/Data/Responses/Atproto/Server/DescribeServerResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Server;
4
4
5
-
class DescribeServerResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class DescribeServerResponse implements Arrayable
6
11
{
7
12
/**
8
13
* @param array<string> $availableUserDomains
···
26
31
links: $data['links'] ?? null,
27
32
contact: $data['contact'] ?? null,
28
33
);
34
+
}
35
+
36
+
public function toArray(): array
37
+
{
38
+
return [
39
+
'did' => $this->did,
40
+
'availableUserDomains' => $this->availableUserDomains,
41
+
'inviteCodeRequired' => $this->inviteCodeRequired,
42
+
'phoneVerificationRequired' => $this->phoneVerificationRequired,
43
+
'links' => $this->links,
44
+
'contact' => $this->contact,
45
+
];
29
46
}
30
47
}
+20
-1
src/Data/Responses/Atproto/Server/GetSessionResponse.php
+20
-1
src/Data/Responses/Atproto/Server/GetSessionResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Server;
4
4
5
-
class GetSessionResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class GetSessionResponse implements Arrayable
6
11
{
7
12
public function __construct(
8
13
public readonly string $handle,
···
27
32
active: $data['active'] ?? null,
28
33
status: $data['status'] ?? null,
29
34
);
35
+
}
36
+
37
+
public function toArray(): array
38
+
{
39
+
return [
40
+
'handle' => $this->handle,
41
+
'did' => $this->did,
42
+
'email' => $this->email,
43
+
'emailConfirmed' => $this->emailConfirmed,
44
+
'emailAuthFactor' => $this->emailAuthFactor,
45
+
'didDoc' => $this->didDoc,
46
+
'active' => $this->active,
47
+
'status' => $this->status,
48
+
];
30
49
}
31
50
}
+16
-1
src/Data/Responses/Atproto/Sync/GetRepoStatusResponse.php
+16
-1
src/Data/Responses/Atproto/Sync/GetRepoStatusResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Sync;
4
4
5
-
class GetRepoStatusResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class GetRepoStatusResponse implements Arrayable
6
11
{
7
12
public function __construct(
8
13
public readonly string $did,
···
19
24
status: $data['status'] ?? null,
20
25
rev: $data['rev'] ?? null,
21
26
);
27
+
}
28
+
29
+
public function toArray(): array
30
+
{
31
+
return [
32
+
'did' => $this->did,
33
+
'active' => $this->active,
34
+
'status' => $this->status,
35
+
'rev' => $this->rev,
36
+
];
22
37
}
23
38
}
+14
-1
src/Data/Responses/Atproto/Sync/ListBlobsResponse.php
+14
-1
src/Data/Responses/Atproto/Sync/ListBlobsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Sync;
4
4
5
-
class ListBlobsResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class ListBlobsResponse implements Arrayable
6
11
{
7
12
/**
8
13
* @param array<string> $cids
···
18
23
cids: $data['cids'] ?? [],
19
24
cursor: $data['cursor'] ?? null,
20
25
);
26
+
}
27
+
28
+
public function toArray(): array
29
+
{
30
+
return [
31
+
'cids' => $this->cids,
32
+
'cursor' => $this->cursor,
33
+
];
21
34
}
22
35
}
+36
src/Data/Responses/Atproto/Sync/ListReposByCollectionResponse.php
+36
src/Data/Responses/Atproto/Sync/ListReposByCollectionResponse.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Data\Responses\Atproto\Sync;
4
+
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
7
+
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class ListReposByCollectionResponse implements Arrayable
12
+
{
13
+
/**
14
+
* @param Collection<int, array{did: string, rev: string}> $repos
15
+
*/
16
+
public function __construct(
17
+
public readonly Collection $repos,
18
+
public readonly ?string $cursor = null,
19
+
) {}
20
+
21
+
public static function fromArray(array $data): self
22
+
{
23
+
return new self(
24
+
repos: collect($data['repos'] ?? []),
25
+
cursor: $data['cursor'] ?? null,
26
+
);
27
+
}
28
+
29
+
public function toArray(): array
30
+
{
31
+
return [
32
+
'repos' => $this->repos->all(),
33
+
'cursor' => $this->cursor,
34
+
];
35
+
}
36
+
}
+18
-4
src/Data/Responses/Atproto/Sync/ListReposResponse.php
+18
-4
src/Data/Responses/Atproto/Sync/ListReposResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Atproto\Sync;
4
4
5
-
class ListReposResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
7
+
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class ListReposResponse implements Arrayable
6
12
{
7
13
/**
8
-
* @param array<array{did: string, head: string, rev: string, active?: bool, status?: string}> $repos
14
+
* @param Collection<int, array{did: string, head: string, rev: string, active?: bool, status?: string}> $repos
9
15
*/
10
16
public function __construct(
11
-
public readonly array $repos,
17
+
public readonly Collection $repos,
12
18
public readonly ?string $cursor = null,
13
19
) {}
14
20
15
21
public static function fromArray(array $data): self
16
22
{
17
23
return new self(
18
-
repos: $data['repos'] ?? [],
24
+
repos: collect($data['repos'] ?? []),
19
25
cursor: $data['cursor'] ?? null,
20
26
);
27
+
}
28
+
29
+
public function toArray(): array
30
+
{
31
+
return [
32
+
'repos' => $this->repos->all(),
33
+
'cursor' => $this->cursor,
34
+
];
21
35
}
22
36
}
+17
-6
src/Data/Responses/Bsky/Actor/GetProfilesResponse.php
+17
-6
src/Data/Responses/Bsky/Actor/GetProfilesResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Actor;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileViewDetailed;
6
8
7
-
class GetProfilesResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetProfilesResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileViewDetailed> $profiles
15
+
* @param Collection<int, ProfileViewDetailed> $profiles
11
16
*/
12
17
public function __construct(
13
-
public readonly array $profiles,
18
+
public readonly Collection $profiles,
14
19
) {}
15
20
16
21
public static function fromArray(array $data): self
17
22
{
18
23
return new self(
19
-
profiles: array_map(
20
-
fn (array $profile) => ProfileViewDetailed::fromArray($profile),
21
-
$data['profiles'] ?? []
24
+
profiles: collect($data['profiles'] ?? [])->map(
25
+
fn (array $profile) => ProfileViewDetailed::fromArray($profile)
22
26
),
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'profiles' => $this->profiles->map(fn (ProfileViewDetailed $p) => $p->toArray())->all(),
34
+
];
24
35
}
25
36
}
+18
-6
src/Data/Responses/Bsky/Actor/GetSuggestionsResponse.php
+18
-6
src/Data/Responses/Bsky/Actor/GetSuggestionsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Actor;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileView;
6
8
7
-
class GetSuggestionsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetSuggestionsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileView> $actors
15
+
* @param Collection<int, ProfileView> $actors
11
16
*/
12
17
public function __construct(
13
-
public readonly array $actors,
18
+
public readonly Collection $actors,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
actors: array_map(
21
-
fn (array $actor) => ProfileView::fromArray($actor),
22
-
$data['actors'] ?? []
25
+
actors: collect($data['actors'] ?? [])->map(
26
+
fn (array $actor) => ProfileView::fromArray($actor)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'actors' => $this->actors->map(fn (ProfileView $a) => $a->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+18
-6
src/Data/Responses/Bsky/Actor/SearchActorsResponse.php
+18
-6
src/Data/Responses/Bsky/Actor/SearchActorsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Actor;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileView;
6
8
7
-
class SearchActorsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class SearchActorsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileView> $actors
15
+
* @param Collection<int, ProfileView> $actors
11
16
*/
12
17
public function __construct(
13
-
public readonly array $actors,
18
+
public readonly Collection $actors,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
actors: array_map(
21
-
fn (array $actor) => ProfileView::fromArray($actor),
22
-
$data['actors'] ?? []
25
+
actors: collect($data['actors'] ?? [])->map(
26
+
fn (array $actor) => ProfileView::fromArray($actor)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'actors' => $this->actors->map(fn (ProfileView $a) => $a->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+17
-6
src/Data/Responses/Bsky/Actor/SearchActorsTypeaheadResponse.php
+17
-6
src/Data/Responses/Bsky/Actor/SearchActorsTypeaheadResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Actor;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileViewBasic;
6
8
7
-
class SearchActorsTypeaheadResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class SearchActorsTypeaheadResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileViewBasic> $actors
15
+
* @param Collection<int, ProfileViewBasic> $actors
11
16
*/
12
17
public function __construct(
13
-
public readonly array $actors,
18
+
public readonly Collection $actors,
14
19
) {}
15
20
16
21
public static function fromArray(array $data): self
17
22
{
18
23
return new self(
19
-
actors: array_map(
20
-
fn (array $actor) => ProfileViewBasic::fromArray($actor),
21
-
$data['actors'] ?? []
24
+
actors: collect($data['actors'] ?? [])->map(
25
+
fn (array $actor) => ProfileViewBasic::fromArray($actor)
22
26
),
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'actors' => $this->actors->map(fn (ProfileViewBasic $a) => $a->toArray())->all(),
34
+
];
24
35
}
25
36
}
+15
-1
src/Data/Responses/Bsky/Feed/DescribeFeedGeneratorResponse.php
+15
-1
src/Data/Responses/Bsky/Feed/DescribeFeedGeneratorResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
-
class DescribeFeedGeneratorResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class DescribeFeedGeneratorResponse implements Arrayable
6
11
{
7
12
/**
8
13
* @param array<array{uri: string}> $feeds
···
20
25
feeds: $data['feeds'] ?? [],
21
26
links: $data['links'] ?? null,
22
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'did' => $this->did,
34
+
'feeds' => $this->feeds,
35
+
'links' => $this->links,
36
+
];
23
37
}
24
38
}
+18
-6
src/Data/Responses/Bsky/Feed/GetActorFeedsResponse.php
+18
-6
src/Data/Responses/Bsky/Feed/GetActorFeedsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\GeneratorView;
6
8
7
-
class GetActorFeedsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetActorFeedsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<GeneratorView> $feeds
15
+
* @param Collection<int, GeneratorView> $feeds
11
16
*/
12
17
public function __construct(
13
-
public readonly array $feeds,
18
+
public readonly Collection $feeds,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
feeds: array_map(
21
-
fn (array $feed) => GeneratorView::fromArray($feed),
22
-
$data['feeds'] ?? []
25
+
feeds: collect($data['feeds'] ?? [])->map(
26
+
fn (array $feed) => GeneratorView::fromArray($feed)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'feeds' => $this->feeds->map(fn (GeneratorView $f) => $f->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+18
-6
src/Data/Responses/Bsky/Feed/GetActorLikesResponse.php
+18
-6
src/Data/Responses/Bsky/Feed/GetActorLikesResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\FeedViewPost;
6
8
7
-
class GetActorLikesResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetActorLikesResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<FeedViewPost> $feed
15
+
* @param Collection<int, FeedViewPost> $feed
11
16
*/
12
17
public function __construct(
13
-
public readonly array $feed,
18
+
public readonly Collection $feed,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
feed: array_map(
21
-
fn (array $post) => FeedViewPost::fromArray($post),
22
-
$data['feed'] ?? []
25
+
feed: collect($data['feed'] ?? [])->map(
26
+
fn (array $post) => FeedViewPost::fromArray($post)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'feed' => $this->feed->map(fn (FeedViewPost $p) => $p->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+18
-6
src/Data/Responses/Bsky/Feed/GetAuthorFeedResponse.php
+18
-6
src/Data/Responses/Bsky/Feed/GetAuthorFeedResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\FeedViewPost;
6
8
7
-
class GetAuthorFeedResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetAuthorFeedResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<FeedViewPost> $feed
15
+
* @param Collection<int, FeedViewPost> $feed
11
16
*/
12
17
public function __construct(
13
-
public readonly array $feed,
18
+
public readonly Collection $feed,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
feed: array_map(
21
-
fn (array $post) => FeedViewPost::fromArray($post),
22
-
$data['feed'] ?? []
25
+
feed: collect($data['feed'] ?? [])->map(
26
+
fn (array $post) => FeedViewPost::fromArray($post)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'feed' => $this->feed->map(fn (FeedViewPost $p) => $p->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+14
-1
src/Data/Responses/Bsky/Feed/GetFeedGeneratorResponse.php
+14
-1
src/Data/Responses/Bsky/Feed/GetFeedGeneratorResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
5
6
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\GeneratorView;
6
7
7
-
class GetFeedGeneratorResponse
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class GetFeedGeneratorResponse implements Arrayable
8
12
{
9
13
public function __construct(
10
14
public readonly GeneratorView $view,
···
19
23
isOnline: $data['isOnline'],
20
24
isValid: $data['isValid'],
21
25
);
26
+
}
27
+
28
+
public function toArray(): array
29
+
{
30
+
return [
31
+
'view' => $this->view->toArray(),
32
+
'isOnline' => $this->isOnline,
33
+
'isValid' => $this->isValid,
34
+
];
22
35
}
23
36
}
+17
-6
src/Data/Responses/Bsky/Feed/GetFeedGeneratorsResponse.php
+17
-6
src/Data/Responses/Bsky/Feed/GetFeedGeneratorsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\GeneratorView;
6
8
7
-
class GetFeedGeneratorsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetFeedGeneratorsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<GeneratorView> $feeds
15
+
* @param Collection<int, GeneratorView> $feeds
11
16
*/
12
17
public function __construct(
13
-
public readonly array $feeds,
18
+
public readonly Collection $feeds,
14
19
) {}
15
20
16
21
public static function fromArray(array $data): self
17
22
{
18
23
return new self(
19
-
feeds: array_map(
20
-
fn (array $feed) => GeneratorView::fromArray($feed),
21
-
$data['feeds'] ?? []
24
+
feeds: collect($data['feeds'] ?? [])->map(
25
+
fn (array $feed) => GeneratorView::fromArray($feed)
22
26
),
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'feeds' => $this->feeds->map(fn (GeneratorView $f) => $f->toArray())->all(),
34
+
];
24
35
}
25
36
}
+18
-6
src/Data/Responses/Bsky/Feed/GetFeedResponse.php
+18
-6
src/Data/Responses/Bsky/Feed/GetFeedResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\FeedViewPost;
6
8
7
-
class GetFeedResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetFeedResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<FeedViewPost> $feed
15
+
* @param Collection<int, FeedViewPost> $feed
11
16
*/
12
17
public function __construct(
13
-
public readonly array $feed,
18
+
public readonly Collection $feed,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
feed: array_map(
21
-
fn (array $post) => FeedViewPost::fromArray($post),
22
-
$data['feed'] ?? []
25
+
feed: collect($data['feed'] ?? [])->map(
26
+
fn (array $post) => FeedViewPost::fromArray($post)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'feed' => $this->feed->map(fn (FeedViewPost $p) => $p->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+20
-6
src/Data/Responses/Bsky/Feed/GetLikesResponse.php
+20
-6
src/Data/Responses/Bsky/Feed/GetLikesResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\GetLikes\Like;
6
8
7
-
class GetLikesResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetLikesResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<Like> $likes
15
+
* @param Collection<int, Like> $likes
11
16
*/
12
17
public function __construct(
13
18
public readonly string $uri,
14
-
public readonly array $likes,
19
+
public readonly Collection $likes,
15
20
public readonly ?string $cid = null,
16
21
public readonly ?string $cursor = null,
17
22
) {}
···
20
25
{
21
26
return new self(
22
27
uri: $data['uri'],
23
-
likes: array_map(
24
-
fn (array $like) => Like::fromArray($like),
25
-
$data['likes'] ?? []
28
+
likes: collect($data['likes'] ?? [])->map(
29
+
fn (array $like) => Like::fromArray($like)
26
30
),
27
31
cid: $data['cid'] ?? null,
28
32
cursor: $data['cursor'] ?? null,
29
33
);
34
+
}
35
+
36
+
public function toArray(): array
37
+
{
38
+
return [
39
+
'uri' => $this->uri,
40
+
'likes' => $this->likes->map(fn (Like $l) => $l->toArray())->all(),
41
+
'cid' => $this->cid,
42
+
'cursor' => $this->cursor,
43
+
];
30
44
}
31
45
}
+13
-1
src/Data/Responses/Bsky/Feed/GetPostThreadResponse.php
+13
-1
src/Data/Responses/Bsky/Feed/GetPostThreadResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
5
6
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\ThreadViewPost;
6
7
7
-
class GetPostThreadResponse
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class GetPostThreadResponse implements Arrayable
8
12
{
9
13
public function __construct(
10
14
public readonly ThreadViewPost $thread,
···
17
21
thread: ThreadViewPost::fromArray($data['thread']),
18
22
threadgate: $data['threadgate'] ?? null,
19
23
);
24
+
}
25
+
26
+
public function toArray(): array
27
+
{
28
+
return [
29
+
'thread' => $this->thread->toArray(),
30
+
'threadgate' => $this->threadgate,
31
+
];
20
32
}
21
33
}
+17
-6
src/Data/Responses/Bsky/Feed/GetPostsResponse.php
+17
-6
src/Data/Responses/Bsky/Feed/GetPostsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\PostView;
6
8
7
-
class GetPostsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetPostsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<PostView> $posts
15
+
* @param Collection<int, PostView> $posts
11
16
*/
12
17
public function __construct(
13
-
public readonly array $posts,
18
+
public readonly Collection $posts,
14
19
) {}
15
20
16
21
public static function fromArray(array $data): self
17
22
{
18
23
return new self(
19
-
posts: array_map(
20
-
fn (array $post) => PostView::fromArray($post),
21
-
$data['posts'] ?? []
24
+
posts: collect($data['posts'] ?? [])->map(
25
+
fn (array $post) => PostView::fromArray($post)
22
26
),
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'posts' => $this->posts->map(fn (PostView $p) => $p->toArray())->all(),
34
+
];
24
35
}
25
36
}
+20
-6
src/Data/Responses/Bsky/Feed/GetQuotesResponse.php
+20
-6
src/Data/Responses/Bsky/Feed/GetQuotesResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\PostView;
6
8
7
-
class GetQuotesResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetQuotesResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<PostView> $posts
15
+
* @param Collection<int, PostView> $posts
11
16
*/
12
17
public function __construct(
13
18
public readonly string $uri,
14
-
public readonly array $posts,
19
+
public readonly Collection $posts,
15
20
public readonly ?string $cid = null,
16
21
public readonly ?string $cursor = null,
17
22
) {}
···
20
25
{
21
26
return new self(
22
27
uri: $data['uri'],
23
-
posts: array_map(
24
-
fn (array $post) => PostView::fromArray($post),
25
-
$data['posts'] ?? []
28
+
posts: collect($data['posts'] ?? [])->map(
29
+
fn (array $post) => PostView::fromArray($post)
26
30
),
27
31
cid: $data['cid'] ?? null,
28
32
cursor: $data['cursor'] ?? null,
29
33
);
34
+
}
35
+
36
+
public function toArray(): array
37
+
{
38
+
return [
39
+
'uri' => $this->uri,
40
+
'posts' => $this->posts->map(fn (PostView $p) => $p->toArray())->all(),
41
+
'cid' => $this->cid,
42
+
'cursor' => $this->cursor,
43
+
];
30
44
}
31
45
}
+20
-6
src/Data/Responses/Bsky/Feed/GetRepostedByResponse.php
+20
-6
src/Data/Responses/Bsky/Feed/GetRepostedByResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileView;
6
8
7
-
class GetRepostedByResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetRepostedByResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileView> $repostedBy
15
+
* @param Collection<int, ProfileView> $repostedBy
11
16
*/
12
17
public function __construct(
13
18
public readonly string $uri,
14
-
public readonly array $repostedBy,
19
+
public readonly Collection $repostedBy,
15
20
public readonly ?string $cid = null,
16
21
public readonly ?string $cursor = null,
17
22
) {}
···
20
25
{
21
26
return new self(
22
27
uri: $data['uri'],
23
-
repostedBy: array_map(
24
-
fn (array $profile) => ProfileView::fromArray($profile),
25
-
$data['repostedBy'] ?? []
28
+
repostedBy: collect($data['repostedBy'] ?? [])->map(
29
+
fn (array $profile) => ProfileView::fromArray($profile)
26
30
),
27
31
cid: $data['cid'] ?? null,
28
32
cursor: $data['cursor'] ?? null,
29
33
);
34
+
}
35
+
36
+
public function toArray(): array
37
+
{
38
+
return [
39
+
'uri' => $this->uri,
40
+
'repostedBy' => $this->repostedBy->map(fn (ProfileView $p) => $p->toArray())->all(),
41
+
'cid' => $this->cid,
42
+
'cursor' => $this->cursor,
43
+
];
30
44
}
31
45
}
+18
-6
src/Data/Responses/Bsky/Feed/GetSuggestedFeedsResponse.php
+18
-6
src/Data/Responses/Bsky/Feed/GetSuggestedFeedsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\GeneratorView;
6
8
7
-
class GetSuggestedFeedsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetSuggestedFeedsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<GeneratorView> $feeds
15
+
* @param Collection<int, GeneratorView> $feeds
11
16
*/
12
17
public function __construct(
13
-
public readonly array $feeds,
18
+
public readonly Collection $feeds,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
feeds: array_map(
21
-
fn (array $feed) => GeneratorView::fromArray($feed),
22
-
$data['feeds'] ?? []
25
+
feeds: collect($data['feeds'] ?? [])->map(
26
+
fn (array $feed) => GeneratorView::fromArray($feed)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'feeds' => $this->feeds->map(fn (GeneratorView $f) => $f->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+18
-6
src/Data/Responses/Bsky/Feed/GetTimelineResponse.php
+18
-6
src/Data/Responses/Bsky/Feed/GetTimelineResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\FeedViewPost;
6
8
7
-
class GetTimelineResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetTimelineResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<FeedViewPost> $feed
15
+
* @param Collection<int, FeedViewPost> $feed
11
16
*/
12
17
public function __construct(
13
-
public readonly array $feed,
18
+
public readonly Collection $feed,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
feed: array_map(
21
-
fn (array $post) => FeedViewPost::fromArray($post),
22
-
$data['feed'] ?? []
25
+
feed: collect($data['feed'] ?? [])->map(
26
+
fn (array $post) => FeedViewPost::fromArray($post)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'feed' => $this->feed->map(fn (FeedViewPost $p) => $p->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+19
-6
src/Data/Responses/Bsky/Feed/SearchPostsResponse.php
+19
-6
src/Data/Responses/Bsky/Feed/SearchPostsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Feed;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Feed\Defs\PostView;
6
8
7
-
class SearchPostsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class SearchPostsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<PostView> $posts
15
+
* @param Collection<int, PostView> $posts
11
16
*/
12
17
public function __construct(
13
-
public readonly array $posts,
18
+
public readonly Collection $posts,
14
19
public readonly ?string $cursor = null,
15
20
public readonly ?int $hitsTotal = null,
16
21
) {}
···
18
23
public static function fromArray(array $data): self
19
24
{
20
25
return new self(
21
-
posts: array_map(
22
-
fn (array $post) => PostView::fromArray($post),
23
-
$data['posts'] ?? []
26
+
posts: collect($data['posts'] ?? [])->map(
27
+
fn (array $post) => PostView::fromArray($post)
24
28
),
25
29
cursor: $data['cursor'] ?? null,
26
30
hitsTotal: $data['hitsTotal'] ?? null,
27
31
);
32
+
}
33
+
34
+
public function toArray(): array
35
+
{
36
+
return [
37
+
'posts' => $this->posts->map(fn (PostView $p) => $p->toArray())->all(),
38
+
'cursor' => $this->cursor,
39
+
'hitsTotal' => $this->hitsTotal,
40
+
];
28
41
}
29
42
}
+19
-6
src/Data/Responses/Bsky/Graph/GetFollowersResponse.php
+19
-6
src/Data/Responses/Bsky/Graph/GetFollowersResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileView;
6
8
7
-
class GetFollowersResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetFollowersResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileView> $followers
15
+
* @param Collection<int, ProfileView> $followers
11
16
*/
12
17
public function __construct(
13
18
public readonly ProfileView $subject,
14
-
public readonly array $followers,
19
+
public readonly Collection $followers,
15
20
public readonly ?string $cursor = null,
16
21
) {}
17
22
···
19
24
{
20
25
return new self(
21
26
subject: ProfileView::fromArray($data['subject']),
22
-
followers: array_map(
23
-
fn (array $profile) => ProfileView::fromArray($profile),
24
-
$data['followers'] ?? []
27
+
followers: collect($data['followers'] ?? [])->map(
28
+
fn (array $profile) => ProfileView::fromArray($profile)
25
29
),
26
30
cursor: $data['cursor'] ?? null,
27
31
);
32
+
}
33
+
34
+
public function toArray(): array
35
+
{
36
+
return [
37
+
'subject' => $this->subject->toArray(),
38
+
'followers' => $this->followers->map(fn (ProfileView $p) => $p->toArray())->all(),
39
+
'cursor' => $this->cursor,
40
+
];
28
41
}
29
42
}
+19
-6
src/Data/Responses/Bsky/Graph/GetFollowsResponse.php
+19
-6
src/Data/Responses/Bsky/Graph/GetFollowsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileView;
6
8
7
-
class GetFollowsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetFollowsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileView> $follows
15
+
* @param Collection<int, ProfileView> $follows
11
16
*/
12
17
public function __construct(
13
18
public readonly ProfileView $subject,
14
-
public readonly array $follows,
19
+
public readonly Collection $follows,
15
20
public readonly ?string $cursor = null,
16
21
) {}
17
22
···
19
24
{
20
25
return new self(
21
26
subject: ProfileView::fromArray($data['subject']),
22
-
follows: array_map(
23
-
fn (array $profile) => ProfileView::fromArray($profile),
24
-
$data['follows'] ?? []
27
+
follows: collect($data['follows'] ?? [])->map(
28
+
fn (array $profile) => ProfileView::fromArray($profile)
25
29
),
26
30
cursor: $data['cursor'] ?? null,
27
31
);
32
+
}
33
+
34
+
public function toArray(): array
35
+
{
36
+
return [
37
+
'subject' => $this->subject->toArray(),
38
+
'follows' => $this->follows->map(fn (ProfileView $p) => $p->toArray())->all(),
39
+
'cursor' => $this->cursor,
40
+
];
28
41
}
29
42
}
+19
-6
src/Data/Responses/Bsky/Graph/GetKnownFollowersResponse.php
+19
-6
src/Data/Responses/Bsky/Graph/GetKnownFollowersResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileView;
6
8
7
-
class GetKnownFollowersResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetKnownFollowersResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileView> $followers
15
+
* @param Collection<int, ProfileView> $followers
11
16
*/
12
17
public function __construct(
13
18
public readonly ProfileView $subject,
14
-
public readonly array $followers,
19
+
public readonly Collection $followers,
15
20
public readonly ?string $cursor = null,
16
21
) {}
17
22
···
19
24
{
20
25
return new self(
21
26
subject: ProfileView::fromArray($data['subject']),
22
-
followers: array_map(
23
-
fn (array $profile) => ProfileView::fromArray($profile),
24
-
$data['followers'] ?? []
27
+
followers: collect($data['followers'] ?? [])->map(
28
+
fn (array $profile) => ProfileView::fromArray($profile)
25
29
),
26
30
cursor: $data['cursor'] ?? null,
27
31
);
32
+
}
33
+
34
+
public function toArray(): array
35
+
{
36
+
return [
37
+
'subject' => $this->subject->toArray(),
38
+
'followers' => $this->followers->map(fn (ProfileView $p) => $p->toArray())->all(),
39
+
'cursor' => $this->cursor,
40
+
];
28
41
}
29
42
}
+19
-6
src/Data/Responses/Bsky/Graph/GetListResponse.php
+19
-6
src/Data/Responses/Bsky/Graph/GetListResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Graph\Defs\ListItemView;
6
8
use SocialDept\AtpSchema\Generated\App\Bsky\Graph\Defs\ListView;
7
9
8
-
class GetListResponse
10
+
/**
11
+
* @implements Arrayable<string, mixed>
12
+
*/
13
+
class GetListResponse implements Arrayable
9
14
{
10
15
/**
11
-
* @param array<ListItemView> $items
16
+
* @param Collection<int, ListItemView> $items
12
17
*/
13
18
public function __construct(
14
19
public readonly ListView $list,
15
-
public readonly array $items,
20
+
public readonly Collection $items,
16
21
public readonly ?string $cursor = null,
17
22
) {}
18
23
···
20
25
{
21
26
return new self(
22
27
list: ListView::fromArray($data['list']),
23
-
items: array_map(
24
-
fn (array $item) => ListItemView::fromArray($item),
25
-
$data['items'] ?? []
28
+
items: collect($data['items'] ?? [])->map(
29
+
fn (array $item) => ListItemView::fromArray($item)
26
30
),
27
31
cursor: $data['cursor'] ?? null,
28
32
);
33
+
}
34
+
35
+
public function toArray(): array
36
+
{
37
+
return [
38
+
'list' => $this->list->toArray(),
39
+
'items' => $this->items->map(fn (ListItemView $i) => $i->toArray())->all(),
40
+
'cursor' => $this->cursor,
41
+
];
29
42
}
30
43
}
+18
-6
src/Data/Responses/Bsky/Graph/GetListsResponse.php
+18
-6
src/Data/Responses/Bsky/Graph/GetListsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Graph\Defs\ListView;
6
8
7
-
class GetListsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetListsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ListView> $lists
15
+
* @param Collection<int, ListView> $lists
11
16
*/
12
17
public function __construct(
13
-
public readonly array $lists,
18
+
public readonly Collection $lists,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
lists: array_map(
21
-
fn (array $list) => ListView::fromArray($list),
22
-
$data['lists'] ?? []
25
+
lists: collect($data['lists'] ?? [])->map(
26
+
fn (array $list) => ListView::fromArray($list)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'lists' => $this->lists->map(fn (ListView $l) => $l->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+18
-4
src/Data/Responses/Bsky/Graph/GetRelationshipsResponse.php
+18
-4
src/Data/Responses/Bsky/Graph/GetRelationshipsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
-
class GetRelationshipsResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
7
+
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class GetRelationshipsResponse implements Arrayable
6
12
{
7
13
/**
8
-
* @param array<mixed> $relationships Array of Relationship or NotFoundActor objects
14
+
* @param Collection<int, mixed> $relationships Collection of Relationship or NotFoundActor objects
9
15
*/
10
16
public function __construct(
11
-
public readonly array $relationships,
17
+
public readonly Collection $relationships,
12
18
public readonly ?string $actor = null,
13
19
) {}
14
20
15
21
public static function fromArray(array $data): self
16
22
{
17
23
return new self(
18
-
relationships: $data['relationships'] ?? [],
24
+
relationships: collect($data['relationships'] ?? []),
19
25
actor: $data['actor'] ?? null,
20
26
);
27
+
}
28
+
29
+
public function toArray(): array
30
+
{
31
+
return [
32
+
'relationships' => $this->relationships->all(),
33
+
'actor' => $this->actor,
34
+
];
21
35
}
22
36
}
+17
-6
src/Data/Responses/Bsky/Graph/GetStarterPacksResponse.php
+17
-6
src/Data/Responses/Bsky/Graph/GetStarterPacksResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Graph\Defs\StarterPackViewBasic;
6
8
7
-
class GetStarterPacksResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetStarterPacksResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<StarterPackViewBasic> $starterPacks
15
+
* @param Collection<int, StarterPackViewBasic> $starterPacks
11
16
*/
12
17
public function __construct(
13
-
public readonly array $starterPacks,
18
+
public readonly Collection $starterPacks,
14
19
) {}
15
20
16
21
public static function fromArray(array $data): self
17
22
{
18
23
return new self(
19
-
starterPacks: array_map(
20
-
fn (array $pack) => StarterPackViewBasic::fromArray($pack),
21
-
$data['starterPacks'] ?? []
24
+
starterPacks: collect($data['starterPacks'] ?? [])->map(
25
+
fn (array $pack) => StarterPackViewBasic::fromArray($pack)
22
26
),
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'starterPacks' => $this->starterPacks->map(fn (StarterPackViewBasic $p) => $p->toArray())->all(),
34
+
];
24
35
}
25
36
}
+18
-6
src/Data/Responses/Bsky/Graph/GetSuggestedFollowsByActorResponse.php
+18
-6
src/Data/Responses/Bsky/Graph/GetSuggestedFollowsByActorResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Graph;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Actor\Defs\ProfileView;
6
8
7
-
class GetSuggestedFollowsByActorResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetSuggestedFollowsByActorResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ProfileView> $suggestions
15
+
* @param Collection<int, ProfileView> $suggestions
11
16
*/
12
17
public function __construct(
13
-
public readonly array $suggestions,
18
+
public readonly Collection $suggestions,
14
19
public readonly ?bool $isFallback = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
suggestions: array_map(
21
-
fn (array $profile) => ProfileView::fromArray($profile),
22
-
$data['suggestions'] ?? []
25
+
suggestions: collect($data['suggestions'] ?? [])->map(
26
+
fn (array $profile) => ProfileView::fromArray($profile)
23
27
),
24
28
isFallback: $data['isFallback'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'suggestions' => $this->suggestions->map(fn (ProfileView $p) => $p->toArray())->all(),
36
+
'isFallback' => $this->isFallback,
37
+
];
26
38
}
27
39
}
+17
-6
src/Data/Responses/Bsky/Labeler/GetServicesResponse.php
+17
-6
src/Data/Responses/Bsky/Labeler/GetServicesResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Bsky\Labeler;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\App\Bsky\Labeler\Defs\LabelerView;
6
8
use SocialDept\AtpSchema\Generated\App\Bsky\Labeler\Defs\LabelerViewDetailed;
7
9
8
-
class GetServicesResponse
10
+
/**
11
+
* @implements Arrayable<string, mixed>
12
+
*/
13
+
class GetServicesResponse implements Arrayable
9
14
{
10
15
/**
11
-
* @param array<LabelerView|LabelerViewDetailed> $views
16
+
* @param Collection<int, LabelerView|LabelerViewDetailed> $views
12
17
*/
13
18
public function __construct(
14
-
public readonly array $views,
19
+
public readonly Collection $views,
15
20
) {}
16
21
17
22
public static function fromArray(array $data, bool $detailed = false): self
18
23
{
19
24
return new self(
20
-
views: array_map(
25
+
views: collect($data['views'] ?? [])->map(
21
26
fn (array $view) => $detailed
22
27
? LabelerViewDetailed::fromArray($view)
23
-
: LabelerView::fromArray($view),
24
-
$data['views'] ?? []
28
+
: LabelerView::fromArray($view)
25
29
),
26
30
);
31
+
}
32
+
33
+
public function toArray(): array
34
+
{
35
+
return [
36
+
'views' => $this->views->map(fn (LabelerView|LabelerViewDetailed $v) => $v->toArray())->all(),
37
+
];
27
38
}
28
39
}
+18
-4
src/Data/Responses/Chat/Convo/GetLogResponse.php
+18
-4
src/Data/Responses/Chat/Convo/GetLogResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Chat\Convo;
4
4
5
-
class GetLogResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
7
+
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class GetLogResponse implements Arrayable
6
12
{
7
13
/**
8
-
* @param array<mixed> $logs Array of log event objects (LogBeginConvo, LogCreateMessage, etc.)
14
+
* @param Collection<int, mixed> $logs Collection of log event objects (LogBeginConvo, LogCreateMessage, etc.)
9
15
*/
10
16
public function __construct(
11
-
public readonly array $logs,
17
+
public readonly Collection $logs,
12
18
public readonly ?string $cursor = null,
13
19
) {}
14
20
15
21
public static function fromArray(array $data): self
16
22
{
17
23
return new self(
18
-
logs: $data['logs'] ?? [],
24
+
logs: collect($data['logs'] ?? []),
19
25
cursor: $data['cursor'] ?? null,
20
26
);
27
+
}
28
+
29
+
public function toArray(): array
30
+
{
31
+
return [
32
+
'logs' => $this->logs->all(),
33
+
'cursor' => $this->cursor,
34
+
];
21
35
}
22
36
}
+18
-6
src/Data/Responses/Chat/Convo/GetMessagesResponse.php
+18
-6
src/Data/Responses/Chat/Convo/GetMessagesResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Chat\Convo;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\Chat\Bsky\Convo\Defs\DeletedMessageView;
6
8
use SocialDept\AtpSchema\Generated\Chat\Bsky\Convo\Defs\MessageView;
7
9
8
-
class GetMessagesResponse
10
+
/**
11
+
* @implements Arrayable<string, mixed>
12
+
*/
13
+
class GetMessagesResponse implements Arrayable
9
14
{
10
15
/**
11
-
* @param array<MessageView|DeletedMessageView> $messages
16
+
* @param Collection<int, MessageView|DeletedMessageView> $messages
12
17
*/
13
18
public function __construct(
14
-
public readonly array $messages,
19
+
public readonly Collection $messages,
15
20
public readonly ?string $cursor = null,
16
21
) {}
17
22
18
23
public static function fromArray(array $data): self
19
24
{
20
25
return new self(
21
-
messages: array_map(
26
+
messages: collect($data['messages'] ?? [])->map(
22
27
function (array $message) {
23
28
if (isset($message['$type']) && $message['$type'] === 'chat.bsky.convo.defs#deletedMessageView') {
24
29
return DeletedMessageView::fromArray($message);
25
30
}
26
31
27
32
return MessageView::fromArray($message);
28
-
},
29
-
$data['messages'] ?? []
33
+
}
30
34
),
31
35
cursor: $data['cursor'] ?? null,
32
36
);
37
+
}
38
+
39
+
public function toArray(): array
40
+
{
41
+
return [
42
+
'messages' => $this->messages->map(fn (MessageView|DeletedMessageView $m) => $m->toArray())->all(),
43
+
'cursor' => $this->cursor,
44
+
];
33
45
}
34
46
}
+14
-1
src/Data/Responses/Chat/Convo/LeaveConvoResponse.php
+14
-1
src/Data/Responses/Chat/Convo/LeaveConvoResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Chat\Convo;
4
4
5
-
class LeaveConvoResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class LeaveConvoResponse implements Arrayable
6
11
{
7
12
public function __construct(
8
13
public readonly string $convoId,
···
15
20
convoId: $data['convoId'],
16
21
rev: $data['rev'],
17
22
);
23
+
}
24
+
25
+
public function toArray(): array
26
+
{
27
+
return [
28
+
'convoId' => $this->convoId,
29
+
'rev' => $this->rev,
30
+
];
18
31
}
19
32
}
+18
-6
src/Data/Responses/Chat/Convo/ListConvosResponse.php
+18
-6
src/Data/Responses/Chat/Convo/ListConvosResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Chat\Convo;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\Chat\Bsky\Convo\Defs\ConvoView;
6
8
7
-
class ListConvosResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class ListConvosResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ConvoView> $convos
15
+
* @param Collection<int, ConvoView> $convos
11
16
*/
12
17
public function __construct(
13
-
public readonly array $convos,
18
+
public readonly Collection $convos,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
convos: array_map(
21
-
fn (array $convo) => ConvoView::fromArray($convo),
22
-
$data['convos'] ?? []
25
+
convos: collect($data['convos'] ?? [])->map(
26
+
fn (array $convo) => ConvoView::fromArray($convo)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'convos' => $this->convos->map(fn (ConvoView $c) => $c->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+17
-6
src/Data/Responses/Chat/Convo/SendMessageBatchResponse.php
+17
-6
src/Data/Responses/Chat/Convo/SendMessageBatchResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Chat\Convo;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\Chat\Bsky\Convo\Defs\MessageView;
6
8
7
-
class SendMessageBatchResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class SendMessageBatchResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<MessageView> $items
15
+
* @param Collection<int, MessageView> $items
11
16
*/
12
17
public function __construct(
13
-
public readonly array $items,
18
+
public readonly Collection $items,
14
19
) {}
15
20
16
21
public static function fromArray(array $data): self
17
22
{
18
23
return new self(
19
-
items: array_map(
20
-
fn (array $item) => MessageView::fromArray($item),
21
-
$data['items'] ?? []
24
+
items: collect($data['items'] ?? [])->map(
25
+
fn (array $item) => MessageView::fromArray($item)
22
26
),
23
27
);
28
+
}
29
+
30
+
public function toArray(): array
31
+
{
32
+
return [
33
+
'items' => $this->items->map(fn (MessageView $m) => $m->toArray())->all(),
34
+
];
24
35
}
25
36
}
+25
src/Data/Responses/EmptyResponse.php
+25
src/Data/Responses/EmptyResponse.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Data\Responses;
4
+
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* Response class for endpoints that return empty objects.
9
+
*
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class EmptyResponse implements Arrayable
13
+
{
14
+
public function __construct() {}
15
+
16
+
public static function fromArray(array $data): self
17
+
{
18
+
return new self;
19
+
}
20
+
21
+
public function toArray(): array
22
+
{
23
+
return [];
24
+
}
25
+
}
+18
-6
src/Data/Responses/Ozone/Moderation/QueryEventsResponse.php
+18
-6
src/Data/Responses/Ozone/Moderation/QueryEventsResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Ozone\Moderation;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\Tools\Ozone\Moderation\Defs\ModEventView;
6
8
7
-
class QueryEventsResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class QueryEventsResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<ModEventView> $events
15
+
* @param Collection<int, ModEventView> $events
11
16
*/
12
17
public function __construct(
13
-
public readonly array $events,
18
+
public readonly Collection $events,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
events: array_map(
21
-
fn (array $event) => ModEventView::fromArray($event),
22
-
$data['events'] ?? []
25
+
events: collect($data['events'] ?? [])->map(
26
+
fn (array $event) => ModEventView::fromArray($event)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'events' => $this->events->map(fn (ModEventView $e) => $e->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+18
-6
src/Data/Responses/Ozone/Moderation/QueryStatusesResponse.php
+18
-6
src/Data/Responses/Ozone/Moderation/QueryStatusesResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Ozone\Moderation;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\Tools\Ozone\Moderation\Defs\SubjectStatusView;
6
8
7
-
class QueryStatusesResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class QueryStatusesResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<SubjectStatusView> $subjectStatuses
15
+
* @param Collection<int, SubjectStatusView> $subjectStatuses
11
16
*/
12
17
public function __construct(
13
-
public readonly array $subjectStatuses,
18
+
public readonly Collection $subjectStatuses,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
subjectStatuses: array_map(
21
-
fn (array $status) => SubjectStatusView::fromArray($status),
22
-
$data['subjectStatuses'] ?? []
25
+
subjectStatuses: collect($data['subjectStatuses'] ?? [])->map(
26
+
fn (array $status) => SubjectStatusView::fromArray($status)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'subjectStatuses' => $this->subjectStatuses->map(fn (SubjectStatusView $s) => $s->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+18
-6
src/Data/Responses/Ozone/Moderation/SearchReposResponse.php
+18
-6
src/Data/Responses/Ozone/Moderation/SearchReposResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Ozone\Moderation;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
5
7
use SocialDept\AtpSchema\Generated\Tools\Ozone\Moderation\Defs\RepoView;
6
8
7
-
class SearchReposResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class SearchReposResponse implements Arrayable
8
13
{
9
14
/**
10
-
* @param array<RepoView> $repos
15
+
* @param Collection<int, RepoView> $repos
11
16
*/
12
17
public function __construct(
13
-
public readonly array $repos,
18
+
public readonly Collection $repos,
14
19
public readonly ?string $cursor = null,
15
20
) {}
16
21
17
22
public static function fromArray(array $data): self
18
23
{
19
24
return new self(
20
-
repos: array_map(
21
-
fn (array $repo) => RepoView::fromArray($repo),
22
-
$data['repos'] ?? []
25
+
repos: collect($data['repos'] ?? [])->map(
26
+
fn (array $repo) => RepoView::fromArray($repo)
23
27
),
24
28
cursor: $data['cursor'] ?? null,
25
29
);
30
+
}
31
+
32
+
public function toArray(): array
33
+
{
34
+
return [
35
+
'repos' => $this->repos->map(fn (RepoView $r) => $r->toArray())->all(),
36
+
'cursor' => $this->cursor,
37
+
];
26
38
}
27
39
}
+17
-1
src/Data/Responses/Ozone/Server/GetConfigResponse.php
+17
-1
src/Data/Responses/Ozone/Server/GetConfigResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Ozone\Server;
4
4
5
+
use Illuminate\Contracts\Support\Arrayable;
5
6
use SocialDept\AtpSchema\Generated\Tools\Ozone\Server\GetConfig\ServiceConfig;
6
7
use SocialDept\AtpSchema\Generated\Tools\Ozone\Server\GetConfig\ViewerConfig;
7
8
8
-
class GetConfigResponse
9
+
/**
10
+
* @implements Arrayable<string, mixed>
11
+
*/
12
+
class GetConfigResponse implements Arrayable
9
13
{
10
14
public function __construct(
11
15
public readonly ?ServiceConfig $appview = null,
···
26
30
viewer: isset($data['viewer']) ? ViewerConfig::fromArray($data['viewer']) : null,
27
31
verifierDid: $data['verifierDid'] ?? null,
28
32
);
33
+
}
34
+
35
+
public function toArray(): array
36
+
{
37
+
return [
38
+
'appview' => $this->appview?->toArray(),
39
+
'pds' => $this->pds?->toArray(),
40
+
'blobDivert' => $this->blobDivert?->toArray(),
41
+
'chat' => $this->chat?->toArray(),
42
+
'viewer' => $this->viewer?->toArray(),
43
+
'verifierDid' => $this->verifierDid,
44
+
];
29
45
}
30
46
}
+18
-4
src/Data/Responses/Ozone/Team/ListMembersResponse.php
+18
-4
src/Data/Responses/Ozone/Team/ListMembersResponse.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data\Responses\Ozone\Team;
4
4
5
-
class ListMembersResponse
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
use Illuminate\Support\Collection;
7
+
8
+
/**
9
+
* @implements Arrayable<string, mixed>
10
+
*/
11
+
class ListMembersResponse implements Arrayable
6
12
{
7
13
/**
8
-
* @param array<array<string, mixed>> $members Array of team member objects
14
+
* @param Collection<int, array<string, mixed>> $members Collection of team member objects
9
15
*/
10
16
public function __construct(
11
-
public readonly array $members,
17
+
public readonly Collection $members,
12
18
public readonly ?string $cursor = null,
13
19
) {}
14
20
15
21
public static function fromArray(array $data): self
16
22
{
17
23
return new self(
18
-
members: $data['members'] ?? [],
24
+
members: collect($data['members'] ?? []),
19
25
cursor: $data['cursor'] ?? null,
20
26
);
27
+
}
28
+
29
+
public function toArray(): array
30
+
{
31
+
return [
32
+
'members' => $this->members->all(),
33
+
'cursor' => $this->cursor,
34
+
];
21
35
}
22
36
}
+44
src/Data/Responses/Ozone/Team/MemberResponse.php
+44
src/Data/Responses/Ozone/Team/MemberResponse.php
···
1
+
<?php
2
+
3
+
namespace SocialDept\AtpClient\Data\Responses\Ozone\Team;
4
+
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, mixed>
9
+
*/
10
+
class MemberResponse implements Arrayable
11
+
{
12
+
public function __construct(
13
+
public readonly string $did,
14
+
public readonly bool $disabled,
15
+
public readonly ?string $role = null,
16
+
public readonly ?string $createdAt = null,
17
+
public readonly ?string $updatedAt = null,
18
+
public readonly ?string $lastUpdatedBy = null,
19
+
) {}
20
+
21
+
public static function fromArray(array $data): self
22
+
{
23
+
return new self(
24
+
did: $data['did'],
25
+
disabled: $data['disabled'] ?? false,
26
+
role: $data['role'] ?? null,
27
+
createdAt: $data['createdAt'] ?? null,
28
+
updatedAt: $data['updatedAt'] ?? null,
29
+
lastUpdatedBy: $data['lastUpdatedBy'] ?? null,
30
+
);
31
+
}
32
+
33
+
public function toArray(): array
34
+
{
35
+
return array_filter([
36
+
'did' => $this->did,
37
+
'disabled' => $this->disabled,
38
+
'role' => $this->role,
39
+
'createdAt' => $this->createdAt,
40
+
'updatedAt' => $this->updatedAt,
41
+
'lastUpdatedBy' => $this->lastUpdatedBy,
42
+
], fn ($v) => $v !== null);
43
+
}
44
+
}
+6
-1
src/Data/StrongRef.php
+6
-1
src/Data/StrongRef.php
···
2
2
3
3
namespace SocialDept\AtpClient\Data;
4
4
5
-
class StrongRef
5
+
use Illuminate\Contracts\Support\Arrayable;
6
+
7
+
/**
8
+
* @implements Arrayable<string, string>
9
+
*/
10
+
class StrongRef implements Arrayable
6
11
{
7
12
public function __construct(
8
13
public readonly string $uri,
+1
src/Enums/Nsid/AtprotoSync.php
+1
src/Enums/Nsid/AtprotoSync.php
···
10
10
case GetBlob = 'com.atproto.sync.getBlob';
11
11
case GetRepo = 'com.atproto.sync.getRepo';
12
12
case ListRepos = 'com.atproto.sync.listRepos';
13
+
case ListReposByCollection = 'com.atproto.sync.listReposByCollection';
13
14
case GetLatestCommit = 'com.atproto.sync.getLatestCommit';
14
15
case GetRecord = 'com.atproto.sync.getRecord';
15
16
case ListBlobs = 'com.atproto.sync.listBlobs';
+10
-5
src/Enums/Scope.php
+10
-5
src/Enums/Scope.php
···
2
2
3
3
namespace SocialDept\AtpClient\Enums;
4
4
5
+
use BackedEnum;
6
+
5
7
enum Scope: string
6
8
{
7
9
// Transition scopes (current)
···
13
15
/**
14
16
* Build a repo scope string for record operations.
15
17
*
16
-
* @param string $collection The collection NSID (e.g., 'app.bsky.feed.post')
17
-
* @param string|null $action The action (create, update, delete)
18
+
* @param string|BackedEnum $collection The collection NSID (e.g., 'app.bsky.feed.post')
19
+
* @param array|null $actions The action (create, update, delete)
20
+
*
21
+
* @return string
18
22
*/
19
-
public static function repo(string $collection, ?string $action = null): string
23
+
public static function repo(string|BackedEnum $collection, ?array $actions = []): string
20
24
{
25
+
$collection = $collection instanceof BackedEnum ? $collection->value : $collection;
21
26
$scope = "repo:{$collection}";
22
27
23
-
if ($action !== null) {
24
-
$scope .= "?action={$action}";
28
+
if (!empty($actions)) {
29
+
$scope .= '?' . implode('&', array_map(fn ($action) => "action={$action}", $actions));
25
30
}
26
31
27
32
return $scope;
+2
-3
src/Facades/Atp.php
+2
-3
src/Facades/Atp.php
···
3
3
namespace SocialDept\AtpClient\Facades;
4
4
5
5
use Illuminate\Support\Facades\Facade;
6
+
use SocialDept\AtpClient\AtpClient;
6
7
use SocialDept\AtpClient\Auth\OAuthEngine;
7
-
use SocialDept\AtpClient\Client\AtpClient;
8
-
use SocialDept\AtpClient\Client\Public\AtpPublicClient;
9
8
use SocialDept\AtpClient\Contracts\CredentialProvider;
10
9
11
10
/**
12
11
* @method static AtpClient as(string $actor)
13
12
* @method static AtpClient login(string $actor, string $password)
14
13
* @method static OAuthEngine oauth()
15
-
* @method static AtpPublicClient public(?string $service = null)
14
+
* @method static AtpClient public(?string $service = null)
16
15
* @method static void setDefaultProvider(CredentialProvider $provider)
17
16
*
18
17
* @see \SocialDept\AtpClient\AtpClientServiceProvider
+3
-3
src/Http/HasHttp.php
+3
-3
src/Http/HasHttp.php
···
16
16
17
17
trait HasHttp
18
18
{
19
-
protected SessionManager $sessions;
19
+
protected ?SessionManager $sessions = null;
20
20
21
-
protected string $did;
21
+
protected ?string $did = null;
22
22
23
-
protected DPoPClient $dpopClient;
23
+
protected ?DPoPClient $dpopClient = null;
24
24
25
25
protected ?ScopeChecker $scopeChecker = null;
26
26
+5
-186
src/RichText/TextBuilder.php
+5
-186
src/RichText/TextBuilder.php
···
2
2
3
3
namespace SocialDept\AtpClient\RichText;
4
4
5
-
use SocialDept\AtpResolver\Facades\Resolver;
5
+
use SocialDept\AtpClient\Builders\Concerns\BuildsRichText;
6
6
7
7
class TextBuilder
8
8
{
9
-
protected string $text = '';
10
-
protected array $facets = [];
9
+
use BuildsRichText;
11
10
12
11
/**
13
12
* Create a new text builder instance
14
13
*/
15
14
public static function make(): self
16
15
{
17
-
return new self();
16
+
return new self;
18
17
}
19
18
20
19
/**
···
22
21
*/
23
22
public static function build(callable $callback): array
24
23
{
25
-
$builder = new self();
24
+
$builder = new self;
26
25
$callback($builder);
27
26
28
27
return $builder->toArray();
29
28
}
30
29
31
30
/**
32
-
* Add plain text
33
-
*/
34
-
public function text(string $text): self
35
-
{
36
-
$this->text .= $text;
37
-
38
-
return $this;
39
-
}
40
-
41
-
/**
42
-
* Add a new line
43
-
*/
44
-
public function newLine(): self
45
-
{
46
-
$this->text .= "\n";
47
-
48
-
return $this;
49
-
}
50
-
51
-
/**
52
-
* Add mention (@handle)
53
-
*/
54
-
public function mention(string $handle, ?string $did = null): self
55
-
{
56
-
$handle = ltrim($handle, '@');
57
-
$start = $this->getBytePosition();
58
-
$this->text .= '@'.$handle;
59
-
$end = $this->getBytePosition();
60
-
61
-
// Resolve DID if not provided
62
-
if (! $did) {
63
-
try {
64
-
$did = Resolver::handleToDid($handle);
65
-
} catch (\Exception $e) {
66
-
// If resolution fails, still add the text but skip the facet
67
-
return $this;
68
-
}
69
-
}
70
-
71
-
$this->facets[] = [
72
-
'index' => [
73
-
'byteStart' => $start,
74
-
'byteEnd' => $end,
75
-
],
76
-
'features' => [
77
-
[
78
-
'$type' => 'app.bsky.richtext.facet#mention',
79
-
'did' => $did,
80
-
],
81
-
],
82
-
];
83
-
84
-
return $this;
85
-
}
86
-
87
-
/**
88
-
* Add link with custom display text
89
-
*/
90
-
public function link(string $text, string $uri): self
91
-
{
92
-
$start = $this->getBytePosition();
93
-
$this->text .= $text;
94
-
$end = $this->getBytePosition();
95
-
96
-
$this->facets[] = [
97
-
'index' => [
98
-
'byteStart' => $start,
99
-
'byteEnd' => $end,
100
-
],
101
-
'features' => [
102
-
[
103
-
'$type' => 'app.bsky.richtext.facet#link',
104
-
'uri' => $uri,
105
-
],
106
-
],
107
-
];
108
-
109
-
return $this;
110
-
}
111
-
112
-
/**
113
-
* Add a URL (displayed as-is)
114
-
*/
115
-
public function url(string $url): self
116
-
{
117
-
return $this->link($url, $url);
118
-
}
119
-
120
-
/**
121
-
* Add hashtag
122
-
*/
123
-
public function tag(string $tag): self
124
-
{
125
-
$tag = ltrim($tag, '#');
126
-
127
-
$start = $this->getBytePosition();
128
-
$this->text .= '#'.$tag;
129
-
$end = $this->getBytePosition();
130
-
131
-
$this->facets[] = [
132
-
'index' => [
133
-
'byteStart' => $start,
134
-
'byteEnd' => $end,
135
-
],
136
-
'features' => [
137
-
[
138
-
'$type' => 'app.bsky.richtext.facet#tag',
139
-
'tag' => $tag,
140
-
],
141
-
],
142
-
];
143
-
144
-
return $this;
145
-
}
146
-
147
-
/**
148
-
* Auto-detect and add facets from plain text
149
-
*/
150
-
public function autoDetect(string $text): self
151
-
{
152
-
$start = $this->getBytePosition();
153
-
$this->text .= $text;
154
-
155
-
// Detect facets in the added text
156
-
$detected = FacetDetector::detect($text);
157
-
158
-
// Adjust byte positions to account for existing text
159
-
foreach ($detected as $facet) {
160
-
$facet['index']['byteStart'] += $start;
161
-
$facet['index']['byteEnd'] += $start;
162
-
$this->facets[] = $facet;
163
-
}
164
-
165
-
return $this;
166
-
}
167
-
168
-
/**
169
-
* Get current byte position
170
-
*/
171
-
protected function getBytePosition(): int
172
-
{
173
-
return strlen($this->text);
174
-
}
175
-
176
-
/**
177
-
* Get the text content
178
-
*/
179
-
public function getText(): string
180
-
{
181
-
return $this->text;
182
-
}
183
-
184
-
/**
185
-
* Get the facets
186
-
*/
187
-
public function getFacets(): array
188
-
{
189
-
return $this->facets;
190
-
}
191
-
192
-
/**
193
31
* Build the final text and facets array
194
32
*/
195
33
public function toArray(): array
196
34
{
197
-
return [
198
-
'text' => $this->text,
199
-
'facets' => $this->facets,
200
-
];
35
+
return $this->getTextAndFacets();
201
36
}
202
37
203
38
/**
···
233
68
public function getByteCount(): int
234
69
{
235
70
return strlen($this->text);
236
-
}
237
-
238
-
/**
239
-
* Check if text exceeds AT Protocol post limit (300 graphemes)
240
-
*/
241
-
public function exceedsLimit(int $limit = 300): bool
242
-
{
243
-
return $this->getGraphemeCount() > $limit;
244
-
}
245
-
246
-
/**
247
-
* Get grapheme count (closest to what AT Protocol uses)
248
-
*/
249
-
public function getGraphemeCount(): int
250
-
{
251
-
return grapheme_strlen($this->text);
252
71
}
253
72
254
73
/**