Initial commit, basic proxying working

This commit is contained in:
Ske
2020-12-22 13:15:26 +01:00
parent c3f6becea4
commit a6fbd869be
109 changed files with 3539 additions and 359 deletions

View File

@@ -1,15 +1,13 @@
using System.Threading.Tasks;
using DSharpPlus;
using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
using Myriad.Gateway;
namespace PluralKit.Bot
{
public interface IEventHandler<in T> where T: DiscordEventArgs
public interface IEventHandler<in T> where T: IGatewayEvent
{
Task Handle(DiscordClient shard, T evt);
Task Handle(Shard shard, T evt);
DiscordChannel ErrorChannelFor(T evt) => null;
ulong? ErrorChannelFor(T evt) => null;
}
}

View File

@@ -5,18 +5,22 @@ using App.Metrics;
using Autofac;
using DSharpPlus;
using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
using Myriad.Cache;
using Myriad.Extensions;
using Myriad.Gateway;
using Myriad.Rest;
using Myriad.Rest.Types.Requests;
using Myriad.Types;
using PluralKit.Core;
namespace PluralKit.Bot
{
public class MessageCreated: IEventHandler<MessageCreateEventArgs>
public class MessageCreated: IEventHandler<MessageCreateEvent>
{
private readonly Bot _bot;
private readonly CommandTree _tree;
private readonly DiscordShardedClient _client;
private readonly IDiscordCache _cache;
private readonly LastMessageCacheService _lastMessageCache;
private readonly LoggerCleanService _loggerClean;
private readonly IMetrics _metrics;
@@ -25,73 +29,81 @@ namespace PluralKit.Bot
private readonly IDatabase _db;
private readonly ModelRepository _repo;
private readonly BotConfig _config;
private readonly DiscordApiClient _rest;
public MessageCreated(LastMessageCacheService lastMessageCache, LoggerCleanService loggerClean,
IMetrics metrics, ProxyService proxy, DiscordShardedClient client,
CommandTree tree, ILifetimeScope services, IDatabase db, BotConfig config, ModelRepository repo)
IMetrics metrics, ProxyService proxy,
CommandTree tree, ILifetimeScope services, IDatabase db, BotConfig config, ModelRepository repo, IDiscordCache cache, Bot bot, DiscordApiClient rest)
{
_lastMessageCache = lastMessageCache;
_loggerClean = loggerClean;
_metrics = metrics;
_proxy = proxy;
_client = client;
_tree = tree;
_services = services;
_db = db;
_config = config;
_repo = repo;
_cache = cache;
_bot = bot;
_rest = rest;
}
public DiscordChannel ErrorChannelFor(MessageCreateEventArgs evt) => evt.Channel;
public ulong? ErrorChannelFor(MessageCreateEvent evt) => evt.ChannelId;
private bool IsDuplicateMessage(DiscordMessage evt) =>
private bool IsDuplicateMessage(Message msg) =>
// We consider a message duplicate if it has the same ID as the previous message that hit the gateway
_lastMessageCache.GetLastMessage(evt.ChannelId) == evt.Id;
_lastMessageCache.GetLastMessage(msg.ChannelId) == msg.Id;
public async Task Handle(DiscordClient shard, MessageCreateEventArgs evt)
public async Task Handle(Shard shard, MessageCreateEvent evt)
{
if (evt.Author?.Id == _client.CurrentUser?.Id) return;
if (evt.Message.MessageType != MessageType.Default) return;
if (IsDuplicateMessage(evt.Message)) return;
if (evt.Author.Id == shard.User?.Id) return;
if (evt.Type != Message.MessageType.Default) return;
if (IsDuplicateMessage(evt)) return;
var guild = evt.GuildId != null ? await _cache.GetGuild(evt.GuildId.Value) : null;
var channel = await _cache.GetChannel(evt.ChannelId);
// Log metrics and message info
_metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived);
_lastMessageCache.AddMessage(evt.Channel.Id, evt.Message.Id);
_lastMessageCache.AddMessage(evt.ChannelId, evt.Id);
// Get message context from DB (tracking w/ metrics)
MessageContext ctx;
await using (var conn = await _db.Obtain())
using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime))
ctx = await _repo.GetMessageContext(conn, evt.Author.Id, evt.Channel.GuildId, evt.Channel.Id);
ctx = await _repo.GetMessageContext(conn, evt.Author.Id, evt.GuildId ?? default, evt.ChannelId);
// Try each handler until we find one that succeeds
if (await TryHandleLogClean(evt, ctx))
return;
// Only do command/proxy handling if it's a user account
if (evt.Message.Author.IsBot || evt.Message.WebhookMessage || evt.Message.Author.IsSystem == true)
if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true)
return;
if (await TryHandleCommand(shard, evt, ctx))
if (await TryHandleCommand(shard, evt, guild, channel, ctx))
return;
await TryHandleProxy(shard, evt, ctx);
await TryHandleProxy(shard, evt, guild, channel, ctx);
}
private async ValueTask<bool> TryHandleLogClean(MessageCreateEventArgs evt, MessageContext ctx)
private async ValueTask<bool> TryHandleLogClean(MessageCreateEvent evt, MessageContext ctx)
{
if (!evt.Message.Author.IsBot || evt.Message.Channel.Type != ChannelType.Text ||
var channel = await _cache.GetChannel(evt.ChannelId);
if (!evt.Author.Bot || channel!.Type != Channel.ChannelType.GuildText ||
!ctx.LogCleanupEnabled) return false;
await _loggerClean.HandleLoggerBotCleanup(evt.Message);
await _loggerClean.HandleLoggerBotCleanup(evt);
return true;
}
private async ValueTask<bool> TryHandleCommand(DiscordClient shard, MessageCreateEventArgs evt, MessageContext ctx)
private async ValueTask<bool> TryHandleCommand(Shard shard, MessageCreateEvent evt, Guild? guild, Channel channel, MessageContext ctx)
{
var content = evt.Message.Content;
var content = evt.Content;
if (content == null) return false;
// Check for command prefix
if (!HasCommandPrefix(content, out var cmdStart))
if (!HasCommandPrefix(content, shard.User?.Id ?? default, out var cmdStart))
return false;
// Trim leading whitespace from command without actually modifying the string
@@ -102,7 +114,7 @@ namespace PluralKit.Bot
try
{
var system = ctx.SystemId != null ? await _db.Execute(c => _repo.GetSystem(c, ctx.SystemId.Value)) : null;
await _tree.ExecuteCommand(new Context(_services, shard, evt.Message, cmdStart, system, ctx));
await _tree.ExecuteCommand(new Context(_services, shard, guild, channel, evt, cmdStart, system, ctx, _bot.BotMemberIn(channel.GuildId!.Value)));
}
catch (PKError)
{
@@ -113,7 +125,7 @@ namespace PluralKit.Bot
return true;
}
private bool HasCommandPrefix(string message, out int argPos)
private bool HasCommandPrefix(string message, ulong currentUserId, out int argPos)
{
// First, try prefixes defined in the config
var prefixes = _config.Prefixes ?? BotConfig.DefaultPrefixes;
@@ -128,23 +140,28 @@ namespace PluralKit.Bot
// Then, check mention prefix (must be the bot user, ofc)
argPos = -1;
if (DiscordUtils.HasMentionPrefix(message, ref argPos, out var id))
return id == _client.CurrentUser.Id;
return id == currentUserId;
return false;
}
private async ValueTask<bool> TryHandleProxy(DiscordClient shard, MessageCreateEventArgs evt, MessageContext ctx)
private async ValueTask<bool> TryHandleProxy(Shard shard, MessageCreateEvent evt, Guild guild, Channel channel, MessageContext ctx)
{
var botMember = _bot.BotMemberIn(channel.GuildId!.Value);
var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, shard.User!.Id, botMember!.Roles);
try
{
return await _proxy.HandleIncomingMessage(shard, evt.Message, ctx, allowAutoproxy: ctx.AllowAutoproxy);
return await _proxy.HandleIncomingMessage(shard, evt, ctx, guild, channel, allowAutoproxy: ctx.AllowAutoproxy, botPermissions);
}
catch (PKError e)
{
// User-facing errors, print to the channel properly formatted
var msg = evt.Message;
if (msg.Channel.Guild == null || msg.Channel.BotHasAllPermissions(Permissions.SendMessages))
await msg.Channel.SendMessageFixedAsync($"{Emojis.Error} {e.Message}");
if (botPermissions.HasFlag(PermissionSet.SendMessages))
{
await _rest.CreateMessage(evt.ChannelId,
new MessageRequest {Content = $"{Emojis.Error} {e.Message}"});
}
}
return false;

View File

@@ -1,9 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using DSharpPlus;
using DSharpPlus.EventArgs;
using Myriad.Gateway;
using PluralKit.Core;
@@ -12,7 +10,7 @@ using Serilog;
namespace PluralKit.Bot
{
// Double duty :)
public class MessageDeleted: IEventHandler<MessageDeleteEventArgs>, IEventHandler<MessageBulkDeleteEventArgs>
public class MessageDeleted: IEventHandler<MessageDeleteEvent>, IEventHandler<MessageDeleteBulkEvent>
{
private static readonly TimeSpan MessageDeleteDelay = TimeSpan.FromSeconds(15);
@@ -27,7 +25,7 @@ namespace PluralKit.Bot
_logger = logger.ForContext<MessageDeleted>();
}
public Task Handle(DiscordClient shard, MessageDeleteEventArgs evt)
public Task Handle(Shard shard, MessageDeleteEvent evt)
{
// Delete deleted webhook messages from the data store
// Most of the data in the given message is wrong/missing, so always delete just to be sure.
@@ -35,7 +33,8 @@ namespace PluralKit.Bot
async Task Inner()
{
await Task.Delay(MessageDeleteDelay);
await _db.Execute(c => _repo.DeleteMessage(c, evt.Message.Id));
// TODO
// await _db.Execute(c => _repo.DeleteMessage(c, evt.Message.Id));
}
// Fork a task to delete the message after a short delay
@@ -44,14 +43,15 @@ namespace PluralKit.Bot
return Task.CompletedTask;
}
public Task Handle(DiscordClient shard, MessageBulkDeleteEventArgs evt)
public Task Handle(Shard shard, MessageDeleteBulkEvent evt)
{
// Same as above, but bulk
async Task Inner()
{
await Task.Delay(MessageDeleteDelay);
_logger.Information("Bulk deleting {Count} messages in channel {Channel}", evt.Messages.Count, evt.Channel.Id);
await _db.Execute(c => _repo.DeleteMessagesBulk(c, evt.Messages.Select(m => m.Id).ToList()));
// TODO
// _logger.Information("Bulk deleting {Count} messages in channel {Channel}", evt.Messages.Count, evt.Channel.Id);
// await _db.Execute(c => _repo.DeleteMessagesBulk(c, evt.Messages.Select(m => m.Id).ToList()));
}
_ = Inner();

View File

@@ -3,14 +3,15 @@ using System.Threading.Tasks;
using App.Metrics;
using DSharpPlus;
using DSharpPlus.EventArgs;
using Myriad.Gateway;
using PluralKit.Core;
namespace PluralKit.Bot
{
public class MessageEdited: IEventHandler<MessageUpdateEventArgs>
public class MessageEdited: IEventHandler<MessageUpdateEvent>
{
private readonly LastMessageCacheService _lastMessageCache;
private readonly ProxyService _proxy;
@@ -29,22 +30,23 @@ namespace PluralKit.Bot
_client = client;
}
public async Task Handle(DiscordClient shard, MessageUpdateEventArgs evt)
public async Task Handle(Shard shard, MessageUpdateEvent evt)
{
if (evt.Author?.Id == _client.CurrentUser?.Id) return;
// Edit message events sometimes arrive with missing data; double-check it's all there
if (evt.Message.Content == null || evt.Author == null || evt.Channel.Guild == null) return;
// Only react to the last message in the channel
if (_lastMessageCache.GetLastMessage(evt.Channel.Id) != evt.Message.Id) return;
// Just run the normal message handling code, with a flag to disable autoproxying
MessageContext ctx;
await using (var conn = await _db.Obtain())
using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime))
ctx = await _repo.GetMessageContext(conn, evt.Author.Id, evt.Channel.GuildId, evt.Channel.Id);
await _proxy.HandleIncomingMessage(shard, evt.Message, ctx, allowAutoproxy: false);
// TODO: fix
// if (evt.Author?.Id == _client.CurrentUser?.Id) return;
//
// // Edit message events sometimes arrive with missing data; double-check it's all there
// if (evt.Message.Content == null || evt.Author == null || evt.Channel.Guild == null) return;
//
// // Only react to the last message in the channel
// if (_lastMessageCache.GetLastMessage(evt.Channel.Id) != evt.Message.Id) return;
//
// // Just run the normal message handling code, with a flag to disable autoproxying
// MessageContext ctx;
// await using (var conn = await _db.Obtain())
// using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime))
// ctx = await _repo.GetMessageContext(conn, evt.Author.Id, evt.Channel.GuildId, evt.Channel.Id);
// await _proxy.HandleIncomingMessage(shard, evt.Message, ctx, allowAutoproxy: false);
}
}
}

View File

@@ -5,13 +5,15 @@ using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
using DSharpPlus.Exceptions;
using Myriad.Gateway;
using PluralKit.Core;
using Serilog;
namespace PluralKit.Bot
{
public class ReactionAdded: IEventHandler<MessageReactionAddEventArgs>
public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
{
private readonly IDatabase _db;
private readonly ModelRepository _repo;
@@ -28,9 +30,9 @@ namespace PluralKit.Bot
_logger = logger.ForContext<ReactionAdded>();
}
public async Task Handle(DiscordClient shard, MessageReactionAddEventArgs evt)
public async Task Handle(Shard shard, MessageReactionAddEvent evt)
{
await TryHandleProxyMessageReactions(shard, evt);
// await TryHandleProxyMessageReactions(shard, evt);
}
private async ValueTask TryHandleProxyMessageReactions(DiscordClient shard, MessageReactionAddEventArgs evt)