Update to D#+ 4.0.0-rc1

This commit is contained in:
Ske 2020-11-15 13:53:31 +01:00
parent 90b2fcfdd4
commit 634173e205
14 changed files with 73 additions and 87 deletions

View File

@ -9,8 +9,6 @@ using App.Metrics;
using Autofac;
using Dapper;
using DSharpPlus;
using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
@ -24,7 +22,6 @@ using Sentry;
using Serilog;
using Serilog.Context;
using Serilog.Events;
namespace PluralKit.Bot
{
@ -55,9 +52,6 @@ namespace PluralKit.Bot
public void Init()
{
// Attach the handlers we need
_client.DebugLogger.LogMessageReceived += FrameworkLog;
// HandleEvent takes a type parameter, automatically inferred by the event type
// It will then look up an IEventHandler<TypeOfEvent> in the DI container and call that object's handler method
// For registering new ones, see Modules.cs
@ -68,12 +62,12 @@ namespace PluralKit.Bot
_client.MessageReactionAdded += HandleEvent;
// Update shard status for shards immediately on connect
_client.Ready += args =>
_client.Ready += (client, _) =>
{
_hasReceivedReady = true;
return UpdateBotStatus(args.Client);
return UpdateBotStatus(client);
};
_client.Resumed += args => UpdateBotStatus(args.Client);
_client.Resumed += (client, _) => UpdateBotStatus(client);
// Init the shard stuff
_services.Resolve<ShardInfoService>().Init();
@ -101,7 +95,7 @@ namespace PluralKit.Bot
await _client.UpdateStatusAsync(new DiscordActivity("Restarting... (please wait)"), UserStatus.Idle);
}
private Task HandleEvent<T>(T evt) where T: DiscordEventArgs
private Task HandleEvent<T>(DiscordClient shard, T evt) where T: DiscordEventArgs
{
// We don't want to stall the event pipeline, so we'll "fork" inside here
var _ = HandleEventInner();
@ -118,7 +112,7 @@ namespace PluralKit.Bot
// Also, find a Sentry enricher for the event type (if one is present), and ask it to put some event data in the Sentry scope
var sentryEnricher = serviceScope.ResolveOptional<ISentryEnricher<T>>();
sentryEnricher?.Enrich(serviceScope.Resolve<Scope>(), evt);
sentryEnricher?.Enrich(serviceScope.Resolve<Scope>(), shard, evt);
// Find an event handler that can handle the type of event (<T>) we're given
var handler = serviceScope.Resolve<IEventHandler<T>>();
@ -129,7 +123,7 @@ namespace PluralKit.Bot
// the TryHandle call returns true if it's handled the event
// Usually it won't, so just pass it on to the main handler
if (queue == null || !await queue.TryHandle(evt))
await handler.Handle(evt);
await handler.Handle(shard, evt);
}
catch (Exception exc)
{
@ -209,23 +203,5 @@ namespace PluralKit.Bot
}
catch (WebSocketException) { }
}
public void FrameworkLog(object sender, DebugLogMessageEventArgs args)
{
// Bridge D#+ logging to Serilog
LogEventLevel level = LogEventLevel.Verbose;
if (args.Level == LogLevel.Critical)
level = LogEventLevel.Fatal;
else if (args.Level == LogLevel.Debug)
level = LogEventLevel.Debug;
else if (args.Level == LogLevel.Error)
level = LogEventLevel.Error;
else if (args.Level == LogLevel.Info)
level = LogEventLevel.Information;
else if (args.Level == LogLevel.Warning)
level = LogEventLevel.Warning;
_logger.Write(level, args.Exception, "D#+ {Source}: {Message}", args.Application, args.Message);
}
}
}

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using DSharpPlus;
using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
@ -7,7 +8,7 @@ namespace PluralKit.Bot
{
public interface IEventHandler<in T> where T: DiscordEventArgs
{
Task Handle(T evt);
Task Handle(DiscordClient shard, T evt);
DiscordChannel ErrorChannelFor(T evt) => null;
}

View File

@ -48,7 +48,7 @@ namespace PluralKit.Bot
// 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;
public async Task Handle(MessageCreateEventArgs evt)
public async Task Handle(DiscordClient shard, MessageCreateEventArgs evt)
{
if (evt.Author?.Id == _client.CurrentUser?.Id) return;
if (evt.Message.MessageType != MessageType.Default) return;
@ -71,7 +71,7 @@ namespace PluralKit.Bot
// Only do command/proxy handling if it's a user account
if (evt.Message.Author.IsBot || evt.Message.WebhookMessage || evt.Message.Author.IsSystem == true)
return;
if (await TryHandleCommand(evt, ctx))
if (await TryHandleCommand(shard, evt, ctx))
return;
await TryHandleProxy(evt, ctx);
}
@ -85,7 +85,7 @@ namespace PluralKit.Bot
return true;
}
private async ValueTask<bool> TryHandleCommand(MessageCreateEventArgs evt, MessageContext ctx)
private async ValueTask<bool> TryHandleCommand(DiscordClient shard, MessageCreateEventArgs evt, MessageContext ctx)
{
var content = evt.Message.Content;
if (content == null) return false;
@ -102,7 +102,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, evt.Client, evt.Message, cmdStart, system, ctx));
await _tree.ExecuteCommand(new Context(_services, shard, evt.Message, cmdStart, system, ctx));
}
catch (PKError)
{

View File

@ -1,6 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using DSharpPlus;
using DSharpPlus.EventArgs;
using PluralKit.Core;
@ -23,14 +24,14 @@ namespace PluralKit.Bot
_logger = logger.ForContext<MessageDeleted>();
}
public async Task Handle(MessageDeleteEventArgs evt)
public async Task Handle(DiscordClient shard, MessageDeleteEventArgs 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.
await _db.Execute(c => _repo.DeleteMessage(c, evt.Message.Id));
}
public async Task Handle(MessageBulkDeleteEventArgs evt)
public async Task Handle(DiscordClient shard, MessageBulkDeleteEventArgs evt)
{
// Same as above, but bulk
_logger.Information("Bulk deleting {Count} messages in channel {Channel}", evt.Messages.Count, evt.Channel.Id);

View File

@ -29,7 +29,7 @@ namespace PluralKit.Bot
_client = client;
}
public async Task Handle(MessageUpdateEventArgs evt)
public async Task Handle(DiscordClient shard, MessageUpdateEventArgs evt)
{
if (evt.Author?.Id == _client.CurrentUser?.Id) return;

View File

@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks;
using DSharpPlus;
@ -29,18 +28,18 @@ namespace PluralKit.Bot
_logger = logger.ForContext<ReactionAdded>();
}
public async Task Handle(MessageReactionAddEventArgs evt)
public async Task Handle(DiscordClient shard, MessageReactionAddEventArgs evt)
{
await TryHandleProxyMessageReactions(evt);
await TryHandleProxyMessageReactions(shard, evt);
}
private async ValueTask TryHandleProxyMessageReactions(MessageReactionAddEventArgs evt)
private async ValueTask TryHandleProxyMessageReactions(DiscordClient shard, MessageReactionAddEventArgs evt)
{
// 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;
if (!shard.TryGetCachedUser(evt.User.Id, out _)) return;
// check if it's a command message first
// since this can happen in DMs as well
@ -79,7 +78,7 @@ namespace PluralKit.Bot
await using var conn = await _db.Obtain();
var msg = await _repo.GetMessage(conn, evt.Message.Id);
if (msg != null)
await HandleQueryReaction(evt, msg);
await HandleQueryReaction(shard, evt, msg);
break;
}
@ -139,14 +138,14 @@ namespace PluralKit.Bot
// No need to delete database row here, it'll get deleted by the once-per-minute scheduled task.
}
private async ValueTask HandleQueryReaction(MessageReactionAddEventArgs evt, FullMessage msg)
private async ValueTask HandleQueryReaction(DiscordClient shard, MessageReactionAddEventArgs evt, FullMessage msg)
{
// Try to DM the user info about the message
var member = await evt.Guild.GetMember(evt.User.Id);
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));
await member.SendMessageAsync(embed: await _embeds.CreateMessageInfoEmbed(shard, msg));
}
catch (UnauthorizedException) { } // No permissions to DM, can't check for this :(

View File

@ -24,7 +24,8 @@ namespace PluralKit.Bot
Token = c.Resolve<BotConfig>().Token,
TokenType = TokenType.Bot,
MessageCacheSize = 0,
LargeThreshold = 50
LargeThreshold = 50,
LoggerFactory = c.Resolve<Microsoft.Extensions.Logging.ILoggerFactory>()
}).AsSelf();
builder.Register(c => new DiscordShardedClient(c.Resolve<DiscordConfiguration>())).AsSelf().SingleInstance();
builder.Register(c => new DiscordRestClient(c.Resolve<DiscordConfiguration>())).AsSelf().SingleInstance();

View File

@ -15,8 +15,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DSharpPlus" Version="4.0.0-nightly-00712" />
<PackageReference Include="DSharpPlus.Rest" Version="4.0.0-nightly-00712" />
<PackageReference Include="DSharpPlus" Version="4.0.0-rc1" />
<PackageReference Include="DSharpPlus.Rest" Version="4.0.0-rc1" />
<PackageReference Include="Humanizer.Core" Version="2.7.9" />
<PackageReference Include="Sentry" Version="2.1.4" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" />

View File

@ -42,7 +42,7 @@ namespace PluralKit.Bot
{
// We initialize this before any shards are actually created and connected
// This means the client won't know the shard count, so we attach a listener every time a shard gets connected
_client.SocketOpened += RefreshShardList;
_client.SocketOpened += (_, __) => RefreshShardList();
}
private void ReportShardStatus()
@ -66,8 +66,8 @@ namespace PluralKit.Bot
// Call our own SocketOpened listener manually (and then attach the listener properly)
await SocketOpened(shard);
shard.SocketOpened += () => SocketOpened(shard);
await SocketOpened(shard, null);
shard.SocketOpened += SocketOpened;
// Register listeners for new shards
_logger.Information("Attaching listeners to new shard #{Shard}", shard.ShardId);
@ -81,11 +81,11 @@ namespace PluralKit.Bot
}
}
private Task SocketOpened(DiscordClient e)
private Task SocketOpened(DiscordClient shard, SocketEventArgs _)
{
// We do nothing else here, since this kinda doesn't mean *much*? It's only really started once we get Ready/Resumed
// And it doesn't get fired first time around since we don't have time to add the event listener before it's fired'
_logger.Information("Shard #{Shard} opened socket", e.ShardId);
_logger.Information("Shard #{Shard} opened socket", shard.ShardId);
return Task.CompletedTask;
}
@ -99,45 +99,45 @@ namespace PluralKit.Bot
return info;
}
private Task Resumed(ReadyEventArgs e)
private Task Resumed(DiscordClient shard, ReadyEventArgs e)
{
_logger.Information("Shard #{Shard} resumed connection", e.Client.ShardId);
_logger.Information("Shard #{Shard} resumed connection", shard.ShardId);
var info = TryGetShard(e.Client);
var info = TryGetShard(shard);
// info.LastConnectionTime = SystemClock.Instance.GetCurrentInstant();
info.Connected = true;
ReportShardStatus();
return Task.CompletedTask;
}
private Task Ready(ReadyEventArgs e)
private Task Ready(DiscordClient shard, ReadyEventArgs e)
{
_logger.Information("Shard #{Shard} sent Ready event", e.Client.ShardId);
_logger.Information("Shard #{Shard} sent Ready event", shard.ShardId);
var info = TryGetShard(e.Client);
var info = TryGetShard(shard);
info.LastConnectionTime = SystemClock.Instance.GetCurrentInstant();
info.Connected = true;
ReportShardStatus();
return Task.CompletedTask;
}
private Task SocketClosed(SocketCloseEventArgs e)
private Task SocketClosed(DiscordClient shard, SocketCloseEventArgs e)
{
_logger.Warning("Shard #{Shard} disconnected ({CloseCode}: {CloseMessage})", e.Client.ShardId, e.CloseCode, e.CloseMessage);
_logger.Warning("Shard #{Shard} disconnected ({CloseCode}: {CloseMessage})", shard.ShardId, e.CloseCode, e.CloseMessage);
var info = TryGetShard(e.Client);
var info = TryGetShard(shard);
info.DisconnectionCount++;
info.Connected = false;
ReportShardStatus();
return Task.CompletedTask;
}
private Task Heartbeated(HeartbeatEventArgs e)
private Task Heartbeated(DiscordClient shard, HeartbeatEventArgs e)
{
var latency = Duration.FromMilliseconds(e.Ping);
_logger.Information("Shard #{Shard} received heartbeat (latency: {Latency} ms)", e.Client.ShardId, latency.Milliseconds);
_logger.Information("Shard #{Shard} received heartbeat (latency: {Latency} ms)", shard.ShardId, latency.Milliseconds);
var info = TryGetShard(e.Client);
var info = TryGetShard(shard);
info.LastHeartbeatTime = e.Timestamp.ToInstant();
info.Connected = true;
info.ShardLatency = latency;

View File

@ -48,7 +48,8 @@ namespace PluralKit.Bot
}
// Want shard last, just for visual reasons
props.Add(new LogEventProperty("Shard", new ScalarValue(dea.Client.ShardId)));
// TODO: D#+ update means we can't pull shard ID out of this, what do?
// props.Add(new LogEventProperty("Shard", new ScalarValue(dea.Client.ShardId)));
result = new StructureValue(props);
return true;

View File

@ -79,7 +79,7 @@ namespace PluralKit.Bot {
public static async Task<MessageReactionAddEventArgs> AwaitReaction(this Context ctx, DiscordMessage message, DiscordUser user = null, Func<MessageReactionAddEventArgs, bool> predicate = null, TimeSpan? timeout = null) {
var tcs = new TaskCompletionSource<MessageReactionAddEventArgs>();
Task Inner(MessageReactionAddEventArgs args) {
Task Inner(DiscordClient _, MessageReactionAddEventArgs args) {
if (message.Id != args.Message.Id) return Task.CompletedTask; // Ignore reactions for different messages
if (user != null && user.Id != args.User.Id) return Task.CompletedTask; // Ignore messages from other users if a user was defined
if (predicate != null && !predicate.Invoke(args)) return Task.CompletedTask; // Check predicate

View File

@ -10,7 +10,7 @@ namespace PluralKit.Bot
{
public interface ISentryEnricher<T> where T: DiscordEventArgs
{
void Enrich(Scope scope, T evt);
void Enrich(Scope scope, DiscordClient shard, T evt);
}
public class SentryEnricher:
@ -23,7 +23,7 @@ namespace PluralKit.Bot
// TODO: should this class take the Scope by dependency injection instead?
// Would allow us to create a centralized "chain of handlers" where this class could just be registered as an entry in
public void Enrich(Scope scope, MessageCreateEventArgs evt)
public void Enrich(Scope scope, DiscordClient shard, MessageCreateEventArgs evt)
{
scope.AddBreadcrumb(evt.Message.Content, "event.message", data: new Dictionary<string, string>
{
@ -32,7 +32,7 @@ namespace PluralKit.Bot
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()},
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
scope.SetTag("shard", shard.ShardId.ToString());
// Also report information about the bot's permissions in the channel
// We get a lot of permission errors so this'll be useful for determining problems
@ -40,7 +40,7 @@ namespace PluralKit.Bot
scope.AddBreadcrumb(perms.ToPermissionString(), "permissions");
}
public void Enrich(Scope scope, MessageDeleteEventArgs evt)
public void Enrich(Scope scope, DiscordClient shard, MessageDeleteEventArgs evt)
{
scope.AddBreadcrumb("", "event.messageDelete",
data: new Dictionary<string, string>()
@ -49,10 +49,10 @@ namespace PluralKit.Bot
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()},
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
scope.SetTag("shard", shard.ShardId.ToString());
}
public void Enrich(Scope scope, MessageUpdateEventArgs evt)
public void Enrich(Scope scope, DiscordClient shard, MessageUpdateEventArgs evt)
{
scope.AddBreadcrumb(evt.Message.Content ?? "<unknown>", "event.messageEdit",
data: new Dictionary<string, string>()
@ -61,10 +61,10 @@ namespace PluralKit.Bot
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()}
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
scope.SetTag("shard", shard.ShardId.ToString());
}
public void Enrich(Scope scope, MessageBulkDeleteEventArgs evt)
public void Enrich(Scope scope, DiscordClient shard, MessageBulkDeleteEventArgs evt)
{
scope.AddBreadcrumb("", "event.messageDelete",
data: new Dictionary<string, string>()
@ -73,10 +73,10 @@ namespace PluralKit.Bot
{"guild", evt.Channel.Id.ToString()},
{"messages", string.Join(",", evt.Messages.Select(m => m.Id))},
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
scope.SetTag("shard", shard.ShardId.ToString());
}
public void Enrich(Scope scope, MessageReactionAddEventArgs evt)
public void Enrich(Scope scope, DiscordClient shard, MessageReactionAddEventArgs evt)
{
scope.AddBreadcrumb("", "event.reaction",
data: new Dictionary<string, string>()
@ -87,7 +87,7 @@ namespace PluralKit.Bot
{"message", evt.Message.Id.ToString()},
{"reaction", evt.Emoji.Name}
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
scope.SetTag("shard", shard.ShardId.ToString());
}
}
}

View File

@ -99,6 +99,10 @@ namespace PluralKit.Core
// AutoActivate ensures logging is enabled as early as possible in the API startup flow
// since we set the Log.Logger global >.>
.AutoActivate();
builder.Register(c => new Microsoft.Extensions.Logging.LoggerFactory().AddSerilog(c.Resolve<ILogger>()))
.As<Microsoft.Extensions.Logging.ILoggerFactory>()
.SingleInstance();
}
private ILogger InitLogger(CoreConfig config)
@ -111,9 +115,10 @@ namespace PluralKit.Core
.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)
.Enrich.WithProperty("Component", _component)
.MinimumLevel.Is(config.ConsoleLogLevel)
// Don't want App.Metrics spam
// Don't want App.Metrics/D#+ spam
.MinimumLevel.Override("App.Metrics", LogEventLevel.Information)
.MinimumLevel.Override("DSharpPlus", LogEventLevel.Debug)
// Actual formatting for these is handled in ScalarFormatting
.Destructure.AsScalar<SystemId>()

View File

@ -17,18 +17,20 @@
<PackageReference Include="Dapper" Version="1.60.6" />
<PackageReference Include="Dapper.Contrib" Version="1.60.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging " Version="3.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NodaTime" Version="3.0.0-beta01" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.2.0" />
<PackageReference Include="Npgsql" Version="4.1.4" />
<PackageReference Include="Npgsql.NodaTime" Version="4.1.4" />
<PackageReference Include="Serilog" Version="2.9.1-dev-01154" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
<PackageReference Include="Serilog.NodaTime" Version="3.0.0-beta01" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.1-dev-00071" />