/// <summary> /// Handles a user message /// * will keep track of user messages, and rate limit them when appropiate /// * will keep track of user cooldowns, and only let through commands if not on cooldown /// </summary> private async Task MessageReceivedAsync(SocketMessage messageParam) { try { if (!(messageParam is SocketUserMessage message) || message.Author.IsBot || message.Author.IsWebhook || message.Channel is SocketDMChannel) { return; } var author = message.Author; TrackRateLimit(author.Id, message); if (IsRateLimited(author.Id)) { await message.DeleteAsync(new RequestOptions { AuditLogReason = $"User is rate limited until {RateLimitedUsers[author.Id].ToString()}" }); if (NeedsRateLimit(author.Id) && author is SocketGuildUser sgu) { await sgu.KickAsync($"User repeatedly spammed while rate limited"); } return; } bool isAdmin = author.Id == BotOwner.Id; SocketTextChannel channel = message.Channel as SocketTextChannel; SocketGuild guild = channel?.Guild; GuildConfig config = null; if (channel != null) { if ((config = ConfigManager.GetManagedConfig(guild.Id)) == null) { return; } if (!isAdmin) { isAdmin = config.Permissions.IsAdmin(message.Author.Id); } } var context = new SocketCommandContext(_client, message); if (!isAdmin) { // we're not admin, and we've spammed. we get muted. if (NeedsRateLimit(author.Id)) { var startTime = DateTimeOffset.UtcNow; var endTime = await GiveRateLimit(author.Id, startTime, config); bool notify = true; if (config != null && author is SocketGuildUser gu && config.UserRateLimitCounts[author.Id] > 3) { try { notify = false; var reason = $"User was automatically kicked after being rate limited {config.UserRateLimitCounts[author.Id]} times."; await gu.KickAsync(reason, new RequestOptions { AuditLogReason = reason }); } catch (Exception) { // TODO notify no ability to kick. --> mute persists, no worries. // could not kick notify = true; } } if (notify) { await context.Channel.SendMessageAsync($"{author.Mention}, you have been rate limited for {(endTime - startTime).TotalMinutes} minutes."); } // go over messages of spam, delete them foreach (var msg in RateLimits.Select(x => x.Value).SelectMany(x => x.Item1)) { await msg.DeleteAsync(new RequestOptions { AuditLogReason = "User was automatically rate limited." }); } // clear UntrackRateLimit(author.Id); return; } if (!CooldownReady(message.Author.Id, DateTimeOffset.UtcNow)) { return; } } if (NeedsTrackingClear(author.Id)) { UntrackRateLimit(author.Id); } var argPos = 0; if (message.Content.EqualsIgnoreCase(".") || !(message.HasCharPrefix('.', ref argPos) /*|| message.HasMentionPrefix(_client.CurrentUser, ref argPos))*/)) { return; } // give command cooldown GiveCooldown(message.Author.Id, DateTimeOffset.UtcNow.AddSeconds(3)); // attempt execute var result = await _commands.ExecuteAsync(context, argPos, _services); // no success if (!result.IsSuccess) { // try looking for tags with this key if (channel != null) { var key = message.Content.Substring(1); var check = Format.Sanitize(key); if (check.Equals(key)) { // we dont have this key, try looking for other people's keys if (!config.HasTagKey(author.Id, key)) { if (config.AnyKeyName(key)) { var tagstr = message.Content.Substring(1); // first, check for any global tags. var globalTags = config.Tags.Values.SelectMany(x => x.Where(y => y.Key.Contains(tagstr) && y.IsGlobal)); if (globalTags.Any()) { // only one. get it if (globalTags.Count() == 1) { var tag = globalTags.First(); result = await _commands.ExecuteAsync(context, $"tag -g {tag.OwnerId} {tag.Key}", multiMatchHandling : MultiMatchHandling.Exception); if (result.IsSuccess) { return; } } else // multiple, log { // todo result = await _commands.ExecuteAsync(context, $"tag -f tags::global", multiMatchHandling : MultiMatchHandling.Exception); if (result.IsSuccess) { return; } } } result = await _commands.ExecuteAsync(context, $"tag -f {tagstr}", multiMatchHandling : MultiMatchHandling.Exception); } if (result.IsSuccess) { return; } } else { // we have this key, get ours //TakeCooldown(message.Author.Id); var tag = config.Tags[author.Id].First(x => x.Key.EqualsIgnoreCase(key)); await channel.SendMessageAsync($"{Format.Bold($"Tag: {tag.Key}")}" + $"\n{tag.Value}"); return; } } } TakeCooldown(author.Id); // send the problem if (!result.ErrorReason.EqualsIgnoreCase("Unknown command.")) { await context.Channel.SendMessageAsync(result.ErrorReason); } } } catch (Exception e) { await Log(new LogMessage(LogSeverity.Info, "self", e.ToString(), e)); } }