/// <summary> /// Constructs a new <see cref="CommandListeningBehavior"/>, with the given dependencies. /// </summary> public CommandListeningBehavior( IServiceProvider serviceProvider, CommandService commandService, CommandErrorHandler commandErrorHandler, IDiscordClient discordClient, IAuthorizationService authorizationService) { ServiceProvider = serviceProvider; CommandService = commandService; CommandErrorHandler = commandErrorHandler; DiscordClient = discordClient; AuthorizationService = authorizationService; }
/// <summary> /// Constructs a new <see cref="CommandListeningBehavior"/>, with the given dependencies. /// </summary> public CommandListeningBehavior( ICommandPrefixParser commandPrefixParser, IServiceProvider serviceProvider, CommandService commandService, CommandErrorHandler commandErrorHandler, IDiscordClient discordClient, IAuthorizationService authorizationService, IDogStatsd stats = null) { _commandPrefixParser = commandPrefixParser; ServiceProvider = serviceProvider; CommandService = commandService; CommandErrorHandler = commandErrorHandler; DiscordClient = discordClient; AuthorizationService = authorizationService; _stats = stats; }
public ModixBot( DiscordSocketClient discordClient, DiscordRestClient restClient, IOptions <ModixConfig> modixConfig, CommandService commandService, DiscordSerilogAdapter serilogAdapter, IApplicationLifetime applicationLifetime, IServiceProvider serviceProvider, ILogger <ModixBot> logger, CommandErrorHandler commandErrorHandler) { _client = discordClient ?? throw new ArgumentNullException(nameof(discordClient)); _restClient = restClient ?? throw new ArgumentNullException(nameof(restClient)); _config = modixConfig?.Value ?? throw new ArgumentNullException(nameof(modixConfig)); _commands = commandService ?? throw new ArgumentNullException(nameof(commandService)); _provider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); _serilogAdapter = serilogAdapter ?? throw new ArgumentNullException(nameof(serilogAdapter)); _applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime)); Log = logger ?? throw new ArgumentNullException(nameof(logger)); _commandErrorHandler = commandErrorHandler; }
/// <inheritdoc /> public async Task HandleNotificationAsync(MessageReceivedNotification notification, CancellationToken cancellationToken = default) { var stopwatch = new Stopwatch(); stopwatch.Start(); if (!(notification.Message is IUserMessage userMessage) || (userMessage.Author is null)) { return; } if (!(userMessage.Author is IGuildUser author) || (author.Guild is null) || author.IsBot || author.IsWebhook) { return; } var argPos = 0; if (!userMessage.HasCharPrefix('!', ref argPos) && !userMessage.HasMentionPrefix(DiscordClient.CurrentUser, ref argPos)) { return; } if (userMessage.Content.Length <= 1) { return; } var commandContext = new CommandContext(DiscordClient, userMessage); await AuthorizationService.OnAuthenticatedAsync(author); IResult commandResult = null; var commandTimer = Stopwatch.StartNew(); try { commandResult = await CommandService.ExecuteAsync(commandContext, argPos, ServiceProvider); } finally { commandTimer.Stop(); var duration = commandTimer.ElapsedMilliseconds; if (!(_stats is null) && (commandResult.IsSuccess || !string.Equals(commandResult.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase))) { var commandInfo = CommandService.Search(commandContext, argPos).Commands.FirstOrDefault(); var name = commandInfo.Command?.Name.ToLowerInvariant(); _stats?.Timer("command_duration_ms", duration, tags: new[] { $"guild:{commandContext.Guild.Name}", $"success:{commandResult.IsSuccess}", $"command:{name}" }); } } if (!commandResult.IsSuccess) { var error = $"{commandResult.Error}: {commandResult.ErrorReason}"; if (string.Equals(commandResult.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase)) { Log.Error(error); } else { Log.Warning(error); } if (commandResult.Error == CommandError.Exception) { await commandContext.Channel.SendMessageAsync($"Error: {FormatUtilities.SanitizeEveryone(commandResult.ErrorReason)}"); } else { await CommandErrorHandler.AssociateError(userMessage, error); } } stopwatch.Stop(); Log.Information($"Command took {stopwatch.ElapsedMilliseconds}ms to process: {commandContext.Message}"); }
/// <inheritdoc /> public async Task HandleNotificationAsync(MessageReceivedNotification notification, CancellationToken cancellationToken = default) { var stopwatch = new Stopwatch(); stopwatch.Start(); if (!(notification.Message is IUserMessage userMessage) || (userMessage.Author is null)) { return; } if (!(userMessage.Author is IGuildUser author) || (author.Guild is null) || author.IsBot || author.IsWebhook) { return; } var argPos = 0; if (!userMessage.HasCharPrefix('!', ref argPos) && !userMessage.HasMentionPrefix(DiscordClient.CurrentUser, ref argPos)) { return; } if (userMessage.Content.Length <= 1) { return; } var commandContext = new CommandContext(DiscordClient, userMessage); await AuthorizationService.OnAuthenticatedAsync(author); var commandResult = await CommandService.ExecuteAsync(commandContext, argPos, ServiceProvider); if (!commandResult.IsSuccess) { var error = $"{commandResult.Error}: {commandResult.ErrorReason}"; if (string.Equals(commandResult.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase)) { Log.Error(error); } else { Log.Warning(error); } if (commandResult.Error == CommandError.Exception) { await commandContext.Channel.SendMessageAsync($"Error: {FormatUtilities.SanitizeEveryone(commandResult.ErrorReason)}"); } else { await CommandErrorHandler.AssociateError(userMessage, error); } } stopwatch.Stop(); Log.Information($"Command took {stopwatch.ElapsedMilliseconds}ms to process: {commandContext.Message}"); }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { Log.LogInformation("Starting bot background service."); IServiceScope scope = null; try { // Create a new scope for the session. scope = _provider.CreateScope(); Log.LogTrace("Registering listeners for Discord client events."); _client.Disconnected += OnDisconnect; _client.MessageReceived += HandleCommand; _client.Log += _serilogAdapter.HandleLog; _commands.Log += _serilogAdapter.HandleLog; // Register with the cancellation token so we can stop listening to client events if the service is // shutting down or being disposed. stoppingToken.Register(OnStopping); Log.LogInformation("Running database migrations."); scope.ServiceProvider.GetRequiredService <ModixContext>() .Database.Migrate(); Log.LogInformation("Starting behaviors."); await scope.ServiceProvider.GetRequiredService <IBehaviourConfigurationService>() .LoadBehaviourConfiguration(); foreach (var behavior in scope.ServiceProvider.GetServices <IBehavior>()) { await behavior.StartAsync(); stoppingToken.Register(() => behavior.StopAsync().GetAwaiter().GetResult()); } _errorHandler = scope.ServiceProvider.GetRequiredService <CommandErrorHandler>(); // The only thing that could go wrong at this point is the client failing to login and start. Promote // our local service scope to a field so that it's available to the HandleCommand method once events // start firing after we've connected. _scope = scope; Log.LogInformation("Loading command modules..."); await _commands.AddModulesAsync(typeof(ModixBot).Assembly, _scope.ServiceProvider); Log.LogInformation("{Modules} modules loaded, containing {Commands} commands", _commands.Modules.Count(), _commands.Modules.SelectMany(d => d.Commands).Count()); Log.LogInformation("Logging into Discord and starting the client."); await StartClient(stoppingToken); Log.LogInformation("Discord client started successfully."); await Task.Delay(-1); } catch (Exception ex) { Log.LogError(ex, "An error occurred while attempting to start the background service."); try { OnStopping(); Log.LogInformation("Logging out of Discord."); await _client.LogoutAsync(); } finally { scope?.Dispose(); _scope = null; } throw; } void OnStopping() { Log.LogInformation("Stopping background service."); _client.Disconnected -= OnDisconnect; _client.MessageReceived -= HandleCommand; _client.Log -= _serilogAdapter.HandleLog; _commands.Log -= _serilogAdapter.HandleLog; } }