2020-12-23 01:19:02 +00:00
using Myriad.Cache ;
using Myriad.Extensions ;
2020-12-22 12:15:26 +00:00
using Myriad.Gateway ;
2020-12-23 01:19:02 +00:00
using Myriad.Rest ;
using Myriad.Rest.Exceptions ;
using Myriad.Rest.Types ;
2020-12-25 12:58:45 +00:00
using Myriad.Rest.Types.Requests ;
2020-12-23 01:19:02 +00:00
using Myriad.Types ;
2020-12-22 12:15:26 +00:00
2020-06-11 21:20:46 +00:00
using PluralKit.Core ;
2020-05-01 23:52:52 +00:00
2021-11-30 02:35:21 +00:00
using NodaTime ;
2020-07-18 11:05:22 +00:00
using Serilog ;
2021-11-27 02:10:56 +00:00
namespace PluralKit.Bot ;
public class ReactionAdded : IEventHandler < MessageReactionAddEvent >
2020-05-01 23:52:52 +00:00
{
2021-11-27 02:10:56 +00:00
private readonly Bot _bot ;
private readonly IDiscordCache _cache ;
private readonly Cluster _cluster ;
private readonly CommandMessageService _commandMessageService ;
private readonly IDatabase _db ;
private readonly EmbedService _embeds ;
private readonly ILogger _logger ;
private readonly ModelRepository _repo ;
private readonly DiscordApiClient _rest ;
public ReactionAdded ( ILogger logger , IDatabase db , ModelRepository repo ,
CommandMessageService commandMessageService , IDiscordCache cache , Bot bot , Cluster cluster ,
DiscordApiClient rest , EmbedService embeds )
2020-05-01 23:52:52 +00:00
{
2021-11-27 02:10:56 +00:00
_db = db ;
_repo = repo ;
_commandMessageService = commandMessageService ;
_cache = cache ;
_bot = bot ;
_cluster = cluster ;
_rest = rest ;
_embeds = embeds ;
_logger = logger . ForContext < ReactionAdded > ( ) ;
}
2020-05-01 23:52:52 +00:00
2021-11-27 02:10:56 +00:00
public async Task Handle ( Shard shard , MessageReactionAddEvent evt )
{
await TryHandleProxyMessageReactions ( evt ) ;
}
private async ValueTask TryHandleProxyMessageReactions ( MessageReactionAddEvent evt )
{
// Sometimes we get events from users that aren't in the user cache
// We just ignore all of those for now, should be quite rare...
if ( ! ( await _cache . TryGetUser ( evt . UserId ) is User user ) )
return ;
2020-06-11 21:20:46 +00:00
2021-11-27 02:10:56 +00:00
// ignore any reactions added by *us*
if ( evt . UserId = = await _cache . GetOwnUser ( ) )
return ;
// Ignore reactions from bots (we can't DM them anyway)
if ( user . Bot ) return ;
var channel = await _cache . GetChannel ( evt . ChannelId ) ;
// check if it's a command message first
// since this can happen in DMs as well
if ( evt . Emoji . Name = = "\u274c" )
2020-06-11 21:20:46 +00:00
{
2021-11-27 02:10:56 +00:00
// in DMs, allow deleting any PK message
if ( channel . GuildId = = null )
{
await HandleCommandDeleteReaction ( evt , null ) ;
2020-12-23 01:19:02 +00:00
return ;
2021-11-27 02:10:56 +00:00
}
2020-12-23 01:19:02 +00:00
2021-11-27 02:10:56 +00:00
var commandMsg = await _commandMessageService . GetCommandMessage ( evt . MessageId ) ;
if ( commandMsg ! = null )
{
await HandleCommandDeleteReaction ( evt , commandMsg ) ;
2021-09-25 19:04:06 +00:00
return ;
2021-11-27 02:10:56 +00:00
}
}
2021-09-25 19:04:06 +00:00
2021-11-27 02:10:56 +00:00
// Proxied messages only exist in guild text channels, so skip checking if we're elsewhere
if ( ! DiscordUtils . IsValidGuildChannel ( channel ) ) return ;
2021-09-25 19:04:06 +00:00
2021-11-27 02:10:56 +00:00
switch ( evt . Emoji . Name )
{
// Message deletion
case "\u274C" : // Red X
{
var msg = await _db . Execute ( c = > _repo . GetMessage ( c , evt . MessageId ) ) ;
if ( msg ! = null )
await HandleProxyDeleteReaction ( evt , msg ) ;
2020-11-04 16:30:00 +00:00
2021-11-27 02:10:56 +00:00
break ;
}
case "\u2753" : // Red question mark
case "\u2754" : // White question mark
2021-09-13 06:13:36 +00:00
{
2021-11-27 02:10:56 +00:00
var msg = await _db . Execute ( c = > _repo . GetMessage ( c , evt . MessageId ) ) ;
if ( msg ! = null )
await HandleQueryReaction ( evt , msg ) ;
break ;
2021-09-13 06:13:36 +00:00
}
2021-11-27 02:10:56 +00:00
case "\U0001F514" : // Bell
case "\U0001F6CE" : // Bellhop bell
case "\U0001F3D3" : // Ping pong paddle (lol)
case "\u23F0" : // Alarm clock
case "\u2757" : // Exclamation mark
2020-11-04 16:30:00 +00:00
{
2021-11-27 02:10:56 +00:00
var msg = await _db . Execute ( c = > _repo . GetMessage ( c , evt . MessageId ) ) ;
if ( msg ! = null )
await HandlePingReaction ( evt , msg ) ;
break ;
2020-11-04 16:30:00 +00:00
}
2021-11-27 02:10:56 +00:00
}
}
2020-11-04 16:30:00 +00:00
2021-11-27 02:10:56 +00:00
private async ValueTask HandleProxyDeleteReaction ( MessageReactionAddEvent evt , FullMessage msg )
{
if ( ! ( await _cache . PermissionsIn ( evt . ChannelId ) ) . HasFlag ( PermissionSet . ManageMessages ) )
return ;
2020-11-04 16:30:00 +00:00
2021-11-27 02:10:56 +00:00
var system = await _repo . GetSystemByAccount ( evt . UserId ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
// Can only delete your own message
if ( msg . System . Id ! = system ? . Id ) return ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
try
{
await _rest . DeleteMessage ( evt . ChannelId , evt . MessageId ) ;
2020-06-11 21:20:46 +00:00
}
2021-11-27 02:10:56 +00:00
catch ( NotFoundException )
2020-06-11 21:20:46 +00:00
{
2021-11-27 02:10:56 +00:00
// Message was deleted by something/someone else before we got to it
}
2021-07-27 15:39:37 +00:00
2021-11-27 02:10:56 +00:00
await _repo . DeleteMessage ( evt . MessageId ) ;
}
2020-06-11 21:20:46 +00:00
2021-11-27 02:10:56 +00:00
private async ValueTask HandleCommandDeleteReaction ( MessageReactionAddEvent evt , CommandMessage ? msg )
{
// Can only delete your own message
// (except in DMs, where msg will be null)
// todo: don't try to delete the user's messages
if ( msg ! = null & & msg . AuthorId ! = evt . UserId )
return ;
2020-06-11 21:20:46 +00:00
2021-11-27 02:10:56 +00:00
try
{
await _rest . DeleteMessage ( evt . ChannelId , evt . MessageId ) ;
2020-06-11 21:20:46 +00:00
}
2021-11-27 02:10:56 +00:00
catch ( NotFoundException )
2020-10-18 05:59:36 +00:00
{
2021-11-27 02:10:56 +00:00
// Message was deleted by something/someone else before we got to it
}
2020-10-18 05:59:36 +00:00
2021-11-27 02:10:56 +00:00
// No need to delete database row here, it'll get deleted by the once-per-minute scheduled task.
}
2020-10-18 05:59:36 +00:00
2021-11-27 02:10:56 +00:00
private async ValueTask HandleQueryReaction ( MessageReactionAddEvent evt , FullMessage msg )
{
var guild = await _cache . GetGuild ( evt . GuildId ! . Value ) ;
2020-10-18 05:59:36 +00:00
2021-11-27 02:10:56 +00:00
// Try to DM the user info about the message
try
2020-06-11 21:20:46 +00:00
{
2021-11-27 02:10:56 +00:00
var dm = await _cache . GetOrCreateDmChannel ( _rest , evt . UserId ) ;
await _rest . CreateMessage ( dm . Id , new MessageRequest
2020-06-11 21:20:46 +00:00
{
2021-11-27 02:10:56 +00:00
Embed = await _embeds . CreateMemberEmbed (
msg . System ,
msg . Member ,
guild ,
2021-11-30 02:35:21 +00:00
LookupContext . ByNonOwner ,
DateTimeZone . Utc
2021-11-27 02:10:56 +00:00
)
} ) ;
await _rest . CreateMessage (
dm . Id ,
new MessageRequest { Embed = await _embeds . CreateMessageInfoEmbed ( msg , true ) }
) ;
2020-06-11 21:20:46 +00:00
}
2021-11-27 02:10:56 +00:00
catch ( ForbiddenException ) { } // No permissions to DM, can't check for this :(
2020-06-11 21:20:46 +00:00
2021-11-27 02:10:56 +00:00
await TryRemoveOriginalReaction ( evt ) ;
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
private async ValueTask HandlePingReaction ( MessageReactionAddEvent evt , FullMessage msg )
{
if ( ! ( await _cache . PermissionsIn ( evt . ChannelId ) ) . HasFlag ( PermissionSet . ManageMessages ) )
return ;
// Check if the "pinger" has permission to send messages in this channel
// (if not, PK shouldn't send messages on their behalf)
var member = await _rest . GetGuildMember ( evt . GuildId ! . Value , evt . UserId ) ;
var requiredPerms = PermissionSet . ViewChannel | PermissionSet . SendMessages ;
if ( member = = null | | ! ( await _cache . PermissionsFor ( evt . ChannelId , member ) ) . HasFlag ( requiredPerms ) ) return ;
2021-11-30 02:35:21 +00:00
var config = await _repo . GetSystemConfig ( msg . System . Id ) ;
if ( config . PingsEnabled )
2021-11-27 02:10:56 +00:00
// If the system has pings enabled, go ahead
await _rest . CreateMessage ( evt . ChannelId , new MessageRequest
2020-06-11 21:20:46 +00:00
{
2021-11-27 02:10:56 +00:00
Content = $"Psst, **{msg.Member.DisplayName()}** (<@{msg.Message.Sender}>), you have been pinged by <@{evt.UserId}>." ,
Components = new [ ]
2020-12-23 01:19:02 +00:00
{
2021-11-27 02:10:56 +00:00
new MessageComponent
2021-06-11 16:17:08 +00:00
{
2021-11-27 02:10:56 +00:00
Type = ComponentType . ActionRow ,
Components = new [ ]
2021-06-11 16:17:08 +00:00
{
2021-11-27 02:10:56 +00:00
new MessageComponent
2021-06-11 16:17:08 +00:00
{
2021-11-27 02:10:56 +00:00
Style = ButtonStyle . Link ,
Type = ComponentType . Button ,
Label = "Jump" ,
Url = evt . JumpLink ( )
2021-06-11 16:17:08 +00:00
}
}
2021-11-27 02:10:56 +00:00
}
} ,
AllowedMentions = new AllowedMentions { Users = new [ ] { msg . Message . Sender } }
} ) ;
else
// If not, tell them in DMs (if we can)
try
2020-06-11 21:20:46 +00:00
{
2021-11-27 02:10:56 +00:00
var dm = await _cache . GetOrCreateDmChannel ( _rest , evt . UserId ) ;
await _rest . CreateMessage ( dm . Id ,
new MessageRequest
2020-12-25 12:58:45 +00:00
{
2021-11-27 02:10:56 +00:00
Content =
$"{Emojis.Error} {msg.Member.DisplayName()}'s system has disabled reaction pings. If you want to mention them anyway, you can copy/paste the following message:"
2020-12-25 12:58:45 +00:00
} ) ;
2021-11-27 02:10:56 +00:00
await _rest . CreateMessage (
dm . Id ,
new MessageRequest { Content = $"<@{msg.Message.Sender}>" . AsCode ( ) }
) ;
2020-06-11 21:20:46 +00:00
}
2021-11-27 02:10:56 +00:00
catch ( ForbiddenException ) { }
2020-07-18 11:05:22 +00:00
2021-11-27 02:10:56 +00:00
await TryRemoveOriginalReaction ( evt ) ;
}
2020-07-18 11:05:22 +00:00
2021-11-27 02:10:56 +00:00
private async Task TryRemoveOriginalReaction ( MessageReactionAddEvent evt )
{
if ( ( await _cache . PermissionsIn ( evt . ChannelId ) ) . HasFlag ( PermissionSet . ManageMessages ) )
await _rest . DeleteUserReaction ( evt . ChannelId , evt . MessageId , evt . Emoji , evt . UserId ) ;
2020-05-01 23:52:52 +00:00
}
}