2020-11-16 09:07:57 +00:00
using System ;
using System.Collections.Concurrent ;
2020-09-20 20:36:04 +00:00
using System.Threading.Tasks ;
2020-11-16 09:07:57 +00:00
using App.Metrics ;
2020-12-23 01:19:02 +00:00
using Myriad.Builders ;
using Myriad.Rest ;
2020-09-20 20:36:04 +00:00
using NodaTime ;
2020-11-16 09:07:57 +00:00
using Serilog ;
2020-09-20 20:36:04 +00:00
namespace PluralKit.Bot
{
public class ErrorMessageService
{
private static readonly Duration MinErrorInterval = Duration . FromSeconds ( 10 ) ;
private readonly ConcurrentDictionary < ulong , Instant > _lastErrorInChannel = new ConcurrentDictionary < ulong , Instant > ( ) ;
2020-11-16 09:07:57 +00:00
private readonly IMetrics _metrics ;
private readonly ILogger _logger ;
2020-12-23 01:19:02 +00:00
private readonly DiscordApiClient _rest ;
2020-11-16 09:07:57 +00:00
2020-12-23 01:19:02 +00:00
public ErrorMessageService ( IMetrics metrics , ILogger logger , DiscordApiClient rest )
2020-11-16 09:07:57 +00:00
{
_metrics = metrics ;
_logger = logger ;
2020-12-23 01:19:02 +00:00
_rest = rest ;
2020-11-16 09:07:57 +00:00
}
2020-09-20 20:36:04 +00:00
2020-12-23 01:19:02 +00:00
public async Task SendErrorMessage ( ulong channelId , string errorId )
2020-09-20 20:36:04 +00:00
{
2020-11-16 09:07:57 +00:00
var now = SystemClock . Instance . GetCurrentInstant ( ) ;
2020-12-23 01:19:02 +00:00
if ( ! ShouldSendErrorMessage ( channelId , now ) )
2020-09-20 20:36:04 +00:00
{
2020-12-23 01:19:02 +00:00
_logger . Warning ( "Rate limited sending error message to {ChannelId} with error code {ErrorId}" , channelId , errorId ) ;
2020-11-16 09:07:57 +00:00
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "throttled" ) ;
return ;
2020-09-20 20:36:04 +00:00
}
2020-11-16 09:07:57 +00:00
2020-12-23 01:19:02 +00:00
var embed = new EmbedBuilder ( )
. Color ( 0xE74C3C )
. Title ( "Internal error occurred" )
. Description ( "For support, please send the error code above in **#bug-reports-and-errors** on **[the support server *(click to join)*](https://discord.gg/PczBt78)** with a description of what you were doing at the time." )
. Footer ( new ( errorId ) )
. Timestamp ( now . ToDateTimeOffset ( ) . ToString ( "O" ) ) ;
2020-11-16 09:07:57 +00:00
try
{
2020-12-23 01:19:02 +00:00
await _rest . CreateMessage ( channelId , new ( )
{
Content = $"> **Error code:** `{errorId}`" ,
Embed = embed . Build ( )
} ) ;
_logger . Information ( "Sent error message to {ChannelId} with error code {ErrorId}" , channelId , errorId ) ;
2020-11-16 09:07:57 +00:00
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "sent" ) ;
}
catch ( Exception e )
{
2020-12-23 01:19:02 +00:00
_logger . Error ( e , "Error sending error message to {ChannelId}" , channelId ) ;
2020-11-16 09:07:57 +00:00
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "failed" ) ;
throw ;
}
}
2020-12-23 01:19:02 +00:00
private bool ShouldSendErrorMessage ( ulong channelId , Instant now )
2020-11-16 09:07:57 +00:00
{
2020-12-23 01:19:02 +00:00
if ( _lastErrorInChannel . TryGetValue ( channelId , out var lastErrorTime ) )
2020-11-16 09:07:57 +00:00
{
var interval = now - lastErrorTime ;
if ( interval < MinErrorInterval )
return false ;
}
2020-12-23 01:19:02 +00:00
_lastErrorInChannel [ channelId ] = now ;
2020-11-16 09:07:57 +00:00
return true ;
2020-09-20 20:36:04 +00:00
}
}
}