Example #1
0
        public static bool TryParseAbsoluteReminder(Match timerMatch, BotMessageData messageData, out string query)
        {
            query = string.Empty;

            string toMatch     = timerMatch.Groups["target"].ToString().Trim();
            string to          = toMatch == "me" ? messageData.UserName : toMatch;
            string req         = to == messageData.UserName ? string.Empty : messageData.UserName;
            string durationStr = string.Empty;
            long   duration    = 0;

            GroupCollection matchGroups = timerMatch.Groups;
            string          reason      = matchGroups["reason"].ToString();

            var dateTimeString = matchGroups["time"].ToString();

            if (matchGroups["date"].Success)
            {
                dateTimeString = matchGroups["date"] + " " + dateTimeString;
            }

            if (DateTime.TryParse(dateTimeString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out DateTime dt))
            {
                duration    = (long)dt.Subtract(DateTime.UtcNow).TotalSeconds;
                durationStr = $"{duration}s";
            }

            if (duration < 10 || duration > TOOLONG)
            {
                return(false);
            }

            query = $"timer for:\"{to}\" {durationStr} {reason}";

            return(true);
        }
Example #2
0
 public DiscordBotContext(DiscordSocketClient client, SocketInteraction interaction, IUserMessage message)
 {
     this.Client      = client;
     this.Interaction = interaction;
     this.MessageData = BotMessageData.Create(interaction, message, this.Settings);
     this.Message     = message;
 }
Example #3
0
        /// <summary>
        /// Sends farewells and mod log messages, if configured.
        /// </summary>
        private async Task HandleUserLeftAsync(SocketGuildUser guildUser)
        {
            var settings = SettingsConfig.GetSettings(guildUser.Guild.Id);

            if (!string.IsNullOrEmpty(settings.Farewell) && settings.FarewellId != 0)
            {
                var farewell = settings.Farewell.Replace("%user%", guildUser.Mention);
                farewell = farewell.Replace("%username%", $"{guildUser}");

                farewell = Consts.ChannelRegex.Replace(farewell, new MatchEvaluator((Match chanMatch) =>
                {
                    string channelName = chanMatch.Groups[1].Value;
                    var channel        = guildUser.Guild.Channels.Where(c => c is ITextChannel && c.Name.Equals(channelName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
                    return((channel as ITextChannel)?.Mention ?? $"#{channelName}");
                }));

                var farewellChannel = this.Client.GetChannel(settings.FarewellId) as ITextChannel ?? guildUser.Guild.DefaultChannel;
                if (farewellChannel.GetCurrentUserPermissions().SendMessages)
                {
                    await farewellChannel.SendMessageAsync(farewell);
                }
            }

            // mod log
            if (settings.HasFlag(ModOptions.Mod_LogUserLeave) && this.Client.GetChannel(settings.Mod_LogId) is ITextChannel modLogChannel && modLogChannel.GetCurrentUserPermissions().SendMessages)
            {
                this.BatchSendMessageAsync(modLogChannel, $"{guildUser.Mention} ({guildUser}) left.");
            }

            var messageData = BotMessageData.Create(guildUser, settings);

            messageData.Content = ".timer clear";
            messageData.Prefix  = ".";
            await this.BotApi.IssueRequestAsync(messageData);
        }
Example #4
0
        public static BotMessageData Create(IUserMessage message, Settings serverSettings)
        {
            var preferEmbeds = ((message.Channel as SocketTextChannel)?.GetCurrentUserPermissions().EmbedLinks ?? false) && serverSettings.PreferEmbeds;

            var messageData = new BotMessageData(BotType.Discord)
            {
                DiscordMessageData = message,
                UserName           = message.Author.Username,
                UserId             = message.Author.Id.ToString(),
                UserHost           = message.Author.Id.ToString(),
                Channel            = message.Channel.Id.ToString(),
                Server             = (message.Channel as IGuildChannel)?.GuildId.ToString() ?? "private",
                MessageId          = message.Id.ToString(),
                Content            = message.Content,
                Format             = preferEmbeds ? "embed" : string.Empty,
                Sasshat            = serverSettings.SasshatEnabled,
                Prefix             = serverSettings.Prefix,
            };

            // if the user does not have @everyone permissions, block its use
            if (!(message.Author as SocketGuildUser)?.GetPermissions(message.Channel as SocketGuildChannel).MentionEveryone ?? false)
            {
                messageData.Content = messageData.Content.Replace("@everyone", "@every\x200Bone").Replace("@here", "@he\x200Bre");
            }

            return(messageData);
        }
Example #5
0
        // TODO: IRC library needs... some improvements.
        public async void OnIrcEventAsync(MessageData data, IrcClient client)
        {
            var responses = new List <string>();
            var settings  = new Settings();

            if (data.Verb == ReplyCode.RPL_ENDOFMOTD || data.Verb == ReplyCode.RPL_NOMOTD) //  motd end or motd missing
            {
                foreach (string channel in this.Config.Irc.Servers.Where(s => s.Host == client.Id).First().Channels)
                {
                    serverData[client.Host].Channels[channel.ToLowerInvariant()] = new ChannelData();
                    client.Command("JOIN", channel, string.Empty);
                }
            }

            if (data.Verb == "PRIVMSG")
            {
                string query = string.Empty;

                var props = new Dictionary <string, string> {
                    { "server", client.Host.ToLowerInvariant() },
                    { "channel", data.Target.ToLowerInvariant() },
                };

                this.TrackEvent("messageReceived", props);

                // TODO: put this in config
                // twitch parses "." prefix as internal commands; so we have to remap it :(
                if (client.Host == "irc.chat.twitch.tv")
                {
                    settings.Prefix = "^";
                }

                var messageData = BotMessageData.Create(data, client, settings);

                await this.PreProcessMessage(messageData, settings);

                responses.AddRange((await this.ProcessMessageAsync(messageData, settings)).Responses);

                foreach (string response in responses)
                {
                    client.Command("PRIVMSG", data.Target, response);
                    this.TrackEvent("messageSent", props);
                }
            }
            // TODO: This stuff should be handled by the IRC library...
            else if (data.Verb == "353")
            {
                var namesMatch = namesRegex.Match(data.Text);
                var channel    = namesMatch.Groups[1].ToString().ToLowerInvariant();
                var userList   = namesMatch.Groups[2].ToString();
                var users      = userList.Split(new[] { ' ' });

                if (!serverData[client.Host].Channels.ContainsKey(channel))
                {
                    this.serverData[client.Host].Channels[channel] = new ChannelData();
                }

                this.serverData[client.Host].Channels[channel].Users = new HashSet <string>(users);
            }
        }
Example #6
0
        protected async Task PreProcessMessage(BotMessageData messageData, Settings settings)
        {
            this.messageCount++;

            // Update the seen data
            this.UpdateSeen(messageData.Channel + messageData.UserName, new SeenUserData
            {
                Name    = messageData.UserId ?? messageData.UserName,
                Channel = messageData.Channel,
                Server  = messageData.Server,
                //Text = messageData.Content, removed due to discord tos
            });

            var httpMatch = Consts.HttpRegex.Match(messageData.Content);

            if (httpMatch.Success)
            {
                this.urls[messageData.Channel] = httpMatch.Value;
            }

            if (settings.FunResponsesEnabled && !string.IsNullOrEmpty(messageData.Content))
            {
                var repeat = repeatData.GetOrAdd(messageData.Channel + messageData.Server, new RepeatData());
                if (string.Equals(repeat.Text, messageData.Content, StringComparison.OrdinalIgnoreCase))
                {
                    if (!repeat.Nicks.Contains(messageData.UserId ?? messageData.UserName))
                    {
                        repeat.Nicks.Add(messageData.UserId ?? messageData.UserName);
                    }

                    if (repeat.Nicks.Count == 3)
                    {
                        await this.RespondAsync(messageData, messageData.Content);

                        repeat.Reset(string.Empty, string.Empty);
                    }
                }
                else
                {
                    repeat.Reset(messageData.UserId ?? messageData.UserName, messageData.Content);
                }
            }

            string command = messageData.Command;

            if (!CommandsConfig.Instance.Commands.ContainsKey(command))
            {
                command = string.Empty;
            }

            if (string.IsNullOrEmpty(command))
            {
                // match fun
                bool mentionsBot = messageData.MentionsBot(this.Config.Name, Convert.ToUInt64(this.UserId));
                if (CommandsConfig.Instance.TryParseForCommand(messageData.Content, mentionsBot, out string parsedCommand, out string query))
                {
                    messageData.Query = query;
                }
            }
        }
Example #7
0
        public async Task HandleMessage(MessageEvent messageEvent)
        {
            try
            {
                if (messageEvent.IsSystemMessage || messageEvent.CreatedByWebhook != null || messageEvent.CreatedBy == this.client.Me.Id || messageEvent.IsReply)
                {
                    return;
                }

                if (this.Throttler.IsThrottled(messageEvent.CreatedBy.ToString(), ThrottleType.User))
                {
                    Log.Debug($"messaging throttle from user: {messageEvent.CreatedBy} on chan {messageEvent.ChannelId} server {messageEvent.ServerId}");
                    return;
                }

                if (this.Throttler.IsThrottled(messageEvent.ServerId.ToString(), ThrottleType.Guild))
                {
                    Log.Debug($"messaging throttle from guild: {messageEvent.CreatedBy} on chan {messageEvent.ChannelId} server {messageEvent.ServerId}");
                    return;
                }

                var settings = new Settings
                {
                    FunResponsesEnabled = true,
                    AutoTitlesEnabled   = true,
                    PreferEmbeds        = true,
                };

                var messageData = BotMessageData.Create(messageEvent.Message, settings);

                await this.PreProcessMessage(messageData, settings);

                BotResponseData responseData = await this.ProcessMessageAsync(messageData, settings);

                if (responseData.Embed != null)
                {
                    await this.RespondAsync(messageEvent.Message, string.Empty, bypassEdit : true, responseData.Embed.CreateGuildedEmbed());
                }
                else
                {
                    foreach (string response in responseData.Responses)
                    {
                        await this.RespondAsync(messageEvent.Message, response, bypassEdit : responseData.Responses.Count > 1);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Warning(ex, $"Error in HandleMessage");
                this.AppInsights?.TrackException(ex);
            }
        }
Example #8
0
        public static BotMessageData Create(SocketUser user, SocketGuild guild, Settings serverSettings)
        {
            var messageData = new BotMessageData(BotType.Discord)
            {
                UserName = user.Username,
                UserId   = user.Id.ToString(),
                UserHost = user.Id.ToString(),
                Server   = guild.Id.ToString(),
                Prefix   = serverSettings.Prefix,
            };

            return(messageData);
        }
Example #9
0
        protected override Task RespondAsync(BotMessageData messageData, string text)
        {
            this.ircClients[messageData.Server]?.Command("PRIVMSG", messageData.Channel, text);

            var props = new Dictionary <string, string> {
                { "server", messageData.Server.ToLowerInvariant() },
                { "channel", messageData.Channel.ToLowerInvariant() },
            };

            this.TrackEvent("messageSent", props);

            return(Task.CompletedTask);
        }
Example #10
0
 public async Task <BotResponseData> IssueRequestAsync(BotMessageData messageData)
 {
     try
     {
         var response = await new Uri($"{this.apiEndpoint}/{messageData.Command}").PostJsonAsync(messageData);
         return(JsonConvert.DeserializeObject <BotResponseData>(await response.Content.ReadAsStringAsync()));
     }
     catch (Exception ex)
     {
         Log.Error(ex, "Failed to parse {Endpoint}", this.apiEndpoint.ToString());
         return(new BotResponseData());
     }
 }
Example #11
0
        public static BotMessageData Create(SocketInteraction interaction, IUserMessage message, Settings serverSettings)
        {
            var preferEmbeds = ((interaction.Channel as SocketTextChannel)?.GetCurrentUserPermissions().EmbedLinks ?? false) && serverSettings.PreferEmbeds;

            var messageData = new BotMessageData(BotType.Discord)
            {
                DiscordInteraction = interaction,
                UserName           = interaction.User.Username,
                UserId             = interaction.User.Id.ToString(),
                UserHost           = interaction.User.Id.ToString(),
                Channel            = interaction.Channel.Id.ToString(),
                Server             = (interaction.Channel as IGuildChannel)?.GuildId.ToString() ?? "private",
                MessageId          = message?.Id.ToString(),
                Format             = preferEmbeds ? "embed" : string.Empty,
                Sasshat            = serverSettings.SasshatEnabled,
                Content            = message?.Content ?? serverSettings.Prefix + ((interaction as SocketCommandBase)?.CommandName ?? (interaction as SocketMessageComponent)?.Data.CustomId),
                Prefix             = serverSettings.Prefix,
                TargetUserName     = (interaction as SocketUserCommand)?.Data.Member.UserOrNickname(),
                TargetUserId       = (interaction as SocketUserCommand)?.Data.Member.Id.ToString(),
            };

            if (interaction is SocketMessageCommand messageCommand)
            {
                messageData.Content = messageCommand.Data.Message.Content;
            }
            else if (interaction is SocketSlashCommand slashCommand)
            {
                messageData.RequestOptions = new Dictionary <string, string>();
                foreach (var option in slashCommand.Data.Options)
                {
                    if (option.Type == ApplicationCommandOptionType.SubCommand)
                    {
                        messageData.Content += $" {option.Name}";
                        messageData.RequestOptions.Add(option.Name, option.Name);
                    }
                    else
                    {
                        messageData.Content += $" {option.Value}";
                        messageData.RequestOptions.Add(option.Name, option.Value.ToString());
                    }
                }
            }

            // if the user does not have @everyone permissions, block its use
            if (!(interaction.User as SocketGuildUser)?.GetPermissions(interaction.Channel as SocketGuildChannel).MentionEveryone ?? false)
            {
                messageData.Content = messageData.Content.Replace("@everyone", "@every\x200Bone").Replace("@here", "@he\x200Bre");
            }

            return(messageData);
        }
Example #12
0
 public async Task <BotResponseData> IssueRequestAsync(BotMessageData messageData)
 {
     try
     {
         var response = await new Uri($"{this.apiEndpoint}/{messageData.Command}").PostJsonAsync(messageData);
         return(JsonConvert.DeserializeObject <BotResponseData>(await response.Content.ReadAsStringAsync()));
     }
     catch (Exception ex)
     {
         // TODO: proper logger
         Console.WriteLine($"Failed to parse {this.apiEndpoint}: ");
         Console.WriteLine(ex);
         return(new BotResponseData());
     }
 }
Example #13
0
 public DiscordBotContext(DiscordSocketClient client, SocketUserMessage message)
 {
     this.Client      = client;
     this.Message     = message;
     this.MessageData = BotMessageData.Create(Message, this.Settings);
 }
 protected override async Task RespondAsync(BotMessageData messageData, string response)
 {
     await this.RespondAsync(messageData.DiscordMessageData, response);
 }
Example #15
0
 protected override async Task RespondAsync(BotMessageData messageData, string response)
 {
     await this.RespondAsync(messageData.DiscordMessageData, response, rateLimitChecked : messageData.RateLimitChecked);
 }
Example #16
0
        public async Task <BotResponseData> IssueRequestAsync(BotMessageData messageData, string query)
        {
            var responses    = new List <string>();
            var responseData = new BotResponseData();

            string requestUrl = string.Format("{0}?apikey={1}&nick={2}&host={3}&server={4}&channel={5}&bottype={6}&userId={7}&format={8}&query={9}",
                                              this.apiEndpoint,
                                              this.apiKey,
                                              WebUtility.UrlEncode(messageData.UserName),
                                              WebUtility.UrlEncode(messageData.UserHost ?? messageData.UserId),
                                              messageData.Server,
                                              WebUtility.UrlEncode(messageData.Channel),
                                              this.botType.ToString().ToLowerInvariant(),
                                              messageData.UserId,
                                              messageData.Format,
                                              WebUtility.UrlEncode(query));

            var botResponse = await Utilities.GetApiResponseAsync <BotApiResponse>(new Uri(requestUrl));

            if (botResponse != null)
            {
                if (botResponse.Embed != null)
                {
                    responseData.Embed = botResponse.Embed;
                }
                else
                {
                    // TODO: fix API stupidity and just return the array
                    var botResponses = botResponse.Msgs.Length > 0 ? botResponse.Msgs : new string[] { botResponse.Msg };

                    if (botResponses[0] != null)
                    {
                        char a = (char)1;
                        char b = (char)2;
                        char c = (char)3;

                        foreach (var response in botResponses)
                        {
                            if (response != null)
                            {
                                responses.Add(response.Replace("%a", a.ToString()).Replace("%b", b.ToString()).Replace("%c", c.ToString()));
                            }
                        }

                        if (this.botType == BotType.Discord)
                        {
                            string response = string.Join("\n", responses);

                            // Extra processing for figlet/cowsay on Discord
                            if (query.StartsWith("cowsay", StringComparison.OrdinalIgnoreCase) || query.StartsWith("figlet", StringComparison.OrdinalIgnoreCase))
                            {
                                // use a non printable character to force preceeding whitespace to display correctly
                                response = "```" + (char)1 + response + "```";
                            }

                            responses = new List <string> {
                                response
                            };
                        }
                    }
                }

                responseData.Responses = responses;
            }

            return(responseData);
        }
Example #17
0
 protected override async Task RespondAsync(BotMessageData messageData, string text)
 {
     await this.RespondAsync(messageData.GuildedMessageData, text);
 }
Example #18
0
        // TODO:
        // this is icky
        // it's getting worse...TODO: read the above todo and fix it
        internal DiscordCommands(DiscordSocketClient client, AudioManager audioManager, BotApi botApi)
        {
            this.client       = client;
            this.audioManager = audioManager;
            this.botApi       = botApi;
            this.Commands     = new Dictionary <string, Func <SocketUserMessage, Task <CommandResponse> > >();

            this.CreateScriptOptions();

            Commands.Add("debug", (message) =>
            {
                var serverId   = (message.Channel as IGuildChannel)?.GuildId.ToString() ?? "n/a";
                var botVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
                var response   = new CommandResponse {
                    Text = $"```Server ID: {serverId} | Channel ID: {message.Channel.Id} | Your ID: {message.Author.Id} | Shard ID: {client.ShardId} | Version: {botVersion} | Discord.NET Version: {DiscordSocketConfig.Version}```"
                };
                return(Task.FromResult(response));
            });

            Commands.Add("seen", async(message) =>
            {
                if (message.Channel is IGuildChannel guildChannel)
                {
                    var settings = SettingsConfig.GetSettings(guildChannel.GuildId.ToString());
                    if (!settings.SeenEnabled)
                    {
                        return(new CommandResponse {
                            Text = "Seen data is not being tracked for this server.  Enable it in the admin settings panel."
                        });
                    }

                    string[] parts = message.Content.Split(new[] { ' ' }, 2);
                    if (parts.Length != 2)
                    {
                        return(new CommandResponse {
                            Text = "Usage: .seen username"
                        });
                    }

                    var targetUser = (await guildChannel.Guild.GetUsersAsync()).Find(parts[1]).FirstOrDefault();
                    if (targetUser != null)
                    {
                        if (targetUser.Id == client.CurrentUser.Id)
                        {
                            return(new CommandResponse {
                                Text = $"I was last seen...wait...seriously? Ain't no one got time for your shit, {message.Author.Username}."
                            });
                        }

                        if (targetUser.Id == message.Author.Id)
                        {
                            return(new CommandResponse {
                                Text = $"You were last seen now, saying: ... god DAMN it {message.Author.Username}, quit wasting my time"
                            });
                        }

                        string query    = $"seen {targetUser.Id} {targetUser.Username}";
                        var messageData = BotMessageData.Create(message, query, settings);

                        var response = (await this.botApi.IssueRequestAsync(messageData, query)).Responses.FirstOrDefault();

                        if (response != null)
                        {
                            return(new CommandResponse {
                                Text = response
                            });
                        }
                    }

                    return(new CommandResponse {
                        Text = $"I...omg...I have not seen {parts[1]} in this channel :X I AM SOOOOOO SORRY"
                    });
                }

                return(null);
            });

            Commands.Add("remove", async(message) =>
            {
                if (message.Channel is IGuildChannel guildChannel)
                {
                    if (message.Author.Id != guildChannel.Guild.OwnerId)
                    {
                        return(new CommandResponse {
                            Text = "Restricted to server owner."
                        });
                    }

                    var settings = SettingsConfig.GetSettings(guildChannel.GuildId.ToString());

                    string[] parts = message.Content.Split(new[] { ' ' }, 3);

                    if (parts.Length != 3)
                    {
                        return(new CommandResponse {
                            Text = "Usage: .remove type #; valid types are timer and wc"
                        });
                    }

                    var type = parts[1].ToLowerInvariant();
                    var id   = parts[2];

                    string query    = $"remove {CommandsConfig.Instance.HelperKey} {type} {id}";
                    var messageData = BotMessageData.Create(message, query, settings);
                    var response    = (await this.botApi.IssueRequestAsync(messageData, query)).Responses.FirstOrDefault();

                    if (response != null)
                    {
                        return(new CommandResponse {
                            Text = response
                        });
                    }
                }

                return(null);
            });

            Commands.Add("status", async(message) =>
            {
                var serversStatus = await Utilities.GetApiResponseAsync <HeartbeatData[]>(BotConfig.Instance.HeartbeatEndpoint);

                var dataSb = new StringBuilder();
                dataSb.Append("```cs\n" +
                              "type       shard   server count      users   voice count\n");

                int serverTotal = 0;
                int userTotal   = 0;
                int voiceTotal  = 0;
                foreach (HeartbeatData heartbeat in serversStatus)
                {
                    serverTotal += heartbeat.ServerCount;
                    userTotal   += heartbeat.UserCount;
                    voiceTotal  += heartbeat.VoiceChannelCount;

                    var botType = heartbeat.BotType.PadRight(11);
                    var shard   = heartbeat.Shard.ToString().PadLeft(4);
                    var servers = heartbeat.ServerCount.ToString().PadLeft(13);
                    var users   = heartbeat.UserCount.ToString().PadLeft(10);
                    var voice   = heartbeat.VoiceChannelCount.ToString().PadLeft(13);

                    dataSb.Append($"{botType} {shard}  {servers} {users} {voice}\n");
                }

                // add up totals
                dataSb.Append($"-------\n");
                dataSb.Append($"Total:            {serverTotal.ToString().PadLeft(13)} {userTotal.ToString().PadLeft(10)} {voiceTotal.ToString().PadLeft(13)}\n");

                dataSb.Append("```");

                return(new CommandResponse {
                    Text = dataSb.ToString()
                });
            });

            Commands.Add("voice", (message) =>
            {
                var channel = (message.Author as IGuildUser)?.VoiceChannel;
                if (channel == null)
                {
                    return(Task.FromResult(new CommandResponse {
                        Text = "Join a voice channel first"
                    }));
                }

                Task.Run(async() =>
                {
                    try
                    {
                        await audioManager.JoinAudioAsync(channel);
                    }
                    catch (Exception ex)
                    {
                        // TODO: proper logging
                        Console.WriteLine(ex);
                    }
                }).Forget();

                return(Task.FromResult((CommandResponse)null));
            });

            Commands.Add("dvoice", (message) =>
            {
                if (message.Channel is IGuildChannel channel)
                {
                    Task.Run(async() =>
                    {
                        try
                        {
                            await audioManager.LeaveAudioAsync(channel);
                        }
                        catch (Exception ex)
                        {
                            // TODO: proper logging
                            Console.WriteLine(ex);
                        }
                    }).Forget();
                }

                return(Task.FromResult((CommandResponse)null));
            });

            Commands.Add("devoice", (message) =>
            {
                if (message.Channel is IGuildChannel channel)
                {
                    Task.Run(async() =>
                    {
                        try
                        {
                            await audioManager.LeaveAudioAsync(channel);
                        }
                        catch (Exception ex)
                        {
                            // TODO: proper logging
                            Console.WriteLine(ex);
                        }
                    }).Forget();
                }

                return(Task.FromResult((CommandResponse)null));
            });

            Commands.Add("clear", async(message) =>
            {
                if (message.Channel is IDMChannel)
                {
                    return(null);
                }

                var guildUser = message.Author as IGuildUser;
                if (!guildUser.GuildPermissions.ManageMessages)
                {
                    return(new CommandResponse {
                        Text = "you don't have permissions to clear messages, fartface"
                    });
                }

                var guildChannel = message.Channel as IGuildChannel;

                string[] parts = message.Content.Split(new[] { ' ' }, 3);
                if (parts.Length != 2 && parts.Length != 3)
                {
                    return(new CommandResponse {
                        Text = "Usage: .clear #; Usage for user specific messages: .clear # username"
                    });
                }

                IUser deletionUser = null;

                if (parts.Length == 3)
                {
                    if (ulong.TryParse(parts[2], out ulong userId))
                    {
                        deletionUser = await message.Channel.GetUserAsync(userId);
                    }
                    else
                    {
                        deletionUser = (await guildUser.Guild.GetUsersAsync().ConfigureAwait(false)).Find(parts[2]).FirstOrDefault();
                    }

                    if (deletionUser == null)
                    {
                        return(new CommandResponse {
                            Text = "Couldn't find the specified user. Try their ID if nick matching is struggling"
                        });
                    }
                }

                var botGuildUser = await guildChannel.GetUserAsync(client.CurrentUser.Id);
                bool botOnly     = deletionUser == botGuildUser;

                if (!botOnly && !botGuildUser.GetPermissions(guildChannel).ManageMessages)
                {
                    return(new CommandResponse {
                        Text = "yeah I don't have the permissions to delete messages, buttwad."
                    });
                }

                if (int.TryParse(parts[1], out int count))
                {
                    var textChannel = message.Channel as ITextChannel;
                    // +1 for the current .clear message
                    if (deletionUser == null)
                    {
                        count = Math.Min(99, count) + 1;
                    }
                    else
                    {
                        count = Math.Min(100, count);
                    }

                    // download messages until we've hit the limit
                    var msgsToDelete      = new List <IMessage>();
                    var msgsToDeleteCount = 0;
                    ulong?lastMsgId       = null;
                    var i = 0;
                    while (msgsToDeleteCount < count)
                    {
                        i++;
                        IEnumerable <IMessage> downloadedMsgs;
                        try
                        {
                            if (!lastMsgId.HasValue)
                            {
                                downloadedMsgs = await textChannel.GetMessagesAsync(count).Flatten();
                            }
                            else
                            {
                                downloadedMsgs = await textChannel.GetMessagesAsync(lastMsgId.Value, Direction.Before, count).Flatten();
                            }
                        }
                        catch (Exception ex)
                        {
                            downloadedMsgs = new IMessage[0];
                            Console.WriteLine(ex);
                        }

                        if (downloadedMsgs.Count() > 0)
                        {
                            lastMsgId = downloadedMsgs.Last().Id;

                            var msgs           = downloadedMsgs.Where(m => (deletionUser == null || m.Author?.Id == deletionUser.Id)).Take(count - msgsToDeleteCount);
                            msgsToDeleteCount += msgs.Count();
                            msgsToDelete.AddRange(msgs);
                        }
                        else
                        {
                            break;
                        }

                        if (i >= 5)
                        {
                            break;
                        }
                    }

                    var settings = SettingsConfig.GetSettings(guildUser.GuildId.ToString());
                    if (settings.HasFlag(ModOptions.Mod_LogDelete) && this.client.GetChannel(settings.Mod_LogId) is ITextChannel modLogChannel && modLogChannel.GetCurrentUserPermissions().SendMessages)
                    {
                        modLogChannel.SendMessageAsync($"{guildUser.Username}#{guildUser.Discriminator} cleared {msgsToDeleteCount} messages from {textChannel.Mention}").Forget();
                    }

                    try
                    {
                        await(message.Channel as ITextChannel).DeleteMessagesAsync(msgsToDelete);
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        return(new CommandResponse {
                            Text = "Bots cannot delete messages older than 2 weeks."
                        });
                    }

                    return(null);
                }
                else
                {
                    return(new CommandResponse {
                        Text = "Usage: .clear #"
                    });
                }
            });

            Commands.Add("jpeg", async(message) =>
            {
                var messageParts = message.Content.Split(new[] { ' ' }, 2);
                var fileName     = "moar.jpeg";
                var url          = string.Empty;
                if (messageParts.Length == 2 && Uri.IsWellFormedUriString(messageParts[1], UriKind.Absolute))
                {
                    url = messageParts[1];
                }
                else
                {
                    Attachment img = message.Attachments.FirstOrDefault();
                    if (img != null || DiscordBot.imageUrls.TryGetValue(message.Channel.Id.ToString(), out img))
                    {
                        url      = img.Url;
                        fileName = img.Filename;
                    }
                }

                if (!string.IsNullOrEmpty(url))
                {
                    using (var httpClient = new HttpClient())
                    {
                        var response = await httpClient.GetAsync(CommandsConfig.Instance.JpegEndpoint.AppendQueryParam("url", url));
                        var stream   = await response.Content.ReadAsStreamAsync();

                        return(new CommandResponse
                        {
                            Attachment = new FileResponse
                            {
                                Name = fileName,
                                Stream = stream,
                            }
                        });
                    }
                }

                return(null);
            });

            Commands.Add("userinfo", async(message) =>
            {
                SocketUser targetUser = message.MentionedUsers?.FirstOrDefault();

                if (targetUser == null)
                {
                    var messageParts = message.Content.Split(new[] { ' ' }, 2);

                    if (messageParts.Length == 2)
                    {
                        if (message.Channel is IGuildChannel guildChannel)
                        {
                            targetUser = (await guildChannel.Guild.GetUsersAsync()).Find(messageParts[1]).FirstOrDefault() as SocketUser;
                        }

                        if (targetUser == null)
                        {
                            return(new CommandResponse {
                                Text = "User not found. Try a direct mention."
                            });
                        }
                    }
                    else
                    {
                        targetUser = message.Author;
                    }
                }

                var guildUser = targetUser as IGuildUser;

                if (guildUser == null)
                {
                    return(null);
                }

                var userInfo = new
                {
                    Title        = $"UserInfo for {targetUser.Username}#{targetUser.Discriminator}",
                    AvatarUrl    = targetUser.GetAvatarUrl(),
                    NicknameInfo = !string.IsNullOrEmpty(guildUser.Nickname) ? $" aka {guildUser.Nickname}" : "",
                    Footnote     = CommandsConfig.Instance.UserInfoSnippets.Random(),
                    Created      = $"{targetUser.GetCreatedDate().ToString("dd MMM yyyy")} {targetUser.GetCreatedDate().ToString("hh:mm:ss tt")} UTC",
                    Joined       = guildUser.JoinedAt.HasValue ? $"{guildUser.JoinedAt.Value.ToString("dd MMM yyyy")} {guildUser.JoinedAt.Value.ToString("hh:mm:ss tt")} UTC" : "[data temporarily missing]",
                    Id           = targetUser.Id.ToString(),
                };

                EmbedBuilder embedBuilder = null;
                string text = string.Empty;

                var settings = SettingsConfig.GetSettings(guildUser.GuildId.ToString());

                if ((message.Channel as ITextChannel).GetCurrentUserPermissions().EmbedLinks&& settings.PreferEmbeds)
                {
                    embedBuilder = new EmbedBuilder
                    {
                        Title        = userInfo.Title,
                        ThumbnailUrl = userInfo.AvatarUrl,
                    };

                    if (!string.IsNullOrEmpty(userInfo.NicknameInfo))
                    {
                        embedBuilder.Description = userInfo.NicknameInfo;
                    }

                    embedBuilder.Footer = new EmbedFooterBuilder
                    {
                        Text = userInfo.Footnote,
                    };

                    embedBuilder.AddField((field) =>
                    {
                        field.IsInline = true;
                        field.Name     = "created";
                        field.Value    = userInfo.Created;
                    });

                    if (guildUser.JoinedAt.HasValue)
                    {
                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "joined";
                            field.Value    = userInfo.Joined;
                        });
                    }

                    var roles = new List <string>();
                    foreach (ulong roleId in guildUser.RoleIds)
                    {
                        roles.Add(guildUser.Guild.Roles.First(g => g.Id == roleId).Name.TrimStart('@'));
                    }

                    embedBuilder.AddField((field) =>
                    {
                        field.IsInline = false;
                        field.Name     = "roles";
                        field.Value    = string.Join(", ", roles);
                    });

                    embedBuilder.AddField((field) =>
                    {
                        field.IsInline = true;
                        field.Name     = "Id";
                        field.Value    = userInfo.Id;
                    });
                }
                else
                {
                    text = $"{userInfo.Title}{userInfo.NicknameInfo}: ID: {userInfo.Id} | Created: {userInfo.Created} | Joined: {userInfo.Joined} | word on the street: {userInfo.Footnote}";
                }

                return(new CommandResponse {
                    Text = text, Embed = embedBuilder
                });
            });

            Commands.Add("serverinfo", async(message) =>
            {
                EmbedBuilder embedBuilder = null;
                string text = string.Empty;

                if (message.Channel is IGuildChannel guildChannel && message.Channel is ITextChannel textChannel)
                {
                    var guild      = guildChannel.Guild;
                    var emojiCount = guild.Emotes.Count();
                    var emojiText  = "no custom emojis? I am ASHAMED to be here";
                    if (emojiCount > 50)
                    {
                        emojiText = $"...{emojiCount} emojis? hackers";
                    }
                    else if (emojiCount == 50)
                    {
                        emojiText = "wow 50 custom emojis! that's the max";
                    }
                    else if (emojiCount >= 40)
                    {
                        emojiText = $"{emojiCount} custom emojis in here. impressive...most impressive...";
                    }
                    else if (emojiCount > 25)
                    {
                        emojiText = $"the custom emoji force is strong with this guild. {emojiCount} is over halfway to the max.";
                    }
                    else if (emojiCount > 10)
                    {
                        emojiText = $"{emojiCount} custom emoji is...passable";
                    }
                    else if (emojiCount > 0)
                    {
                        emojiText = $"really, only {emojiCount} custom emoji? tsk tsk.";
                    }

                    await(message.Channel as SocketGuildChannel).Guild.DownloadUsersAsync();
                    var serverInfo = new
                    {
                        Title     = $"Server Info for {guild.Name}",
                        UserCount = (await guild.GetUsersAsync()).Count(),
                        Owner     = (await guild.GetOwnerAsync()).Username,
                        Created   = $"{guild.CreatedAt.ToString("dd MMM yyyy")} {guild.CreatedAt.ToString("hh:mm:ss tt")} UTC",
                        EmojiText = emojiText,
                    };

                    var settings = SettingsConfig.GetSettings(guildChannel.GuildId.ToString());

                    if (textChannel.GetCurrentUserPermissions().EmbedLinks&& settings.PreferEmbeds)
                    {
                        embedBuilder = new EmbedBuilder
                        {
                            Title        = serverInfo.Title,
                            ThumbnailUrl = guildChannel.Guild.IconUrl,
                        };

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "Users";
                            field.Value    = serverInfo.UserCount;
                        });

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "Owner";
                            field.Value    = serverInfo.Owner;
                        });

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "Id";
                            field.Value    = guild.Id;
                        });

                        embedBuilder.AddField((field) =>
                        {
                            field.IsInline = true;
                            field.Name     = "created";
                            field.Value    = serverInfo.Created;
                        });

                        embedBuilder.Footer = new EmbedFooterBuilder
                        {
                            Text = serverInfo.EmojiText,
                        };

                        if (!string.IsNullOrEmpty(guild.SplashUrl))
                        {
                            embedBuilder.Footer.IconUrl = guild.SplashUrl;
                        }
                    }
                    else
                    {
                        text = $"{serverInfo.Title}: Users: {serverInfo.UserCount} | Owner: {serverInfo.Owner} | Id: {guild.Id} | Created: {serverInfo.Created} | {serverInfo.EmojiText}";
                    }
                }
Example #19
0
        protected async Task <BotResponseData> ProcessMessageAsync(BotMessageData messageData, Settings settings)
        {
            var responses    = new List <string>();
            var responseData = new BotResponseData {
                Responses = responses
            };

            if (this.BotApi != null)
            {
                // if an explicit command is being used, it wins out over any implicitly parsed command
                string query   = messageData.Query;
                string command = messageData.Command;

                string[] contentParts = messageData.Content.Split(new[] { ' ' });

                if (string.IsNullOrEmpty(command))
                {
                    if (messageData.Content.ToLowerInvariant().Contains("remind "))
                    {
                        // check for reminders
                        Match timerAtMatch  = Consts.TimerOnRegex.Match(messageData.Content);
                        Match timerAt2Match = Consts.TimerOn2Regex.Match(messageData.Content);
                        if (timerAtMatch.Success && Utilities.TryParseAbsoluteReminder(timerAtMatch, messageData, out query) ||
                            timerAt2Match.Success && Utilities.TryParseAbsoluteReminder(timerAt2Match, messageData, out query))
                        {
                            command = "timer";
                        }
                        else // try relative timers if absolute had no match
                        {
                            Match timerMatch  = Consts.TimerRegex.Match(messageData.Content);
                            Match timer2Match = Consts.Timer2Regex.Match(messageData.Content);

                            if (timerMatch.Success || timer2Match.Success)
                            {
                                Match matchToUse = timerMatch.Success && !timerMatch.Groups["prep"].Value.All(char.IsDigit) ? timerMatch : timer2Match;
                                if (Utilities.TryParseReminder(matchToUse, messageData, out query))
                                {
                                    command = "timer";
                                }
                            }
                        }
                    }

                    if (string.IsNullOrEmpty(command))
                    {
                        if (settings.AutoTitlesEnabled && CommandsConfig.Instance.AutoTitleMatches.Any(t => messageData.Content.Contains(t)))
                        {
                            Match  match      = Consts.HttpRegex.Match(messageData.Content);
                            string matchValue = !string.IsNullOrEmpty(match.Value) ? match.Value : Consts.RedditRegex.Match(messageData.Content).Value;
                            if (!string.IsNullOrEmpty(matchValue))
                            {
                                command = "title";
                                query   = $"{command} {matchValue}";
                            }
                        }
                        else if ((settings.FunResponsesEnabled || IsAuthorOwner(messageData)) && contentParts.Length > 1 && contentParts[1] == "face")
                        {
                            command = "face";
                            query   = $"{command} {contentParts[0]}";
                        }
                    }
                }

                // Ignore if the command is disabled on this server
                if (settings.IsCommandDisabled(CommandsConfig.Instance, command) && !IsAuthorOwner(messageData))
                {
                    return(responseData);
                }

                if (CommandsConfig.Instance.Commands.ContainsKey(command))
                {
                    if (!messageData.RateLimitChecked)
                    {
                        // make sure we're not rate limited
                        var commandKey   = command + messageData.Server;
                        var commandCount = this.commandsIssued.AddOrUpdate(commandKey, 1, (key, val) =>
                        {
                            return(val + 1);
                        });

                        if (commandCount > 12)
                        {
                            return(responseData);
                        }
                        else if (commandCount > 10)
                        {
                            responses.Add("rate limited try later");
                            return(responseData);
                        }
                    }

                    var props = new Dictionary <string, string> {
                        { "serverId", messageData.Server },
                    };
                    this.AppInsights?.TrackEvent(command.ToLowerInvariant(), props);

                    // extra processing on ".title" command
                    if ((command == "title" || command == "t") && messageData.Content.EndsWith(command) && urls.ContainsKey(messageData.Channel))
                    {
                        query += $" {this.urls[messageData.Channel]}";
                    }

                    responseData = await this.BotApi.IssueRequestAsync(messageData, query);
                }
            }

            if (responseData.Responses.Count == 0 && responseData.Embed == null)
            {
                string response = null;
                if (messageData.MentionsBot(this.Config.Name, Convert.ToUInt64(this.UserId)))
                {
                    var responseValue = PhrasesConfig.Instance.PartialMentionPhrases.Where(kvp => messageData.Content.ToLowerInvariant().Contains(kvp.Key.ToLowerInvariant())).FirstOrDefault().Value;
                    if (!string.IsNullOrEmpty(responseValue))
                    {
                        response = PhrasesConfig.Instance.Responses[responseValue].Random();
                    }
                }

                if (response == null && (settings.FunResponsesEnabled || IsAuthorOwner(messageData)) && PhrasesConfig.Instance.ExactPhrases.ContainsKey(messageData.Content) && new Random().Next(1, 100) <= settings.FunResponseChance)
                {
                    response = PhrasesConfig.Instance.Responses[PhrasesConfig.Instance.ExactPhrases[messageData.Content]].Random();
                }

                if (response != null)
                {
                    response = response.Replace("%from%", messageData.UserName);
                    string[] resps = response.Split(new char[] { '|' });
                    responseData.Responses.AddRange(resps);
                }
            }

            return(responseData);
        }
Example #20
0
        protected async Task PreProcessMessage(BotMessageData messageData, Settings settings)
        {
            this.messageCount++;

            if (settings.SeenEnabled)
            {
                var userKey = messageData.UserId ?? messageData.UserName;
                this.UpdateSeen(messageData.Server + userKey, new SeenUserData
                {
                    Name    = userKey,
                    Channel = messageData.Channel,
                    Server  = messageData.Server,
                });
            }

            var httpMatch = Consts.UrlRegex.Match(messageData.Content);

            if (httpMatch.Success)
            {
                this.urls[messageData.Channel] = httpMatch.Value;
            }

            if (settings.FunResponsesEnabled && !string.IsNullOrEmpty(messageData.Content))
            {
                var repeat = repeatData.GetOrAdd(messageData.Channel + messageData.Server, new RepeatData());
                if (string.Equals(repeat.Text, messageData.Content, StringComparison.OrdinalIgnoreCase))
                {
                    if (!repeat.Nicks.Contains(messageData.UserId ?? messageData.UserName))
                    {
                        repeat.Nicks.Add(messageData.UserId ?? messageData.UserName);
                    }

                    if (repeat.Nicks.Count == 3)
                    {
                        var commandKey = $"{messageData.Channel}_{messageData.Server}";
                        this.Throttler.Increment(commandKey, ThrottleType.Repeat);

                        if (!this.Throttler.IsThrottled(commandKey, ThrottleType.Repeat))
                        {
                            await this.RespondAsync(messageData, messageData.Content);
                        }
                        else
                        {
                            Log.Debug("repeat throttled");
                        }

                        repeat.Reset(string.Empty, string.Empty);
                    }
                }
                else
                {
                    repeat.Reset(messageData.UserId ?? messageData.UserName, messageData.Content);
                }
            }

            string command = messageData.Command;

            if (!CommandsConfig.Instance.Commands.ContainsKey(command))
            {
                command = string.Empty;
            }

            if (string.IsNullOrEmpty(command))
            {
                // match fun
                bool mentionsBot = messageData.MentionsBot(this.Config.Name, Convert.ToUInt64(this.UserId));
                if (CommandsConfig.Instance.TryParseForCommand(messageData.Content, mentionsBot, out _, out string query))
                {
                    messageData.Content = $"{settings.Prefix}{query}";
                }
            }
        }
Example #21
0
        protected async Task <BotResponseData> ProcessMessageAsync(BotMessageData messageData, Settings settings)
        {
            var responses    = new List <string>();
            var responseData = new BotResponseData {
                Responses = responses
            };

            if (this.BotApi != null)
            {
                // if an explicit command is being used, it wins out over any implicitly parsed command
                string query   = messageData.Query;
                string command = messageData.Command;

                string[] contentParts = messageData.Content.Split(new[] { ' ' });

                if (string.IsNullOrEmpty(command))
                {
                    if (messageData.Content.IContains("remind "))
                    {
                        // check for reminders
                        Match timerAtMatch  = Consts.TimerOnRegex.Match(messageData.Content);
                        Match timerAt2Match = Consts.TimerOn2Regex.Match(messageData.Content);
                        if (timerAtMatch.Success && Utilities.TryParseAbsoluteReminder(timerAtMatch, messageData, out query) ||
                            timerAt2Match.Success && Utilities.TryParseAbsoluteReminder(timerAt2Match, messageData, out query))
                        {
                            command = "timer";
                        }
                        else // try relative timers if absolute had no match
                        {
                            Match timerMatch  = Consts.TimerRegex.Match(messageData.Content);
                            Match timer2Match = Consts.Timer2Regex.Match(messageData.Content);

                            if (timerMatch.Success || timer2Match.Success)
                            {
                                Match matchToUse = timerMatch.Success && !timerMatch.Groups["prep"].Value.All(char.IsDigit) ? timerMatch : timer2Match;
                                if (Utilities.TryParseReminder(matchToUse, messageData, out query))
                                {
                                    command = "timer";
                                }
                            }
                        }
                    }

                    if (string.IsNullOrEmpty(command))
                    {
                        if (settings.AutoTitlesEnabled && (CommandsConfig.Instance.AutoTitleMatches.Any(t => messageData.Content.Contains(t)) || Consts.RedditRegex.IsMatch(messageData.Content)))
                        {
                            Match  match      = Consts.HttpRegex.Match(messageData.Content);
                            string matchValue = !string.IsNullOrEmpty(match.Value) ? match.Value : Consts.RedditRegex.Match(messageData.Content).Value;
                            if (!string.IsNullOrEmpty(matchValue))
                            {
                                command = "title";
                                query   = $"{command} {matchValue}";
                            }
                        }
                        else if ((settings.FunResponsesEnabled || IsAuthorOwner(messageData)) && contentParts.Length > 1 && contentParts[1] == "face")
                        {
                            command = "face";
                            query   = $"{command} {contentParts[0]}";
                        }
                    }
                }

                // Ignore if the command is disabled on this server
                if (settings.IsCommandDisabled(CommandsConfig.Instance, command) && !IsAuthorOwner(messageData))
                {
                    return(responseData);
                }

                if (CommandsConfig.Instance.Commands.ContainsKey(command))
                {
                    if (!messageData.RateLimitChecked)
                    {
                        // make sure we're not rate limited
                        var commandKey = command + messageData.Server;

                        if (this.Throttler.IsThrottled(commandKey, ThrottleType.Command))
                        {
                            return(responseData);
                        }

                        this.Throttler.Increment(commandKey, ThrottleType.Command);

                        // if we're now throttled after this increment, return a "rate limited" message
                        if (this.Throttler.IsThrottled(commandKey, ThrottleType.Command))
                        {
                            responses.Add("rate limited try later");
                            return(responseData);
                        }

                        // increment the user/guild throttlers as well
                        this.Throttler.Increment(messageData.UserId, ThrottleType.User);
                        this.Throttler.Increment(messageData.Server, ThrottleType.Guild);

                        messageData.RateLimitChecked = true;
                    }

                    var props = new Dictionary <string, string> {
                        { "command", command.ToLowerInvariant() },
                        { "server", messageData.Server },
                        { "channel", messageData.Channel }
                    };
                    this.TrackEvent("commandProcessed", props);

                    // extra processing on ".title" command
                    if ((command == "title" || command == "t") && messageData.Content.EndsWith(command) && urls.ContainsKey(messageData.Channel))
                    {
                        query += $" {this.urls[messageData.Channel]}";
                    }

                    using (DogStatsd.StartTimer("commandDuration", tags: new[] { $"shard:{this.Shard}", $"command:{command.ToLowerInvariant()}", $"{this.BotType}" }))
                    {
                        messageData.Content = $"{settings.Prefix}{query}";
                        responseData        = await this.BotApi.IssueRequestAsync(messageData);
                    }
                }
            }

            if (responseData.Responses.Count == 0 && responseData.Embed == null)
            {
                string response = null;
                if (messageData.MentionsBot(this.Config.Name, Convert.ToUInt64(this.UserId)))
                {
                    var responseValue = PhrasesConfig.Instance.PartialMentionPhrases.FirstOrDefault(kvp => messageData.Content.IContains(kvp.Key)).Value;
                    if (!string.IsNullOrEmpty(responseValue))
                    {
                        response = PhrasesConfig.Instance.Responses[responseValue].Random();
                    }
                }

                if (response == null && PhrasesConfig.Instance.ExactPhrases.ContainsKey(messageData.Content) && (settings.FunResponsesEnabled && new Random().Next(1, 100) <= settings.FunResponseChance || IsAuthorOwner(messageData)))
                {
                    response = PhrasesConfig.Instance.Responses[PhrasesConfig.Instance.ExactPhrases[messageData.Content]].Random();
                }

                if (response == null)
                {
                    response = settings.CustomCommands.FirstOrDefault(c => c.IsExactMatch && c.Command == messageData.Content || !c.IsExactMatch && messageData.Content.IContains(c.Command))?.Response;
                }

                if (response != null)
                {
                    response = response.Replace("%from%", messageData.UserName);
                    string[] resps = response.Split(new char[] { '|' });
                    responseData.Responses.AddRange(resps);
                }
            }

            return(responseData);
        }
Example #22
0
 protected override Task RespondAsync(BotMessageData messageData, string text)
 {
     this.ircClients[messageData.Server]?.Command("PRIVMSG", messageData.Channel, text);
     return(Task.CompletedTask);
 }
Example #23
0
 protected abstract Task RespondAsync(BotMessageData messageData, string text);
        /// <summary>
        /// Handles responses for messages.
        /// TODO: This method is way too huge.
        /// </summary>
        private async Task HandleMessageReceivedAsync(SocketMessage socketMessage, string reactionType = null, IUser reactionUser = null)
        {
            // Ignore system and our own messages.
            var  message    = socketMessage as SocketUserMessage;
            bool isOutbound = false;

            // replicate to webhook, if configured
            this.CallOutgoingWebhookAsync(message).Forget();

            if (message == null || (isOutbound = message.Author.Id == this.Client.CurrentUser.Id))
            {
                if (isOutbound)
                {
                    if (message.Embeds?.Count > 0)
                    {
                        this.Logger.Log(LogType.Outgoing, $"\tSending [embed content] to {message.Channel.Name}");
                    }
                    else
                    {
                        this.Logger.Log(LogType.Outgoing, $"\tSending to {message.Channel.Name}: {message.Content}");
                    }
                }

                return;
            }

            // Ignore other bots
            if (message.Author.IsBot)
            {
                return;
            }

            // grab the settings for this server
            var botGuildUser = (message.Channel as SocketGuildChannel)?.Guild.CurrentUser;
            var guildUser    = message.Author as IGuildUser;
            var guildId      = (guildUser != null && guildUser.IsWebhook) ? null : guildUser?.GuildId;
            var settings     = SettingsConfig.GetSettings(guildId?.ToString());

            // if it's a globally blocked server, ignore it unless it's the owner
            if (message.Author.Id != this.Config.Discord.OwnerId && guildId != null && this.Config.Discord.BlockedServers.Contains(guildId.Value))
            {
                return;
            }

            // if the user is blocked based on role, return
            var botlessRoleId = guildUser?.Guild?.Roles?.FirstOrDefault(r => r.Name?.ToLowerInvariant() == "botless")?.Id;

            if ((message.Author as IGuildUser)?.RoleIds.Any(r => botlessRoleId != null && r == botlessRoleId.Value) ?? false)
            {
                return;
            }

            // Bail out with help info if it's a PM
            if (message.Channel is IDMChannel && (message.Content.Contains("help") || message.Content.Contains("info") || message.Content.Contains("commands")))
            {
                await this.RespondAsync(message, "Info and commands can be found at: https://ub3r-b0t.com");

                return;
            }

            // check for word censors
            if (botGuildUser?.GuildPermissions.ManageMessages ?? false)
            {
                if (settings.TriggersCensor(message.Content, out string offendingWord))
                {
                    offendingWord = offendingWord != null ? $"`{offendingWord}`" : "*FANCY lanuage filters*";
                    await message.DeleteAsync();

                    var dmChannel = await message.Author.GetOrCreateDMChannelAsync();

                    await dmChannel.SendMessageAsync($"hi uh sorry but your most recent message was tripped up by {offendingWord} and thusly was deleted. complain to management, i'm just the enforcer");

                    return;
                }
            }

            var textChannel = message.Channel as ITextChannel;

            if (botGuildUser != null && !botGuildUser.GetPermissions(textChannel).SendMessages)
            {
                return;
            }

            // special case FAQ channel
            if (message.Channel.Id == this.Config.FaqChannel && message.Content.EndsWith("?") && this.Config.FaqEndpoint != null)
            {
                string content = message.Content.Replace("<@85614143951892480>", "ub3r-b0t");
                var    result  = await this.Config.FaqEndpoint.ToString().WithHeader("Ocp-Apim-Subscription-Key", this.Config.FaqKey).PostJsonAsync(new { question = content });

                if (result.IsSuccessStatusCode)
                {
                    var response = await result.Content.ReadAsStringAsync();

                    var qnaData = JsonConvert.DeserializeObject <QnAMakerData>(response);
                    var score   = Math.Floor(qnaData.Score);
                    var answer  = WebUtility.HtmlDecode(qnaData.Answer);
                    await message.Channel.SendMessageAsync($"{answer} ({score}% match)");
                }
                else
                {
                    await message.Channel.SendMessageAsync("An error occurred while fetching data");
                }

                return;
            }

            string messageContent = message.Content;

            // OCR for fun if requested (patrons only)
            // TODO: need to drive this via config
            // TODO: Need to generalize even further due to more reaction types
            // TODO: oh my god stop writing TODOs and just make the code less awful
            if (!string.IsNullOrEmpty(reactionType))
            {
                string newMessageContent = string.Empty;

                if (reactionType == "💬" || reactionType == "🗨️")
                {
                    newMessageContent = $".quote add \"{messageContent}\" - userid:{message.Author.Id} {message.Author.Username}";
                    await message.AddReactionAsync(new Emoji("💬"));
                }
                else if (string.IsNullOrEmpty(message.Content) && message.Attachments?.FirstOrDefault()?.Url is string attachmentUrl)
                {
                    if (reactionType == "👁")
                    {
                        var result = await this.Config.OcrEndpoint.ToString()
                                     .WithHeader("Ocp-Apim-Subscription-Key", this.Config.VisionKey)
                                     .PostJsonAsync(new { url = attachmentUrl });

                        if (result.IsSuccessStatusCode)
                        {
                            var response = await result.Content.ReadAsStringAsync();

                            var ocrData = JsonConvert.DeserializeObject <OcrData>(response);
                            if (!string.IsNullOrEmpty(ocrData.GetText()))
                            {
                                newMessageContent = ocrData.GetText();
                            }
                        }
                    }
                    else if (reactionType == "🖼")
                    {
                        var analyzeResult = await this.Config.AnalyzeEndpoint.ToString()
                                            .WithHeader("Ocp-Apim-Subscription-Key", this.Config.VisionKey)
                                            .PostJsonAsync(new { url = attachmentUrl });

                        if (analyzeResult.IsSuccessStatusCode)
                        {
                            var response = await analyzeResult.Content.ReadAsStringAsync();

                            var analyzeData = JsonConvert.DeserializeObject <AnalyzeImageData>(response);
                            if (analyzeData.Description.Tags.Contains("ball"))
                            {
                                newMessageContent = ".8ball foo";
                            }
                            else if (analyzeData.Description.Tags.Contains("outdoor"))
                            {
                                newMessageContent = ".fw";
                            }
                        }
                    }
                }

                messageContent = newMessageContent ?? messageContent;
            }

            // If it's a command, match that before anything else.
            string query         = string.Empty;
            bool   hasBotMention = message.MentionedUsers.Any(u => u.Id == this.Client.CurrentUser.Id);

            int argPos = 0;

            if (message.HasMentionPrefix(this.Client.CurrentUser, ref argPos))
            {
                query = messageContent.Substring(argPos);
            }
            else if (messageContent.StartsWith(settings.Prefix))
            {
                query = messageContent.Substring(settings.Prefix.Length);
            }

            var messageData = BotMessageData.Create(message, query, settings);

            messageData.Content = messageContent;
            await this.PreProcessMessage(messageData, settings);

            string command = messageData.Command;

            if (message.Attachments.FirstOrDefault() is Attachment attachment)
            {
                imageUrls[messageData.Channel] = attachment;
            }

            // if it's a blocked command, bail
            if (settings.IsCommandDisabled(CommandsConfig.Instance, command) && !IsAuthorOwner(message))
            {
                return;
            }

            // Check discord specific commands prior to general ones.
            if (discordCommands.Commands.ContainsKey(command))
            {
                var response = await discordCommands.Commands[command].Invoke(message).ConfigureAwait(false);
                if (response != null)
                {
                    if (response.Attachment != null)
                    {
                        var sentMessage = await message.Channel.SendFileAsync(response.Attachment.Stream, response.Attachment.Name, response.Text);

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                    else if (!string.IsNullOrEmpty(response.Text) || response.Embed != null)
                    {
                        var sentMessage = await this.RespondAsync(message, response.Text, response.Embed);

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                }
            }
            else
            {
                IDisposable typingState = null;
                if (CommandsConfig.Instance.Commands.ContainsKey(command))
                {
                    // possible bug with typing state
                    Console.WriteLine($"typing triggered by {command}");
                    typingState = message.Channel.EnterTypingState();
                }

                if (messageData.Command == "quote" && reactionUser != null)
                {
                    messageData.UserName = reactionUser.Username;
                }

                try
                {
                    BotResponseData responseData = await this.ProcessMessageAsync(messageData, settings);

                    if (responseData.Embed != null)
                    {
                        var sentMessage = await this.RespondAsync(message, string.Empty, responseData.Embed.CreateEmbedBuilder());

                        this.botResponsesCache.Add(message.Id, sentMessage);
                    }
                    else
                    {
                        foreach (string response in responseData.Responses)
                        {
                            if (!string.IsNullOrEmpty(response))
                            {
                                // if sending a multi part message, skip the edit optimization.
                                var sentMessage = await this.RespondAsync(message, response, embedResponse : null, bypassEdit : responseData.Responses.Count > 1);

                                this.botResponsesCache.Add(message.Id, sentMessage);
                            }
                        }
                    }
                }
                finally
                {
                    typingState?.Dispose();
                }
            }
        }
Example #25
0
 // Whether or not the message author is the bot owner (will only return true in Discord scenarios).
 private bool IsAuthorOwner(BotMessageData messageData)
 {
     return(!string.IsNullOrEmpty(messageData.UserId) && messageData.UserId == this.Config.Discord?.OwnerId.ToString());
 }
Example #26
0
        public static bool TryParseReminder(Match timerMatch, BotMessageData messageData, out string query)
        {
            query = string.Empty;

            string toMatch     = timerMatch.Groups["target"].ToString().Trim();
            string to          = toMatch == "me" ? messageData.UserName : toMatch;
            string req         = to == messageData.UserName ? string.Empty : messageData.UserName;
            string durationStr = string.Empty;
            long   duration    = 0;

            GroupCollection matchGroups = timerMatch.Groups;
            string          reason      = matchGroups["reason"].ToString();

            if (matchGroups["years"].Success)
            {
                string yearString = timerMatch.Groups["years"].ToString();
                if (int.TryParse(yearString.Remove(yearString.Length - 5, 5), out int yearValue))
                {
                    duration   += yearValue * ONEYEAR;
                    durationStr = $"{yearValue}y";
                }
            }

            if (matchGroups["weeks"].Success)
            {
                string weekString = matchGroups["weeks"].ToString();
                if (int.TryParse(weekString.Remove(weekString.Length - 5, 5), out int weekValue))
                {
                    duration    += weekValue * ONEWEEK;
                    durationStr += $"{weekValue}w";
                }
            }

            if (matchGroups["days"].Success)
            {
                string dayString = matchGroups["days"].ToString();
                if (int.TryParse(dayString.Remove(dayString.Length - 4, 4), out int dayValue))
                {
                    duration    += dayValue * ONEDAY;
                    durationStr += $"{dayValue}d";
                }
            }

            if (matchGroups["hours"].Success)
            {
                string hourString = matchGroups["hours"].ToString();
                if (int.TryParse(hourString.Remove(hourString.Length - 5, 5), out int hourValue))
                {
                    duration    += hourValue * ONEHOUR;
                    durationStr += $"{hourValue}h";
                }
            }

            if (matchGroups["minutes"].Success)
            {
                string minuteString = matchGroups["minutes"].ToString();
                if (int.TryParse(minuteString.Remove(minuteString.Length - 7, 7), out int minuteValue))
                {
                    duration    += minuteValue * 60;
                    durationStr += $"{minuteValue}m";
                }
            }

            if (matchGroups["seconds"].Success)
            {
                string secongString = matchGroups["seconds"].ToString();
                if (int.TryParse(secongString.Remove(secongString.Length - 8, 8), out int secondValue))
                {
                    duration    += secondValue;
                    durationStr += $"{secondValue}s";
                }
            }

            if (duration < 10 || duration > TOOLONG)
            {
                return(false);
            }

            query = $"timer for:\"{to}\" {durationStr} {reason}";

            return(true);
        }