AnyEvent-Discord

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for perl module AnyEvent::Discord

0.7 2021-10-25

 - Update to AnyEvent::WebSocket::Client 0.54
 - Change initial delay for reconnect to 1 second from 4 seconds
 - Fire an AnyEvent->condvar->send on timer end
 - Add timestamp to debug messages when debug is true
 - Randomize heartbeat interval to Discord spec

0.4 2021-01-28

 - Back to Moops

0.2 2021-01-28

 - Fix indexing for Zydeco

doc/AnyEvent-Discord.md  view on Meta::CPAN

- base\_uri (String) (optional)

    The base URI for communicating with the Discord API.

- socket\_options (HashRef) (optional)

    Used to override options to sent to AnyEvent::WebSocket::Client, if needed.

- verbose (Num) (defaults to 0)

    Verbose output, writes internal debug information on 1, writes network messages
    on 2.

# DATA ACCESSORS

- guilds

    Available/created/seen guilds

- channels

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

      'guild_delete'        => [sub { $self->_event_guild_delete(@_); }],
      'channel_create'      => [sub { $self->_event_channel_create(@_); }],
      'channel_delete'      => [sub { $self->_event_channel_delete(@_); }],
      'guild_member_create' => [sub { $self->_event_guild_member_create(@_); }],
      'guild_member_remove' => [sub { $self->_event_guild_member_remove(@_); }]
    };
  }

  method on(Str $event_type, CodeRef $handler) {
    $event_type = lc($event_type);
    $self->_debug('Requesting attach of handler ' . $handler . ' to event ' . $event_type);

    $self->_events->{$event_type} //= [];
    return if (scalar(grep { $_ eq $handler } @{$self->_events->{$event_type}}) > 0);

    $self->_debug('Attaching handler ' . $handler . ' to event ' . $event_type);
    push( @{$self->_events->{$event_type}}, $handler );
  }

  method off(Str $event_type, CodeRef $handler?) {
    $event_type = lc($event_type);
    $self->_debug('Requesting detach of handler ' . ($handler or 'n/a') . ' from event ' . $event_type);
    if ($self->_events->{$event_type}) {
      if ($handler) {
        my $index = 0;
        while ($index < scalar(@{$self->_events->{$event_type}})) {
          if ($self->_events->{$event_type}->[$index] eq $handler) {
            $self->_debug('Detaching handler ' . $handler . ' from event ' . $event_type);
            splice( @{$self->_events->{$event_type}}, $index, 1 );
          }
          $index++;
        }
      } else {
        $self->_debug('Detaching ' . scalar(@{$self->_events->{$event_type}}) . ' handler(s) from event ' . $event_type);
        delete($self->_events->{$event_type});
      }
    }
  }

  method connect() {
    my $gateway = $self->_lookup_gateway();

    $self->_debug('Connecting to ' . $gateway);

    my $ws = AnyEvent::WebSocket::Client->new($self->socket_options);
    $ws->connect($gateway)->cb(sub {
      my $socket = eval { shift->recv };
      if ($@) {
        $self->_debug('Received error connecting: ' . $@);
        $self->_handle_internal_event('error', $@);
        return;
      }
      $self->_debug('Connected to ' . $gateway);

      $self->_socket($socket);
  
      # If we send malformed content, bail out
      $socket->on('parse_error', sub {
        my ($c, $error) = @_;
        $self->_debug(Data::Dumper::Dumper($error));
        die $error;
      });

      # Handle reconnection
      $socket->on('finish', sub {
        my ($c) = @_;
        $self->_debug('Received disconnect');
        $self->_handle_internal_event('disconnected');
        unless ($self->_force_disconnect()) {
          my $seconds = $self->_backoff->failure();
          $self->_debug('Reconnecting in ' . $seconds);
          my $reconnect;
          $reconnect = AnyEvent->timer(
            after => $seconds,
            cb    => sub {
              $self->connect();
              $reconnect = undef;
              AnyEvent->condvar->send();
            }
          );
        }
      });

      # Event handler
      $socket->on('each_message', sub {
        my ($c, $message) = @_;
        $self->_trace('ws in: ' . $message->{'body'});
        my $payload;
        try {
          $payload = AnyEvent::Discord::Payload->from_json($message->{'body'});
        } catch {
          $self->_debug($_);
          return;
        };
        unless ($payload and defined $payload->op) {
          $self->_debug('Invalid payload received from Discord: ' . $message->{'body'});
          return;
        }
        $self->_sequence(0 + $payload->s) if ($payload->s and $payload->s > 0);

        if ($payload->op == 10) {
          $self->_event_hello($payload);
        } elsif ($payload->d) {
          if ($payload->d->{'author'}) {
            my $user = $payload->d->{'author'};
            $self->users->{$user->{'id'}} = $user->{'username'};
          }
          $self->_handle_event($payload);
        }
      });

      $self->_discord_identify();
      $self->_debug('Completed connection sequence');
      $self->_backoff->success();
      AnyEvent->condvar->send();
    });
  }

  method send($channel_id, $content) {
    return $self->_discord_api('POST', 'channels/' . $channel_id . '/messages', encode_json({content => $content}));
  }

  method typing($channel_id) {

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

        return decode_json($res->decoded_content());
      } else {
        return $res->decoded_content();
      }
    }
    return;
  }

  # Send the 'identify' event to the Discord websocket
  method _discord_identify() {
    $self->_debug('Sending identify');
    $self->_ws_send_payload(AnyEvent::Discord::Payload->from_hashref({
      op => 2,
      d  => {
        token           => $self->token,
        compress        => JSON::false,
        large_threshold => 250,
        shard           => [0, 1],
        properties => {
          '$os'      => 'linux',
          '$browser' => $self->user_agent(),
          '$device'  => $self->user_agent(),
        }
      }
    }));
  }

  # Send a payload to the Discord websocket
  method _ws_send_payload(AnyEvent::Discord::Payload $payload) {
    unless ($self->_socket) {
      $self->_debug('Attempted to send payload to disconnected socket');
      return;
    }
    my $msg = $payload->as_json;
    $self->_trace('ws out: ' . $msg);
    $self->_socket->send($msg);
  }

  # Look up the gateway endpoint using the Discord API
  method _lookup_gateway() {
    my $payload = $self->_discord_api('GET', 'gateway');

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

    $gateway .= '/' unless ($gateway =~/\/$/);
    $gateway .= '?v=6&encoding=json';
    return $gateway;
  }

  # Dispatch an internal event type
  method _handle_internal_event(Str $type) {
    foreach my $event_source (qw(_internal_events _events)) {
      if ($self->{$event_source}->{$type}) {
        map {
          $self->_debug('Sending ' . ( $event_source =~ /internal/ ? 'internal' : 'caller' ) . ' event ' . $type);
          $_->($self);
        } @{ $self->{$event_source}->{$type} };
      }
    }
  }

  # Dispatch a Discord event type
  method _handle_event(AnyEvent::Discord::Payload $payload) {
    my $type = lc($payload->t);
    $self->_debug('Got event ' . $type);
    foreach my $event_source (qw(_internal_events _events)) {
      if ($self->{$event_source}->{$type}) {
        map {
          $self->_debug('Sending ' . ( $event_source =~ /internal/ ? 'internal' : 'caller' ) . ' event ' . $type);
          $_->($self, $payload->d, $payload->op);
        } @{ $self->{$event_source}->{$type} };
      }
    }
  }

  # Send debug messages to console if verbose is >=1
  method _debug(Str $message) {
    say time . ' ' . $message if ($self->verbose);
  }

  # Send trace messages to console if verbose is 2
  method _trace(Str $message) {
    say time . ' ' . $message if ($self->verbose and $self->verbose == 2);
  }

  # Called when Discord provides the 'hello' event
  method _event_hello(AnyEvent::Discord::Payload $payload) {
    $self->_debug('Received hello event');
    my $interval = $payload->d->{'heartbeat_interval'};
    my $timer = AnyEvent->timer(
      after    => $interval * rand() / 1000,
      interval => $interval / 1000,
      cb       => sub {
        $self->_debug('Heartbeat');
        $self->_ws_send_payload(AnyEvent::Discord::Payload->from_hashref({
          op => 1,
          d  => $self->_sequence()
        }));
        AnyEvent->condvar->send();
      }
    );
    $self->_heartbeat($timer);
  }

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

=item base_uri (String) (optional)

The base URI for communicating with the Discord API.

=item socket_options (HashRef) (optional)

Used to override options to sent to AnyEvent::WebSocket::Client, if needed.

=item verbose (Num) (defaults to 0)

Verbose output, writes internal debug information at 1, additionally writes
network conversation at 2.

=back

=head1 DATA ACCESSORS

=over 4

=item guilds



( run in 0.824 second using v1.01-cache-2.11-cpan-3cd7ad12f66 )