2020-05-01 23:52:52 +00:00
using System.Threading.Tasks ;
2020-06-11 21:20:46 +00:00
using DSharpPlus ;
using DSharpPlus.Entities ;
2020-05-01 23:52:52 +00:00
using DSharpPlus.EventArgs ;
2020-06-11 21:20:46 +00:00
using DSharpPlus.Exceptions ;
2020-05-01 23:52:52 +00:00
2020-06-11 21:20:46 +00:00
using PluralKit.Core ;
2020-05-01 23:52:52 +00:00
2020-07-18 11:05:22 +00:00
using Serilog ;
2020-05-01 23:52:52 +00:00
namespace PluralKit.Bot
{
public class ReactionAdded : IEventHandler < MessageReactionAddEventArgs >
{
2020-08-29 11:46:27 +00:00
private readonly IDatabase _db ;
private readonly ModelRepository _repo ;
private readonly EmbedService _embeds ;
private readonly ILogger _logger ;
2020-05-01 23:52:52 +00:00
2020-08-29 11:46:27 +00:00
public ReactionAdded ( EmbedService embeds , ILogger logger , IDatabase db , ModelRepository repo )
2020-05-01 23:52:52 +00:00
{
2020-06-11 21:20:46 +00:00
_embeds = embeds ;
2020-08-29 11:46:27 +00:00
_db = db ;
_repo = repo ;
2020-07-18 11:05:22 +00:00
_logger = logger . ForContext < ReactionAdded > ( ) ;
2020-05-01 23:52:52 +00:00
}
2020-06-11 21:20:46 +00:00
public async Task Handle ( MessageReactionAddEventArgs evt )
{
await TryHandleProxyMessageReactions ( evt ) ;
}
private async ValueTask TryHandleProxyMessageReactions ( MessageReactionAddEventArgs evt )
{
// Only proxies in guild text channels
if ( evt . Channel . Type ! = ChannelType . Text ) return ;
2020-06-24 14:47:34 +00:00
// Sometimes we get events from users that aren't in the user cache
// In that case we get a "broken" user object (where eg. calling IsBot throws an exception)
// We just ignore all of those for now, should be quite rare...
if ( ! evt . Client . TryGetCachedUser ( evt . User . Id , out _ ) ) return ;
2020-06-18 15:49:43 +00:00
// Ignore reactions from bots (we can't DM them anyway)
if ( evt . User . IsBot ) return ;
2020-06-11 21:20:46 +00:00
2020-08-29 11:46:27 +00:00
Task < FullMessage > GetMessage ( ) = >
_db . Execute ( c = > _repo . GetMessage ( c , evt . Message . Id ) ) ;
2020-06-11 21:20:46 +00:00
FullMessage msg ;
switch ( evt . Emoji . Name )
{
// Message deletion
case "\u274C" : // Red X
2020-08-29 11:46:27 +00:00
if ( ( msg = await GetMessage ( ) ) ! = null )
2020-06-11 21:20:46 +00:00
await HandleDeleteReaction ( evt , msg ) ;
break ;
case "\u2753" : // Red question mark
case "\u2754" : // White question mark
2020-08-29 11:46:27 +00:00
if ( ( msg = await GetMessage ( ) ) ! = null )
2020-06-11 21:20:46 +00:00
await HandleQueryReaction ( evt , msg ) ;
break ;
case "\U0001F514" : // Bell
case "\U0001F6CE" : // Bellhop bell
case "\U0001F3D3" : // Ping pong paddle (lol)
case "\u23F0" : // Alarm clock
case "\u2757" : // Exclamation mark
2020-08-29 11:46:27 +00:00
if ( ( msg = await GetMessage ( ) ) ! = null )
2020-06-11 21:20:46 +00:00
await HandlePingReaction ( evt , msg ) ;
break ;
}
}
private async ValueTask HandleDeleteReaction ( MessageReactionAddEventArgs evt , FullMessage msg )
{
2020-06-12 18:29:50 +00:00
if ( ! evt . Channel . BotHasAllPermissions ( Permissions . ManageMessages ) ) return ;
2020-06-11 21:20:46 +00:00
// Can only delete your own message
if ( msg . Message . Sender ! = evt . User . Id ) return ;
try
{
await evt . Message . DeleteAsync ( ) ;
}
catch ( NotFoundException )
{
// Message was deleted by something/someone else before we got to it
}
2020-08-29 11:46:27 +00:00
await _db . Execute ( c = > _repo . DeleteMessage ( c , evt . Message . Id ) ) ;
2020-06-11 21:20:46 +00:00
}
private async ValueTask HandleQueryReaction ( MessageReactionAddEventArgs evt , FullMessage msg )
{
// Try to DM the user info about the message
2020-07-02 16:29:04 +00:00
var member = await evt . Guild . GetMember ( evt . User . Id ) ;
2020-06-11 21:20:46 +00:00
try
{
await member . SendMessageAsync ( embed : await _embeds . CreateMemberEmbed ( msg . System , msg . Member , evt . Guild , LookupContext . ByNonOwner ) ) ;
await member . SendMessageAsync ( embed : await _embeds . CreateMessageInfoEmbed ( evt . Client , msg ) ) ;
}
catch ( UnauthorizedException ) { } // No permissions to DM, can't check for this :(
2020-07-18 11:05:22 +00:00
await TryRemoveOriginalReaction ( evt ) ;
2020-06-11 21:20:46 +00:00
}
private async ValueTask HandlePingReaction ( MessageReactionAddEventArgs evt , FullMessage msg )
2020-05-01 23:52:52 +00:00
{
2020-06-11 21:20:46 +00:00
if ( ! evt . Channel . BotHasAllPermissions ( Permissions . SendMessages ) ) return ;
// Check if the "pinger" has permission to send messages in this channel
// (if not, PK shouldn't send messages on their behalf)
2020-07-02 16:29:04 +00:00
var guildUser = await evt . Guild . GetMember ( evt . User . Id ) ;
2020-06-11 21:20:46 +00:00
var requiredPerms = Permissions . AccessChannels | Permissions . SendMessages ;
2020-07-02 16:29:04 +00:00
if ( guildUser = = null | | ( guildUser . PermissionsIn ( evt . Channel ) & requiredPerms ) ! = requiredPerms ) return ;
2020-06-11 21:20:46 +00:00
if ( msg . System . PingsEnabled )
{
// If the system has pings enabled, go ahead
var embed = new DiscordEmbedBuilder ( ) . WithDescription ( $"[Jump to pinged message]({evt.Message.JumpLink})" ) ;
2020-06-20 15:33:10 +00:00
await evt . Channel . SendMessageFixedAsync ( $"Psst, **{msg.Member.DisplayName()}** (<@{msg.Message.Sender}>), you have been pinged by <@{evt.User.Id}>." , embed : embed . Build ( ) ,
new IMention [ ] { new UserMention ( msg . Message . Sender ) } ) ;
2020-06-11 21:20:46 +00:00
}
else
{
// If not, tell them in DMs (if we can)
try
{
2020-06-20 15:33:10 +00:00
await guildUser . SendMessageFixedAsync ( $"{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-08-25 20:44:52 +00:00
await guildUser . SendMessageFixedAsync ( $"<@{msg.Message.Sender}>" . AsCode ( ) ) ;
2020-06-11 21:20:46 +00:00
}
catch ( UnauthorizedException ) { }
}
2020-07-18 11:05:22 +00:00
await TryRemoveOriginalReaction ( evt ) ;
}
private async Task TryRemoveOriginalReaction ( MessageReactionAddEventArgs evt )
{
try
{
if ( evt . Channel . BotHasAllPermissions ( Permissions . ManageMessages ) )
await evt . Message . DeleteReactionAsync ( evt . Emoji , evt . User ) ;
}
catch ( UnauthorizedException )
{
var botPerms = evt . Channel . BotPermissions ( ) ;
// So, in some cases (see Sentry issue 11K) the above check somehow doesn't work, and
// Discord returns a 403 Unauthorized. TODO: figure out the root cause here instead of a workaround
_logger . Warning ( "Attempted to remove reaction {Emoji} from user {User} on message {Channel}/{Message}, but got 403. Bot has permissions {Permissions} according to itself." ,
evt . Emoji . Id , evt . User . Id , evt . Channel . Id , evt . Message . Id , botPerms ) ;
}
2020-05-01 23:52:52 +00:00
}
}
}