Skip to content

Commit

Permalink
Better killmail notification (#88)
Browse files Browse the repository at this point in the history
* use rate limiting for contracts

* improve error handling

* update symfony/yaml

* update killmail notification

* add finishing touches for slack notification

* switch to seat 5 populateMessage instead of toSlack

* fix killmail notification dispatching

* handle string->carbon conversion in timestamp()

* add discord notification

* styleci

* styleci

---------

Co-authored-by: Crypta Eve <[email protected]>
  • Loading branch information
recursivetree and Crypta-Eve authored Nov 9, 2023
1 parent 030a16d commit bfc503f
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 64 deletions.
1 change: 1 addition & 0 deletions src/Config/notifications.alerts.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
'handlers' => [
'mail' => \Seat\Notifications\Notifications\Characters\Mail\Killmail::class,
'slack' => \Seat\Notifications\Notifications\Characters\Slack\Killmail::class,
'discord' => \Seat\Notifications\Notifications\Characters\Discord\Killmail::class,
],
],
//
Expand Down
74 changes: 63 additions & 11 deletions src/Notifications/Characters/Discord/Killmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,76 @@ public function __construct(KillmailDetail $killmail)
public function populateMessage(DiscordMessage $message, $notifiable)
{
$message
->content('A kill has been recorded for your corporation!')
->embed(function (DiscordEmbed $embed) {
$embed->timestamp($this->killmail->killmail_time);
$embed->author('SeAT Kilometer', asset('web/img/favico/apple-icon-180x180.png'));

$embed->field('Ship Type', $this->killmail->victim->ship->typeName);
$embed->field('zKB Link', sprintf('https://zkillboard.com/kill/%d/', $this->killmail->killmail_id));
// title
$embed->title(sprintf('%s destroyed in %s', $this->killmail->victim->ship->typeName, $this->killmail->solar_system->name));

// zkb link
$embed->field(function (DiscordEmbedField $field) {
$field
->name('ZKB')
->value('https://zkillboard.com/kill/' . $this->killmail->killmail_id . '/')
->long();
});

// victim
$embed->field(function (DiscordEmbedField $field) {
$field
->name('Victim')
->value(sprintf("Name: %s\nCorp: %s",
$this->zKillBoardToDiscordLink('character', $this->killmail->victim->character_id, $this->killmail->victim->character->name),
$this->zKillBoardToDiscordLink('corporation', $this->killmail->victim->corporation_id, $this->killmail->victim->corporation->name)
))->long();
});

//final blow
$final_blow = $this->killmail->attackers()->where('final_blow', true)->first();
$embed->field(function (DiscordEmbedField $field) use ($final_blow) {
$field
->name('Final Blow')
->value(sprintf("Name: %s\nCorp:%s",
$this->zKillBoardToDiscordLink('character', $final_blow->character_id, $final_blow->character->name),
$this->zKillBoardToDiscordLink('corporation', $final_blow->corporation_id, $final_blow->corporation->name)
))->long();
});

// attackers
$attacker_count = $this->killmail->attackers()->count();
$attackers = $this->killmail->attackers()
->orderByDesc('damage_done')
->limit(5)
->get()
->map(function ($attacker) {
return sprintf('%s | %s dmg',
$this->zKillBoardToDiscordLink('character', $attacker->character_id, $attacker->character->name),
number_format($attacker->damage_done),
);
});
$others = $attacker_count - $attackers->count();
if($others > 0){
$attackers = $attackers->push(sprintf('%d more', $others));
}
$embed->field(function (DiscordEmbedField $field) use ($attackers, $attacker_count) {
$field
->name(sprintf('Attackers (%d)', $attacker_count))
->value(implode("\n", $attackers->toArray()))
->long();
});

// details
$embed->field(function (DiscordEmbedField $field) {
$field->name('System');
$field->value(
$this->zKillBoardToDiscordLink(
'system',
$this->killmail->solar_system_id,
sprintf('%s (%s)',
$this->killmail->solar_system->name,
number_format($this->killmail->solar_system->security, 2))));
$field
->name('Details')
->value(sprintf("Time: %s Eve Time\nISK Value: %s ISK",
carbon($this->killmail->killmail_time)->toTimeString(),
number_format($this->killmail->victim->getTotalEstimateAttribute())
))->long();
});

//footer
$embed->thumb($this->typeIconUrl($this->killmail->victim->ship_type_id));
$embed->footer('zKillboard', 'https://zkillboard.com/img/wreck.png');

Expand Down
77 changes: 58 additions & 19 deletions src/Notifications/Characters/Slack/Killmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,68 @@ public function __construct(KillmailDetail $killmail)
* @param $notifiable
* @return SlackMessage
*/
public function toSlack($notifiable)
public function populateMessage(SlackMessage $message, $notifiable)
{

$message = (new SlackMessage)
->content('A kill has been recorded for your corporation!')
$message
->from('SeAT Kilometer', $this->typeIconUrl($this->killmail->victim->ship_type_id))
->attachment(function ($attachment) {

$attachment
->timestamp(carbon($this->killmail->killmail_time))
->fields([
'Ship Type' => $this->killmail->victim->ship->typeName,
'zKB Link' => 'https://zkillboard.com/kill/' . $this->killmail->killmail_id . '/',
])
//title with zkb link
->title(sprintf('%s destroyed in %s', $this->killmail->victim->ship->typeName, $this->killmail->solar_system->name))
->field('ZKB', 'https://zkillboard.com/kill/' . $this->killmail->killmail_id . '/')
->field(function ($field) {

$field->title('System')
->content($this->zKillBoardToSlackLink(
'system',
$this->killmail->solar_system_id,
$this->killmail->solar_system->name . ' (' .
number_format($this->killmail->solar_system->security, 2) . ')'));
$field
->title('Victim')
->content(sprintf("Name: %s\nCorp: %s",
$this->zKillBoardToSlackLink('character', $this->killmail->victim->character_id, $this->killmail->victim->character->name),
$this->zKillBoardToSlackLink('corporation', $this->killmail->victim->corporation_id, $this->killmail->victim->corporation->name)
))
->long();
})
->field(function ($field) {
$final_blow = $this->killmail->attackers()->where('final_blow', true)->first();
$field
->title('Final Blow')
->content(sprintf("Name: %s\nCorp:%s",
$this->zKillBoardToSlackLink('character', $final_blow->character_id, $final_blow->character->name),
$this->zKillBoardToSlackLink('corporation', $final_blow->corporation_id, $final_blow->corporation->name)
))
->long();
})
->field(function ($field) {
$attacker_count = $this->killmail->attackers()->count();
$attackers = $this->killmail->attackers()
->orderByDesc('damage_done')
->limit(5)
->get()
->map(function ($attacker) {
return sprintf('%s | %s dmg',
$this->zKillBoardToSlackLink('character', $attacker->character_id, $attacker->character->name),
number_format($attacker->damage_done),
);
});

$others = $attacker_count - $attackers->count();
if($others > 0){
$attackers = $attackers->push(sprintf('%d more', $others));
}

$field
->title(sprintf('Attackers (%d)', $attacker_count))
->content(implode("\n", $attackers->toArray()))
->long();
})
->field(function ($field) {
$field
->title('Details')
->content(sprintf("Time: %s Eve Time\nISK Value: %s ISK",
carbon($this->killmail->killmail_time)->toTimeString(),
number_format($this->killmail->victim->getTotalEstimateAttribute())))
->long();
})

->thumb($this->typeIconUrl($this->killmail->victim->ship_type_id))
->fallback('Kill details')
->footer('zKillboard')
Expand All @@ -106,11 +145,11 @@ public function toArray($notifiable)
{

return [
'characterName' => $this->killmail->attacker->character->name,
'characterName' => $this->killmail->attacker->character->name,
'corporationName' => $this->killmail->attacker->corporation->name,
'typeName' => $this->killmail->victim->ship->typeName,
'system' => $this->killmail->solar_system->name,
'security' => $this->killmail->solar_system->security,
'typeName' => $this->killmail->victim->ship->typeName,
'system' => $this->killmail->solar_system->name,
'security' => $this->killmail->solar_system->security,
];
}
}
37 changes: 5 additions & 32 deletions src/Observers/KillmailNotificationObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@

namespace Seat\Notifications\Observers;

use Illuminate\Notifications\AnonymousNotifiable;
use Seat\Eveapi\Models\Killmails\KillmailDetail;
use Seat\Notifications\Models\NotificationGroup;
use Seat\Notifications\Notifications\Characters\Slack\Killmail;
use Seat\Notifications\Traits\NotificationDispatchTool;

/**
Expand Down Expand Up @@ -60,35 +58,7 @@ private function dispatch(KillmailDetail $killmail)
if (carbon()->diffInSeconds($killmail->killmail_time) > self::EXPIRATION_DELAY)
return;

// ask Laravel to enqueue the notification
$manager = new AnonymousNotifiable();

// retrieve routing candidates for the current notification
$routes = $this->getRoutingCandidates($killmail);

// in case no routing candidates has been delivered, exit
if ($routes->isEmpty())
return;

// append each routing candidate to the notification process
$routes->each(function ($integration) use ($manager) {
$manager->route($integration->channel, $integration->route);
});

// enqueue the notification - delay by 5 minutes to leave time to SeAT to pull complete killmail from ESI
$when = now()->addMinutes(5);
$manager->notify((new Killmail($killmail))->delay($when));
}

/**
* Provide a unique list of notification channels (including driver and route).
*
* @param \Seat\Eveapi\Models\Killmails\KillmailDetail $killmail
* @return \Illuminate\Support\Collection
*/
private function getRoutingCandidates(KillmailDetail $killmail)
{
$settings = NotificationGroup::with('alerts', 'affiliations')
$groups = NotificationGroup::with('alerts', 'affiliations')
->whereHas('alerts', function ($query) {
$query->where('alert', 'killmail');
})->whereHas('affiliations', function ($query) use ($killmail) {
Expand All @@ -97,7 +67,10 @@ private function getRoutingCandidates(KillmailDetail $killmail)
$query->orWhereIn('affiliation_id', $killmail->attackers->pluck('character_id'));
$query->orWhereIn('affiliation_id', $killmail->attackers->pluck('corporation_id'));
})->get();
$when = now()->addMinutes(5);

return $this->mapGroups($settings);
$this->dispatchNotifications('Killmail', $groups, function ($notificationClass) use ($when, $killmail) {
return (new $notificationClass($killmail))->delay($when);
});
}
}
12 changes: 10 additions & 2 deletions src/Services/Discord/Messages/DiscordEmbed.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,19 @@ public function description(string $description): DiscordEmbed
/**
* Set the embed's timestamp.
*
* @param \DateInterval|\DateTimeInterface|int $timestamp
* @param \DateInterval|\DateTimeInterface|int|string $timestamp
* @return $this
*/
public function timestamp(\DateInterval|\DateTimeInterface|int $timestamp): DiscordEmbed
public function timestamp(\DateInterval|\DateTimeInterface|int|string $timestamp): DiscordEmbed
{
// many notifications directly pass a datetime string from a model into this,
// but $this->availableAt doesn't handle strings. Since it's from the laravel
// InteractsWithTime trait, we also can't fix this. Therefore, we special-case
// strings here.
if(is_string($timestamp)){
$timestamp = carbon($timestamp);
}

$this->timestamp = $this->availableAt($timestamp);

return $this;
Expand Down

0 comments on commit bfc503f

Please sign in to comment.