2021-11-09 01:48:47 -05:00
using System.Diagnostics ;
2020-09-20 22:36:04 +02:00
2020-11-16 10:07:57 +01:00
using App.Metrics ;
2020-12-23 02:19:02 +01:00
using Myriad.Builders ;
using Myriad.Rest ;
2021-11-26 21:10:56 -05:00
using Myriad.Rest.Types.Requests ;
using Myriad.Types ;
2023-05-15 15:17:34 +00:00
using Myriad.Gateway ;
2020-09-20 22:36:04 +02:00
using NodaTime ;
2020-11-16 10:07:57 +01:00
using Serilog ;
2021-11-26 21:10:56 -05:00
namespace PluralKit.Bot ;
public class ErrorMessageService
2020-09-20 22:36:04 +02:00
{
2021-11-26 21:10:56 -05:00
// globally rate limit errors for now, don't want to spam users when something breaks
private static readonly Duration MinErrorInterval = Duration . FromSeconds ( 10 ) ;
2022-01-21 00:10:51 -05:00
private static readonly Duration IntervalFromStartup = Duration . FromMinutes ( 2 ) ;
2021-11-26 21:10:56 -05:00
2022-03-30 04:36:22 -04:00
private readonly ILogger _logger ;
2021-11-27 11:09:08 -05:00
private readonly BotConfig _botConfig ;
2021-11-26 21:10:56 -05:00
private readonly IMetrics _metrics ;
private readonly DiscordApiClient _rest ;
2021-11-27 11:09:08 -05:00
public ErrorMessageService ( BotConfig botConfig , IMetrics metrics , ILogger logger , DiscordApiClient rest )
2020-09-20 22:36:04 +02:00
{
2021-11-27 11:09:08 -05:00
_botConfig = botConfig ;
2021-11-26 21:10:56 -05:00
_metrics = metrics ;
_logger = logger ;
_rest = rest ;
2021-11-09 01:48:47 -05:00
2021-11-26 21:10:56 -05:00
lastErrorTime = SystemClock . Instance . GetCurrentInstant ( ) ;
}
2021-08-27 11:03:47 -04:00
2021-11-26 21:10:56 -05:00
// private readonly ConcurrentDictionary<ulong, Instant> _lastErrorInChannel = new ConcurrentDictionary<ulong, Instant>();
private Instant lastErrorTime { get ; set ; }
2021-08-27 11:03:47 -04:00
2023-05-15 15:17:34 +00:00
public async Task InteractionRespondWithErrorMessage ( InteractionCreateEvent evt , string errorId )
{
var now = SystemClock . Instance . GetCurrentInstant ( ) ;
if ( ! ShouldSendErrorMessage ( null , now ) )
{
_logger . Warning ( "Rate limited sending error interaction response for id {InteractionId} with error code {ErrorId}" ,
evt . Id , errorId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "throttled" ) ;
return ;
}
var embed = CreateErrorEmbed ( errorId , now ) ;
try
{
var interactionData = new InteractionApplicationCommandCallbackData
{
Content = $"> **Error code:** `{errorId}`" ,
Embeds = new [ ] { embed } ,
Flags = Message . MessageFlags . Ephemeral
} ;
await _rest . CreateInteractionResponse ( evt . Id , evt . Token ,
new InteractionResponse
{
Type = InteractionResponse . ResponseType . ChannelMessageWithSource ,
Data = interactionData ,
} ) ;
_logger . Information ( "Sent error message interaction response for id {InteractionId} with error code {ErrorId}" , evt . Id , errorId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "sent" ) ;
}
catch ( Exception e )
{
_logger . Error ( e , "Error sending error interaction response for id {InteractionId}" , evt . Id ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "failed" ) ;
throw ;
}
}
2021-11-26 21:10:56 -05:00
public async Task SendErrorMessage ( ulong channelId , string errorId )
{
var now = SystemClock . Instance . GetCurrentInstant ( ) ;
if ( ! ShouldSendErrorMessage ( channelId , now ) )
2020-11-16 10:07:57 +01:00
{
2021-11-26 21:10:56 -05:00
_logger . Warning ( "Rate limited sending error message to {ChannelId} with error code {ErrorId}" ,
channelId , errorId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "throttled" ) ;
return ;
2020-11-16 10:07:57 +01:00
}
2020-09-20 22:36:04 +02:00
2023-05-15 15:17:34 +00:00
var embed = CreateErrorEmbed ( errorId , now ) ;
2021-11-26 21:10:56 -05:00
try
2020-09-20 22:36:04 +02:00
{
2021-11-26 21:10:56 -05:00
await _rest . CreateMessage ( channelId ,
2023-05-15 15:17:34 +00:00
new MessageRequest { Content = $"> **Error code:** `{errorId}`" , Embeds = new [ ] { embed } } ) ;
2020-11-16 10:07:57 +01:00
2021-11-26 21:10:56 -05:00
_logger . Information ( "Sent error message to {ChannelId} with error code {ErrorId}" , channelId , errorId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "sent" ) ;
}
catch ( Exception e )
2020-11-16 10:07:57 +01:00
{
2021-11-26 21:10:56 -05:00
_logger . Error ( e , "Error sending error message to {ChannelId}" , channelId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "failed" ) ;
throw ;
}
}
2021-11-09 01:48:47 -05:00
2023-05-15 15:17:34 +00:00
private Embed CreateErrorEmbed ( string errorId , Instant now )
{
var channelInfo = _botConfig . IsBetaBot
? "**#beta-testing** on **[the support server *(click to join)*](https://discord.gg/THvbH59btW)**"
: "**#bug-reports-and-errors** on **[the support server *(click to join)*](https://discord.gg/PczBt78)**" ;
return new EmbedBuilder ( )
. Color ( 0xE74C3C )
. Title ( "Internal error occurred" )
2025-09-01 21:23:24 -04:00
. Description ( $"**If you need support,** please send/forward the error code above **as text** in {channelInfo} with a description of what you were doing at the time." )
2023-05-15 15:17:34 +00:00
. Footer ( new Embed . EmbedFooter ( errorId ) )
. Timestamp ( now . ToDateTimeOffset ( ) . ToString ( "O" ) )
. Build ( ) ;
}
private bool ShouldSendErrorMessage ( ulong? channelId , Instant now )
2021-11-26 21:10:56 -05:00
{
// if (_lastErrorInChannel.TryGetValue(channelId, out var lastErrorTime))
2021-11-09 01:48:47 -05:00
2021-11-26 21:10:56 -05:00
var startupTime = Instant . FromDateTimeUtc ( Process . GetCurrentProcess ( ) . StartTime . ToUniversalTime ( ) ) ;
// don't send errors during startup
// mostly because Npgsql throws a bunch of errors when opening connections sometimes???
2021-11-27 11:09:08 -05:00
if ( now - startupTime < IntervalFromStartup & & ! _botConfig . IsBetaBot )
2021-11-26 21:10:56 -05:00
return false ;
2020-11-16 10:07:57 +01:00
2021-11-26 21:10:56 -05:00
var interval = now - lastErrorTime ;
if ( interval < MinErrorInterval )
return false ;
// _lastErrorInChannel[channelId] = now;
lastErrorTime = now ;
return true ;
2020-09-20 22:36:04 +02:00
}
}