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 )