2019-04-19 18:48:37 +00:00
|
|
|
|
using System;
|
2019-07-11 20:34:38 +00:00
|
|
|
|
using System.Collections.Generic;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
2019-04-25 16:50:07 +00:00
|
|
|
|
using System.Threading;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2019-07-16 19:59:06 +00:00
|
|
|
|
using App.Metrics;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
using Dapper;
|
|
|
|
|
using Discord;
|
|
|
|
|
using Discord.Commands;
|
|
|
|
|
using Discord.WebSocket;
|
2019-05-08 18:08:56 +00:00
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
2019-07-15 19:02:50 +00:00
|
|
|
|
using Sentry;
|
2019-07-18 15:13:42 +00:00
|
|
|
|
using Serilog;
|
2019-07-19 00:29:08 +00:00
|
|
|
|
using Serilog.Events;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
|
2019-04-21 13:33:22 +00:00
|
|
|
|
namespace PluralKit.Bot
|
2019-04-19 18:48:37 +00:00
|
|
|
|
{
|
|
|
|
|
class Initialize
|
|
|
|
|
{
|
2019-05-08 18:08:56 +00:00
|
|
|
|
private IConfiguration _config;
|
|
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
|
static void Main(string[] args) => new Initialize { _config = InitUtils.BuildConfiguration(args).Build()}.MainAsync().GetAwaiter().GetResult();
|
2019-04-19 18:48:37 +00:00
|
|
|
|
|
|
|
|
|
private async Task MainAsync()
|
|
|
|
|
{
|
2019-04-20 20:25:03 +00:00
|
|
|
|
Console.WriteLine("Starting PluralKit...");
|
2019-05-19 20:03:28 +00:00
|
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
|
InitUtils.Init();
|
2019-07-18 15:13:42 +00:00
|
|
|
|
|
|
|
|
|
// Set up a CancellationToken and a SIGINT hook to properly dispose of things when the app is closed
|
|
|
|
|
// The Task.Delay line will throw/exit (forgot which) and the stack and using statements will properly unwind
|
|
|
|
|
var token = new CancellationTokenSource();
|
|
|
|
|
Console.CancelKeyPress += delegate(object e, ConsoleCancelEventArgs args)
|
|
|
|
|
{
|
|
|
|
|
args.Cancel = true;
|
|
|
|
|
token.Cancel();
|
|
|
|
|
};
|
2019-05-13 20:44:49 +00:00
|
|
|
|
|
2019-04-19 18:48:37 +00:00
|
|
|
|
using (var services = BuildServiceProvider())
|
|
|
|
|
{
|
2019-07-19 00:29:08 +00:00
|
|
|
|
var logger = services.GetRequiredService<ILogger>().ForContext<Initialize>();
|
2019-07-15 19:02:50 +00:00
|
|
|
|
var coreConfig = services.GetRequiredService<CoreConfig>();
|
|
|
|
|
var botConfig = services.GetRequiredService<BotConfig>();
|
2019-04-19 18:48:37 +00:00
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
using (Sentry.SentrySdk.Init(coreConfig.SentryUrl))
|
2019-07-15 19:02:50 +00:00
|
|
|
|
{
|
2019-04-19 18:48:37 +00:00
|
|
|
|
|
2019-07-19 00:29:08 +00:00
|
|
|
|
logger.Information("Connecting to database");
|
2019-07-15 19:02:50 +00:00
|
|
|
|
using (var conn = await services.GetRequiredService<DbConnectionFactory>().Obtain())
|
|
|
|
|
await Schema.CreateTables(conn);
|
|
|
|
|
|
2019-07-19 00:29:08 +00:00
|
|
|
|
logger.Information("Connecting to Discord");
|
2019-07-15 19:02:50 +00:00
|
|
|
|
var client = services.GetRequiredService<IDiscordClient>() as DiscordShardedClient;
|
|
|
|
|
await client.LoginAsync(TokenType.Bot, botConfig.Token);
|
|
|
|
|
|
2019-07-19 00:29:08 +00:00
|
|
|
|
logger.Information("Initializing bot");
|
2019-07-15 19:02:50 +00:00
|
|
|
|
await services.GetRequiredService<Bot>().Init();
|
2019-07-19 00:29:08 +00:00
|
|
|
|
|
2019-07-18 15:13:42 +00:00
|
|
|
|
|
|
|
|
|
await client.StartAsync();
|
2019-07-15 19:02:50 +00:00
|
|
|
|
|
2019-07-18 15:13:42 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Task.Delay(-1, token.Token);
|
|
|
|
|
}
|
|
|
|
|
catch (TaskCanceledException) { } // We'll just exit normally
|
2019-07-19 00:29:08 +00:00
|
|
|
|
logger.Information("Shutting down");
|
2019-07-15 19:02:50 +00:00
|
|
|
|
}
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ServiceProvider BuildServiceProvider() => new ServiceCollection()
|
2019-07-18 15:13:42 +00:00
|
|
|
|
.AddTransient(_ => _config.GetSection("PluralKit").Get<CoreConfig>() ?? new CoreConfig())
|
|
|
|
|
.AddTransient(_ => _config.GetSection("PluralKit").GetSection("Bot").Get<BotConfig>() ?? new BotConfig())
|
2019-04-19 18:48:37 +00:00
|
|
|
|
|
2019-08-11 20:56:20 +00:00
|
|
|
|
.AddSingleton<DbConnectionCountHolder>()
|
|
|
|
|
.AddTransient<DbConnectionFactory>()
|
2019-07-18 15:13:42 +00:00
|
|
|
|
|
2019-07-20 21:10:26 +00:00
|
|
|
|
.AddSingleton<IDiscordClient, DiscordShardedClient>(_ => new DiscordShardedClient(new DiscordSocketConfig
|
|
|
|
|
{
|
2019-07-21 15:16:04 +00:00
|
|
|
|
MessageCacheSize = 0,
|
|
|
|
|
ExclusiveBulkDelete = true
|
2019-07-20 21:10:26 +00:00
|
|
|
|
}))
|
2019-07-18 15:13:42 +00:00
|
|
|
|
.AddSingleton<Bot>()
|
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
.AddSingleton(_ => new CommandService(new CommandServiceConfig
|
2019-07-18 15:13:42 +00:00
|
|
|
|
{
|
|
|
|
|
CaseSensitiveCommands = false,
|
|
|
|
|
QuotationMarkAliasMap = new Dictionary<char, char>
|
2019-07-16 21:34:22 +00:00
|
|
|
|
{
|
2019-07-18 15:13:42 +00:00
|
|
|
|
{'"', '"'},
|
|
|
|
|
{'\'', '\''},
|
|
|
|
|
{'‘', '’'},
|
|
|
|
|
{'“', '”'},
|
|
|
|
|
{'„', '‟'},
|
|
|
|
|
},
|
2019-08-09 10:47:46 +00:00
|
|
|
|
// We're already asyncing stuff by forking off at the client event handlers
|
|
|
|
|
// So adding an additional layer of forking is pointless
|
|
|
|
|
// and leads to the service scope being disposed of prematurely
|
|
|
|
|
DefaultRunMode = RunMode.Sync
|
2019-07-18 15:13:42 +00:00
|
|
|
|
}))
|
|
|
|
|
.AddTransient<EmbedService>()
|
|
|
|
|
.AddTransient<ProxyService>()
|
|
|
|
|
.AddTransient<LogChannelService>()
|
|
|
|
|
.AddTransient<DataFileService>()
|
|
|
|
|
|
|
|
|
|
.AddSingleton<WebhookCacheService>()
|
|
|
|
|
|
|
|
|
|
.AddTransient<SystemStore>()
|
|
|
|
|
.AddTransient<MemberStore>()
|
|
|
|
|
.AddTransient<MessageStore>()
|
|
|
|
|
.AddTransient<SwitchStore>()
|
|
|
|
|
|
|
|
|
|
.AddSingleton<IMetrics>(svc =>
|
|
|
|
|
{
|
|
|
|
|
var cfg = svc.GetRequiredService<CoreConfig>();
|
|
|
|
|
var builder = AppMetrics.CreateDefaultBuilder();
|
|
|
|
|
if (cfg.InfluxUrl != null && cfg.InfluxDb != null)
|
|
|
|
|
builder.Report.ToInfluxDb(cfg.InfluxUrl, cfg.InfluxDb);
|
|
|
|
|
return builder.Build();
|
|
|
|
|
})
|
|
|
|
|
.AddSingleton<PeriodicStatCollector>()
|
2019-08-09 10:47:46 +00:00
|
|
|
|
|
|
|
|
|
.AddScoped(_ => new Sentry.Scope(null))
|
|
|
|
|
.AddTransient<PKEventHandler>()
|
2019-07-18 15:13:42 +00:00
|
|
|
|
|
2019-08-11 22:07:29 +00:00
|
|
|
|
.AddScoped<EventIdProvider>()
|
|
|
|
|
.AddSingleton(svc => new LoggerProvider(svc.GetRequiredService<CoreConfig>(), "bot"))
|
|
|
|
|
.AddScoped(svc => svc.GetRequiredService<LoggerProvider>().RootLogger.ForContext("EventId", svc.GetRequiredService<EventIdProvider>().EventId))
|
|
|
|
|
|
2019-07-18 15:26:06 +00:00
|
|
|
|
.BuildServiceProvider();
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
|
|
|
|
class Bot
|
|
|
|
|
{
|
|
|
|
|
private IServiceProvider _services;
|
2019-07-15 15:16:14 +00:00
|
|
|
|
private DiscordShardedClient _client;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
private CommandService _commands;
|
|
|
|
|
private ProxyService _proxy;
|
2019-04-25 16:50:07 +00:00
|
|
|
|
private Timer _updateTimer;
|
2019-07-16 19:59:06 +00:00
|
|
|
|
private IMetrics _metrics;
|
2019-07-16 21:34:22 +00:00
|
|
|
|
private PeriodicStatCollector _collector;
|
2019-07-18 15:13:42 +00:00
|
|
|
|
private ILogger _logger;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
|
2019-07-18 15:13:42 +00:00
|
|
|
|
public Bot(IServiceProvider services, IDiscordClient client, CommandService commands, ProxyService proxy, IMetrics metrics, PeriodicStatCollector collector, ILogger logger)
|
2019-04-19 18:48:37 +00:00
|
|
|
|
{
|
2019-08-09 10:47:46 +00:00
|
|
|
|
_services = services;
|
|
|
|
|
_client = client as DiscordShardedClient;
|
|
|
|
|
_commands = commands;
|
|
|
|
|
_proxy = proxy;
|
2019-07-16 19:59:06 +00:00
|
|
|
|
_metrics = metrics;
|
2019-07-16 21:34:22 +00:00
|
|
|
|
_collector = collector;
|
2019-07-18 15:13:42 +00:00
|
|
|
|
_logger = logger.ForContext<Bot>();
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task Init()
|
|
|
|
|
{
|
|
|
|
|
_commands.AddTypeReader<PKSystem>(new PKSystemTypeReader());
|
|
|
|
|
_commands.AddTypeReader<PKMember>(new PKMemberTypeReader());
|
|
|
|
|
_commands.CommandExecuted += CommandExecuted;
|
|
|
|
|
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
|
|
|
|
|
|
2019-07-15 15:16:14 +00:00
|
|
|
|
_client.ShardReady += ShardReady;
|
2019-07-19 00:29:08 +00:00
|
|
|
|
_client.Log += FrameworkLog;
|
2019-08-09 10:47:46 +00:00
|
|
|
|
|
|
|
|
|
_client.MessageReceived += (msg) => HandleEvent(s => s.AddMessageBreadcrumb(msg), eh => eh.HandleMessage(msg));
|
|
|
|
|
_client.ReactionAdded += (msg, channel, reaction) => HandleEvent(s => s.AddReactionAddedBreadcrumb(msg, channel, reaction), eh => eh.HandleReactionAdded(msg, channel, reaction));
|
|
|
|
|
_client.MessageDeleted += (msg, channel) => HandleEvent(s => s.AddMessageDeleteBreadcrumb(msg, channel), eh => eh.HandleMessageDeleted(msg, channel));
|
|
|
|
|
_client.MessagesBulkDeleted += (msgs, channel) => HandleEvent(s => s.AddMessageBulkDeleteBreadcrumb(msgs, channel), eh => eh.HandleMessagesBulkDelete(msgs, channel));
|
2019-07-19 00:29:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Task FrameworkLog(LogMessage msg)
|
|
|
|
|
{
|
|
|
|
|
// Bridge D.NET logging to Serilog
|
|
|
|
|
LogEventLevel level = LogEventLevel.Verbose;
|
2019-08-09 10:47:46 +00:00
|
|
|
|
if (msg.Severity == LogSeverity.Critical)
|
|
|
|
|
level = LogEventLevel.Fatal;
|
|
|
|
|
else if (msg.Severity == LogSeverity.Debug)
|
|
|
|
|
level = LogEventLevel.Debug;
|
|
|
|
|
else if (msg.Severity == LogSeverity.Error)
|
|
|
|
|
level = LogEventLevel.Error;
|
|
|
|
|
else if (msg.Severity == LogSeverity.Info)
|
|
|
|
|
level = LogEventLevel.Information;
|
|
|
|
|
else if (msg.Severity == LogSeverity.Verbose)
|
|
|
|
|
level = LogEventLevel.Verbose;
|
|
|
|
|
else if (msg.Severity == LogSeverity.Warning)
|
|
|
|
|
level = LogEventLevel.Warning;
|
2019-07-19 00:29:08 +00:00
|
|
|
|
|
|
|
|
|
_logger.Write(level, msg.Exception, "Discord.Net {Source}: {Message}", msg.Source, msg.Message);
|
|
|
|
|
return Task.CompletedTask;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 21:34:22 +00:00
|
|
|
|
// Method called every 60 seconds
|
2019-04-25 16:50:07 +00:00
|
|
|
|
private async Task UpdatePeriodic()
|
2019-04-20 20:25:03 +00:00
|
|
|
|
{
|
2019-07-16 21:34:22 +00:00
|
|
|
|
// Change bot status
|
2019-04-25 16:50:07 +00:00
|
|
|
|
await _client.SetGameAsync($"pk;help | in {_client.Guilds.Count} servers");
|
2019-07-16 21:34:22 +00:00
|
|
|
|
|
|
|
|
|
await _collector.CollectStats();
|
2019-07-18 15:13:42 +00:00
|
|
|
|
|
|
|
|
|
_logger.Information("Submitted metrics to backend");
|
2019-07-16 21:34:22 +00:00
|
|
|
|
await Task.WhenAll(((IMetricsRoot) _metrics).ReportRunner.RunAllAsync());
|
2019-04-25 16:50:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-21 14:43:28 +00:00
|
|
|
|
private Task ShardReady(DiscordSocketClient shardClient)
|
2019-04-25 16:50:07 +00:00
|
|
|
|
{
|
2019-07-18 15:13:42 +00:00
|
|
|
|
_logger.Information("Shard {Shard} connected", shardClient.ShardId);
|
2019-07-15 15:16:14 +00:00
|
|
|
|
Console.WriteLine($"Shard #{shardClient.ShardId} connected to {shardClient.Guilds.Sum(g => g.Channels.Count)} channels in {shardClient.Guilds.Count} guilds.");
|
2019-07-16 21:34:22 +00:00
|
|
|
|
|
|
|
|
|
if (shardClient.ShardId == 0)
|
|
|
|
|
{
|
2019-08-09 10:47:46 +00:00
|
|
|
|
_updateTimer = new Timer((_) => {
|
|
|
|
|
HandleEvent(s => s.AddPeriodicBreadcrumb(), __ => UpdatePeriodic());
|
|
|
|
|
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
|
2019-07-16 21:34:22 +00:00
|
|
|
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
$"PluralKit started as {_client.CurrentUser.Username}#{_client.CurrentUser.Discriminator} ({_client.CurrentUser.Id}).");
|
|
|
|
|
}
|
2019-07-21 14:43:28 +00:00
|
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
2019-04-20 20:25:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-19 18:48:37 +00:00
|
|
|
|
private async Task CommandExecuted(Optional<CommandInfo> cmd, ICommandContext ctx, IResult _result)
|
|
|
|
|
{
|
2019-07-16 19:59:06 +00:00
|
|
|
|
_metrics.Measure.Meter.Mark(BotMetrics.CommandsRun);
|
|
|
|
|
|
2019-04-27 14:30:34 +00:00
|
|
|
|
// TODO: refactor this entire block, it's fugly.
|
2019-04-19 18:48:37 +00:00
|
|
|
|
if (!_result.IsSuccess) {
|
2019-04-27 14:30:34 +00:00
|
|
|
|
if (_result.Error == CommandError.Unsuccessful || _result.Error == CommandError.Exception) {
|
|
|
|
|
// If this is a PKError (ie. thrown deliberately), show user facing message
|
|
|
|
|
// If not, log as error
|
|
|
|
|
var exception = (_result as ExecuteResult?)?.Exception;
|
|
|
|
|
if (exception is PKError) {
|
|
|
|
|
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {exception.Message}");
|
|
|
|
|
} else if (exception is TimeoutException) {
|
|
|
|
|
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} Operation timed out. Try being faster next time :)");
|
2019-05-21 21:40:26 +00:00
|
|
|
|
} else if (_result is PreconditionResult)
|
|
|
|
|
{
|
|
|
|
|
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}");
|
2019-04-27 14:30:34 +00:00
|
|
|
|
} else {
|
2019-08-09 10:47:46 +00:00
|
|
|
|
HandleRuntimeError((_result as ExecuteResult?)?.Exception, ((PKCommandContext) ctx).ServiceProvider.GetRequiredService<Scope>());
|
2019-04-27 14:30:34 +00:00
|
|
|
|
}
|
|
|
|
|
} else if ((_result.Error == CommandError.BadArgCount || _result.Error == CommandError.MultipleMatches) && cmd.IsSpecified) {
|
|
|
|
|
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}\n**Usage: **pk;{cmd.Value.Remarks}");
|
2019-06-13 14:53:04 +00:00
|
|
|
|
} else if (_result.Error == CommandError.UnknownCommand || _result.Error == CommandError.UnmetPrecondition || _result.Error == CommandError.ObjectNotFound) {
|
2019-04-27 14:30:34 +00:00
|
|
|
|
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}");
|
2019-04-26 15:14:20 +00:00
|
|
|
|
}
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
private Task HandleEvent(Action<Scope> breadcrumbFactory, Func<PKEventHandler, Task> handler)
|
2019-04-19 18:48:37 +00:00
|
|
|
|
{
|
2019-08-09 10:47:46 +00:00
|
|
|
|
// Inner function so we can await the handler without stalling the entire pipeline
|
|
|
|
|
async Task Inner()
|
2019-04-29 15:42:09 +00:00
|
|
|
|
{
|
2019-08-11 23:15:55 +00:00
|
|
|
|
// "Fork" this task off by ~~yeeting~~ yielding it at the back of the task queue
|
|
|
|
|
// This prevents any synchronous nonsense from also stalling the pipeline before the first await point
|
|
|
|
|
await Task.Yield();
|
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
// Create a DI scope for this event
|
|
|
|
|
// and log the breadcrumb to the newly created (in-svc-scope) Sentry scope
|
|
|
|
|
using (var scope = _services.CreateScope())
|
2019-07-15 19:02:50 +00:00
|
|
|
|
{
|
2019-08-09 10:47:46 +00:00
|
|
|
|
var sentryScope = scope.ServiceProvider.GetRequiredService<Scope>();
|
|
|
|
|
breadcrumbFactory(sentryScope);
|
2019-07-18 15:13:42 +00:00
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await handler(scope.ServiceProvider.GetRequiredService<PKEventHandler>());
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
HandleRuntimeError(e, sentryScope);
|
|
|
|
|
}
|
2019-07-15 19:02:50 +00:00
|
|
|
|
}
|
2019-08-09 10:47:46 +00:00
|
|
|
|
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
2019-08-09 10:47:46 +00:00
|
|
|
|
|
|
|
|
|
#pragma warning disable 4014
|
|
|
|
|
Inner();
|
|
|
|
|
#pragma warning restore 4014
|
|
|
|
|
return Task.CompletedTask;
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
2019-04-20 20:36:54 +00:00
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
private void HandleRuntimeError(Exception e, Scope scope = null)
|
2019-04-20 20:36:54 +00:00
|
|
|
|
{
|
2019-07-18 15:13:42 +00:00
|
|
|
|
_logger.Error(e, "Exception in bot event handler");
|
2019-08-09 10:47:46 +00:00
|
|
|
|
|
|
|
|
|
var evt = new SentryEvent(e);
|
|
|
|
|
SentrySdk.CaptureEvent(evt, scope);
|
|
|
|
|
|
2019-04-20 20:36:54 +00:00
|
|
|
|
Console.Error.WriteLine(e);
|
|
|
|
|
}
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|
2019-08-09 10:47:46 +00:00
|
|
|
|
|
|
|
|
|
class PKEventHandler {
|
|
|
|
|
private CommandService _commands;
|
|
|
|
|
private ProxyService _proxy;
|
|
|
|
|
private ILogger _logger;
|
|
|
|
|
private IMetrics _metrics;
|
|
|
|
|
private DiscordShardedClient _client;
|
|
|
|
|
private DbConnectionFactory _connectionFactory;
|
|
|
|
|
private IServiceProvider _services;
|
|
|
|
|
|
|
|
|
|
public PKEventHandler(CommandService commands, ProxyService proxy, ILogger logger, IMetrics metrics, IDiscordClient client, DbConnectionFactory connectionFactory, IServiceProvider services)
|
|
|
|
|
{
|
|
|
|
|
_commands = commands;
|
|
|
|
|
_proxy = proxy;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_metrics = metrics;
|
|
|
|
|
_client = (DiscordShardedClient) client;
|
|
|
|
|
_connectionFactory = connectionFactory;
|
|
|
|
|
_services = services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task HandleMessage(SocketMessage msg)
|
|
|
|
|
{
|
2019-08-11 22:57:23 +00:00
|
|
|
|
RegisterMessageMetrics(msg);
|
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
// _client.CurrentUser will be null if we've connected *some* shards but not shard #0 yet
|
|
|
|
|
// This will cause an error in WebhookCacheServices so we just workaround and don't process any messages
|
|
|
|
|
// until we properly connect. TODO: can we do this without chucking away a bunch of messages?
|
|
|
|
|
if (_client.CurrentUser == null) return;
|
|
|
|
|
|
|
|
|
|
// Ignore system messages (member joined, message pinned, etc)
|
|
|
|
|
var arg = msg as SocketUserMessage;
|
|
|
|
|
if (arg == null) return;
|
|
|
|
|
|
|
|
|
|
// Ignore bot messages
|
|
|
|
|
if (arg.Author.IsBot || arg.Author.IsWebhook) return;
|
|
|
|
|
|
|
|
|
|
int argPos = 0;
|
|
|
|
|
// Check if message starts with the command prefix
|
|
|
|
|
if (arg.HasStringPrefix("pk;", ref argPos, StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
arg.HasStringPrefix("pk!", ref argPos, StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
arg.HasMentionPrefix(_client.CurrentUser, ref argPos))
|
|
|
|
|
{
|
2019-08-11 22:07:29 +00:00
|
|
|
|
_logger.Debug("Parsing command {Command} from message {Channel}-{Message}", msg.Content, msg.Channel.Id, msg.Id);
|
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
// Essentially move the argPos pointer by however much whitespace is at the start of the post-argPos string
|
|
|
|
|
var trimStartLengthDiff = arg.Content.Substring(argPos).Length -
|
|
|
|
|
arg.Content.Substring(argPos).TrimStart().Length;
|
|
|
|
|
argPos += trimStartLengthDiff;
|
|
|
|
|
|
|
|
|
|
// If it does, fetch the sender's system (because most commands need that) into the context,
|
|
|
|
|
// and start command execution
|
|
|
|
|
// Note system may be null if user has no system, hence `OrDefault`
|
|
|
|
|
PKSystem system;
|
|
|
|
|
using (var conn = await _connectionFactory.Obtain())
|
|
|
|
|
system = await conn.QueryFirstOrDefaultAsync<PKSystem>(
|
|
|
|
|
"select systems.* from systems, accounts where accounts.uid = @Id and systems.id = accounts.system",
|
|
|
|
|
new {Id = arg.Author.Id});
|
2019-08-11 22:57:23 +00:00
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
await _commands.ExecuteAsync(new PKCommandContext(_client, arg, system, _services), argPos,
|
|
|
|
|
_services);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// If not, try proxying anyway
|
|
|
|
|
await _proxy.HandleMessageAsync(arg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-11 22:57:23 +00:00
|
|
|
|
private void RegisterMessageMetrics(SocketMessage msg)
|
|
|
|
|
{
|
|
|
|
|
_metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-09 10:47:46 +00:00
|
|
|
|
public Task HandleReactionAdded(Cacheable<IUserMessage, ulong> message, ISocketMessageChannel channel,
|
|
|
|
|
SocketReaction reaction) => _proxy.HandleReactionAddedAsync(message, channel, reaction);
|
|
|
|
|
|
|
|
|
|
public Task HandleMessageDeleted(Cacheable<IMessage, ulong> message, ISocketMessageChannel channel) =>
|
|
|
|
|
_proxy.HandleMessageDeletedAsync(message, channel);
|
|
|
|
|
|
|
|
|
|
public Task HandleMessagesBulkDelete(IReadOnlyCollection<Cacheable<IMessage, ulong>> messages,
|
|
|
|
|
IMessageChannel channel) => _proxy.HandleMessageBulkDeleteAsync(messages, channel);
|
|
|
|
|
}
|
2019-04-19 18:48:37 +00:00
|
|
|
|
}
|