AnyEvent-Discord
view release on metacpan or search on metacpan
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 )