Skip to content

Commit

Permalink
FeedGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
kawax committed Nov 22, 2024
1 parent 8f2ede9 commit b1783c5
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 0 deletions.
53 changes: 53 additions & 0 deletions src/FeedGenerator/FeedGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Revolution\Bluesky\FeedGenerator;

use Illuminate\Http\Request;

final class FeedGenerator
{
protected static array $algos;

/**
* Register FeedGenerator algorithm.
*
* ```
* // Register in your AppServiceProvider::boot()
*
* FeedGenerator::register(name: 'artisan', algo: function(int $limit, string $cursor): array {
* // The implementation is entirely up to you.
*
* $response = Bluesky::searchPosts(q: '#laravel', limit: $limit, cursor: $cursor);
*
* $cursor = $response->json('cursor');
* $feed = $response->collect('posts')->map(function(array $post) {
* return ['post' => data_get($post, 'uri')];
* })->toArray();
*
* return compact('cursor', 'feed');
* });
* ```
*
* @param string $name short name. Used in generator url. `at://did:.../app.bsky.feed.generator/{name}`
* @param callable(int $limit, string $cursor, string $feed): array $algo
*/
public static function register(string $name, callable $algo): void
{
self::$algos[$name] = $algo;
}

public static function getFeedSkeleton(string $name, int $limit, string $cursor, Request $request): mixed
{
return call_user_func(self::$algos[$name], $limit, $cursor, $request);
}

public static function has(string $name): bool
{
return isset(self::$algos[$name]);
}

public static function missing(string $name): bool
{
return ! self::has($name);
}
}
27 changes: 27 additions & 0 deletions src/FeedGenerator/Http/FeedSkeletonController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Revolution\Bluesky\FeedGenerator\Http;

use Illuminate\Http\Request;
use Revolution\AtProto\Lexicon\Enum\Feed;
use Revolution\Bluesky\FeedGenerator\FeedGenerator;
use Revolution\Bluesky\Support\AtUri;

class FeedSkeletonController
{
public function __invoke(Request $request): mixed
{
$at = AtUri::parse($request->input('feed'));

if ($at->collection() !== Feed::Generator->value || FeedGenerator::missing($at->rkey())) {
abort(404);
}

return FeedGenerator::getFeedSkeleton(
name: $at->rkey(),
limit: $request->input('limit', 50),
cursor: $request->input('cursor', ''),
request: $request,
);
}
}
25 changes: 25 additions & 0 deletions src/HasShortHand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Revolution\Bluesky\Client\SubClient\VideoClient;
use Revolution\Bluesky\Record\Block;
use Revolution\Bluesky\Record\Follow;
use Revolution\Bluesky\Record\Generator;
use Revolution\Bluesky\Record\Like;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\Record\Profile;
Expand Down Expand Up @@ -492,6 +493,30 @@ public function getServiceAuth(#[Format('did')] string $aud, ?int $exp = null, #
->getServiceAuth(aud: $aud, exp: $exp, lxm: $lxm);
}

/**
* @param Generator $generator
* @param string $name Generator short name
* @throws AuthenticationException
*/
public function publishFeedGenerator(string $name, Generator $generator): Response
{
return $this->putRecord(
repo: $this->assertDid(),
collection: Feed::Generator->value,
rkey: $name,
record: $generator,
);
}

public function unpublishFeedGenerator(string $name): Response
{
return $this->deleteRecord(
repo: $this->assertDid(),
collection: Feed::Generator->value,
rkey: $name,
);
}

public function getSuggestions(?int $limit = 50, ?string $cursor = null): Response
{
return $this->client(auth: true)
Expand Down
9 changes: 9 additions & 0 deletions src/Providers/BlueskyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Laravel\Socialite\Facades\Socialite;
use Revolution\AtProto\Lexicon\Contracts\App\Bsky\Feed;
use Revolution\Bluesky\BlueskyManager;
use Revolution\Bluesky\Client\AtpClient;
use Revolution\Bluesky\Console\DownloadRecordCommand;
Expand All @@ -17,6 +18,7 @@
use Revolution\Bluesky\Console\NewPrivateKeyCommand;
use Revolution\Bluesky\Contracts\Factory;
use Revolution\Bluesky\Contracts\XrpcClient;
use Revolution\Bluesky\FeedGenerator\Http\FeedSkeletonController;
use Revolution\Bluesky\Socalite\BlueskyProvider;
use Revolution\Bluesky\Socalite\Http\OAuthMetaController;

Expand Down Expand Up @@ -53,6 +55,7 @@ public function boot(): void
}

$this->socialite();
$this->generator();
}

protected function socialite(): void
Expand Down Expand Up @@ -89,4 +92,10 @@ protected function socialite(): void
->name('bluesky.oauth.jwks');
});
}

protected function generator(): void
{
Route::prefix('/xrpc/')
->get(Feed::getFeedSkeleton, FeedSkeletonController::class);
}
}
82 changes: 82 additions & 0 deletions src/Record/Generator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace Revolution\Bluesky\Record;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\Traits\Tappable;
use Revolution\AtProto\Lexicon\Record\App\Bsky\Feed\AbstractGenerator;
use Revolution\Bluesky\Contracts\Recordable;
use Revolution\Bluesky\Types\BlobRef;
use Revolution\Bluesky\Types\SelfLabels;

final class Generator extends AbstractGenerator implements Arrayable, Recordable
{
use HasRecord;
use Macroable;
use Conditionable;
use Tappable;

public function __construct(string $did, string $displayName)
{
$this->did = $did;
$this->displayName = $displayName;
}

public static function create(string $did, string $displayName): self
{
return new self($did, $displayName);
}

public function did(string $did): self
{
$this->did = $did;

return $this;
}

public function displayName(string $displayName): self
{
$this->displayName = $displayName;

return $this;
}

public function description(?string $description = null): self
{
$this->description = $description;

return $this;
}

public function avatar(null|BlobRef|array|callable $avatar = null): self
{
if (is_null($avatar)) {
$this->avatar = $avatar;

return $this;
}

if (is_callable($avatar)) {
$avatar = call_user_func($avatar);
}

if ($avatar instanceof BlobRef) {
$avatar = $avatar->toArray();
}

$this->avatar = $avatar;

return $this;
}

public function labels(?SelfLabels $labels = null): self
{
$this->labels = $labels?->toArray();

return $this;
}
}

0 comments on commit b1783c5

Please sign in to comment.