Discord-Client

 view release on metacpan or  search on metacpan

lib/Discord/Client.pm  view on Meta::CPAN

}

sub _receive_gateway_event {
    my ( $self, $event, $data, $gateway, $packet, $received_at ) = @_;

    # Publish READY state before startup observers or application handlers run.
    $self->{cache}->apply( $event, $data ) if $self->{cache};
    if ( $event eq 'READY' ) {
        $self->{user} = $data->{user};
        $self->{commands}->application_id( $data->{application}{id} )
            if ref( $data->{application} ) eq 'HASH' && defined( $data->{application}{id} );
    }

    # Interaction admission has its own limit, independent of event callbacks.
    $self->_dispatch_interaction( $data, $received_at ) if $event eq 'INTERACTION_CREATE';
    return;
}

sub _dispatch_interaction {
    my ( $self, $data, $received_at ) = @_;

    # Pass receipt time through so queue delays count toward acknowledgement expiry.
    my %options = ( acknowledgement_only => 1 );
    $options{received_at} = $received_at if defined $received_at;
    my $acknowledgement = $self->{interactions}->handle( $data, %options );
    my $weak = $self;
    weaken($weak);

    # Report admission errors here; the dispatcher reports admitted handler errors.
    $acknowledgement->on_fail(
        sub {
            my ( undef, undef, $error ) = @_;
            return unless $weak && !$weak->{closed} && blessed($error);
            if ( $error->category eq 'protocol' || $error->category eq 'queue_full' ) {
                $weak->_report($error);
            }
        }
    );

    return $acknowledgement;
}

# These accessors expose shared components without fetching or copying resources.
sub loop { $_[0]->{loop} }
sub rest { $_[0]->{rest} }
sub gateway { $_[0]->{gateway} }
sub commands { $_[0]->{commands} }
sub interactions { $_[0]->{interactions} }
sub user { $_[0]->{user} }
sub cache { $_[0]->{cache} }

sub model {
    my ( $self, $type, $data ) = @_;
    require Discord::Client::Model;
    return Discord::Client::Model->wrap( $type, $data, rest => $self->{rest} );
}

sub stats {
    my ($self) = @_;

    # Expose counters without including credentials or event payloads.
    return {
        rest => $self->{rest}->stats,
        gateway => $self->{gateway}->stats,
        callbacks => scalar( keys %{ $self->{callbacks} } ),
        ( $self->{cache} ? ( cache => $self->{cache}->stats ) : () )
    };
}

sub command {
    my ( $self, @args ) = @_;
    $self->{commands}->define(@args);
    return $self;
}

sub component {
    my ( $self, @args ) = @_;
    $self->{commands}->component(@args);
    return $self;
}

sub on {
    my ( $self, $event, $callback ) = @_;

    # Normalize event names once so registration and delivery use the same keys.
    croak 'Event and callback are required' unless defined($event) && !ref($event) && ref($callback) eq 'CODE';
    push @{ $self->{handlers}{ uc($event) } }, $callback;
    return $self;
}

sub on_message {
    my ( $self, $callback ) = @_;
    croak 'Message callback must be a code reference' unless ref($callback) eq 'CODE';

    # Reuse event lifetime/error handling while presenting a REST-backed snapshot.
    return $self->on(
        MESSAGE_CREATE => sub {
            my ( $data, $packet, $bot ) = @_;
            my $message = $bot->model( message => $data );
            return $callback->( $message, $bot );
        }
    );
}

sub _emit {
    my ( $self, $event, @data ) = @_;
    return if $self->{closed};

    # Bound pending work before invoking another application callback.
    for my $callback ( @{ $self->{handlers}{ uc($event) } // [] } ) {
        last if $self->{closed};
        if ( keys( %{ $self->{callbacks} } ) >= $self->{max_callbacks} ) {
            $self->_report_error( 'Client event callback capacity exceeded', 'queue_full' );
            last;
        }

        # Isolate synchronous exceptions so later listeners can still run.
        my $future = eval { $callback->( @data, $self ) };
        if ($@) {
            $self->_report_error( 'Client event handler failed', 'handler' );
            next;

lib/Discord/Client.pm  view on Meta::CPAN

updates and command dispatch precede this queue; command dispatch is separately
bounded by C<interaction_options>.
C<on(error =E<gt> CODE)> receives the same arguments as C<on_error>.

=head2 on_message

    # Ignore bot messages and return the reply Future.
    $bot->on_message(sub {
        my ($message, $bot) = @_;
        return if $message->is_from_bot;
        return unless $message->content eq '!ping';
        return $message->reply('Pong!');
    });

Registers a callback receiving C<($message, $bot)> and returns the client.
The message is a L<Discord::Client::Model::Message> snapshot using the client's
REST transport. Wrapping performs no request and needs no cache.
Set message intents explicitly and enable Message Content in the Developer
Portal when reading message text. Bot messages are not filtered automatically.
Raw C<MESSAGE_CREATE> callbacks still receive hashrefs.

=head2 model

    my $channel = $bot->model(channel => {id => $channel_id});

Synchronously wraps data with the client's REST transport. Types are C<message>,
C<channel>, C<user> and C<guild>; see L<Discord::Client::Model/wrap>.
Invalid data throws. Wrapping performs no request.

=head2 rest

Returns the L<Discord::Client::REST> client.

=head2 gateway

Returns the L<Discord::Client::Gateway> or L<Discord::Client::Gateway::Shards>.

=head2 commands

Returns the L<Discord::Client::Commands> registry.

=head2 interactions

Returns the L<Discord::Client::Interactions> dispatcher.

=head2 loop

Returns the IO::Async::Loop.

=head2 user

Returns the bot user hashref after READY, or undef before it.

=head2 cache

Returns the configured Cache object, or undef when disabled.

=head2 stats

Returns a hashref of REST, Gateway, pending callback and optional cache counters.
It does not include payloads or credentials.

=head1 EXAMPLES

From a source checkout, set C<DISCORD_BOT_TOKEN> and C<TEST_GUILD_ID>.
Invite the bot with C<bot> and C<applications.commands> scopes and leave the
Interactions Endpoint URL empty for Gateway delivery.

    # Deploy /ping once, then run the bot.
    perl examples/sync-commands.pl examples/ping.pl
    perl examples/ping.pl

Deployment replaces this application's commands in the test server, removing
commands absent from the example. Run one example receiver per bot token.

The L<example scripts|https://github.com/viasnake/Discord-Client/tree/main/examples>
cover arguments, buttons, forms, channels, messages and asynchronous work.
Replace C<ping.pl> in both commands to run a slash-command example.
C<messages.pl> needs no deployment; it selects C<GUILDS>, C<GUILD_MESSAGES>,
C<DIRECT_MESSAGES> and C<MESSAGE_CONTENT>. Enable Message Content in the portal.
C<workflow-await.pl> additionally needs C<cpanm Future::AsyncAwait>.
For dependencies under C<local/>, prefix commands with C<PERL5LIB=local/lib/perl5>.

=head1 LICENSE

Apache License 2.0.

=cut



( run in 1.793 second using v1.01-cache-2.11-cpan-3f962061493 )