Bluesky
view release on metacpan or search on metacpan
lib/Bluesky.pm view on Meta::CPAN
# Ensure proxy is NOT used for standard repo/feed calls
$self->at->http->at_protocol_proxy(undef);
return $at;
}
method login ( $identifier, $password ) { $self->at->login( $identifier, $password ); }
method resume( $accessJwt, $refreshJwt, $token_type = 'Bearer', $dpop_key_jwk = (), $client_id = (), $handle = (), $pds = (), $scope = () ) {
$self->at->resume( $accessJwt, $refreshJwt, $token_type, $dpop_key_jwk, $client_id, $handle, $pds, $scope );
}
method oauth_start( $handle, $client_id, $redirect_uri, $scope = 'atproto' ) {
return $self->at->oauth_start( $handle, $client_id, $redirect_uri, $scope );
}
method oauth_callback( $code, $state ) { return $self->at->oauth_callback( $code, $state ); }
method oauth_helper (%args) {
my $handle = $args{handle} or die 'Handle is required for oauth_helper';
# client_id base MUST be localhost for the PDS to recognize it as metadata-less
my $client_id_base = $args{client_id} // 'http://localhost';
# RFC 8252 requires loopback IP (127.0.0.1) for the actual redirect
my $redirect_uri = $args{redirect} // 'http://127.0.0.1:8888/callback';
# Transitional scopes for chat access via PDS proxy
my $scope = $args{scope} // 'atproto transition:generic transition:chat.bsky';
my $on_success = $args{on_success} // sub { say 'Success! Session established.' };
# Build the magic localhost Client ID manually to ensure %20 encoding for spaces
require URI::Escape;
my $scope_encoded = URI::Escape::uri_escape($scope);
my $redirect_encoded = URI::Escape::uri_escape($redirect_uri);
my $client_id = "$client_id_base?scope=$scope_encoded&redirect_uri=$redirect_encoded";
say "Starting OAuth flow for $handle...";
my $auth_url = $self->oauth_start( $handle, $client_id, $redirect_uri, $scope );
say 'Please open the following URL in your browser:';
say "\n $auth_url\n";
if ( $args{listen} ) {
# Attempt to start a local server to catch the callback
try {
require Mojolicious;
my $app = Mojolicious->new();
my $port = 8888;
$port = $1 if $redirect_uri =~ /:(\d+)/;
my $path = URI->new($redirect_uri)->path || '/';
$app->routes->get($path)->to(
cb => sub ($c) {
my $code = $c->param('code');
my $state = $c->param('state');
if ( $code && $state ) {
say 'Exchanging code for tokens...';
try {
$self->oauth_callback( $code, $state );
$on_success->($self);
$c->render( text => '<h1>Success!</h1><p>You can close this window and return to the terminal.</p>' );
# Stop loop after response
require Mojo::IOLoop;
Mojo::IOLoop->timer( 1 => sub { exit 0 } );
}
catch ($e) {
$c->render( text => 'OAuth Callback failed: ' . $e, status => 500 );
die $e;
}
}
else {
$c->render( text => 'Error: Missing code or state.', status => 400 );
}
}
);
say "Starting local server on port $port to catch the callback...";
$app->log->level('error');
require Mojo::Server::Daemon;
# Listen on all interfaces but specific to the redirect_uri port
my $daemon = Mojo::Server::Daemon->new( app => $app, listen => [ 'http://127.0.0.1:' . $port ] );
$daemon->run();
return; # Exit method if listener ran (it blocks until finished or exits)
}
catch ($e) {
warn "Could not start listener: $e\nFalling back to manual input.\n";
}
}
# Manual fallback (only reached if listen is false or failed)
say 'After authorizing, you will be redirected to a URL that looks like:';
say "$redirect_uri?code=...&state=...";
say "\nPlease paste the FULL redirect URL here:";
my $callback_url = <STDIN>;
chomp $callback_url;
my ($code) = $callback_url =~ /[?&]code=([^&]+)/;
my ($state) = $callback_url =~ /[?&]state=([^&]+)/;
if ( $code && $state ) {
$self->oauth_callback( $code, $state );
$on_success->($self);
}
else {
die "Could not find code and state in the provided URL.\n";
}
}
method firehose( $callback, $url = () ) { $self->at->firehose( $callback, $url ); }
method session () { $self->at->session; }
#
method did() { $self->at->did }
# Feeds and content
method getTrendingTopics(%args) {
$self->_at_for('app.bsky.unspecced.getTrendingTopics')->get( 'app.bsky.unspecced.getTrendingTopics' => \%args );
}
method getTimeline(%args) { $self->_at_for('app.bsky.feed.getTimeline')->get( 'app.bsky.feed.getTimeline' => \%args ); }
method getAuthorFeed(%args) { $self->_at_for('app.bsky.feed.getAuthorFeed')->get( 'app.bsky.feed.getAuthorFeed' => \%args ); }
method getPostThread(%args) { $self->_at_for('app.bsky.feed.getPostThread')->get( 'app.bsky.feed.getPostThread' => \%args ); }
method getFeed( $feed, %args ) {
my $res = $self->_at_for('app.bsky.feed.getFeed')->get( 'app.bsky.feed.getFeed' => { feed => $feed, %args } );
$res ? $res->{feed} // () : $res;
}
( run in 0.808 second using v1.01-cache-2.11-cpan-acf6aa7dc9e )