/// <summary> /// The _client_ reaction added. /// </summary> /// <param name="messageCacheable"> /// The message Cache-able. /// </param> /// <param name="channel"> /// The channel. /// </param> /// <param name="reaction"> /// The reaction. /// </param> /// <returns> /// The <see cref="Task"/>. /// </returns> internal async Task ReactionAddedAsync(Cacheable <IUserMessage, ulong> messageCacheable, ISocketMessageChannel channel, SocketReaction reaction) { if (prefixOverride) { return; } LogHandler.LogMessage("Reaction Detected", LogSeverity.Verbose); IUserMessage message = messageCacheable.Value ?? await channel.GetMessageAsync(reaction.MessageId) as IUserMessage; if (message == null) { return; } if (reaction.User.Value?.IsBot == true || (string.IsNullOrWhiteSpace(message.Content) && !message.Embeds.Any())) { return; } try { var translateAction = Task.Run( async() => { var guildId = (channel as SocketGuildChannel).Guild.Id; var translationSetup = _Translate.GetSetup(guildId); if (translationSetup == null) { translationSetup = Provider.GetRequiredService <DatabaseHandler>().Execute <GuildModel>(DatabaseHandler.Operation.LOAD, null, guildId.ToString())?.Settings.Translate; if (translationSetup == null) { return; } await _Translate.UpdateSetupAsync(guildId, translationSetup); } if (!translationSetup.EasyTranslate) { return; } // Check custom matches first var languageType = translationSetup.CustomPairs.FirstOrDefault(x => x.EmoteMatches.Any(val => val == reaction.Emote.Name)); if (languageType == null) { // If no custom matches, check default matches languageType = LanguageMap.DefaultMap.FirstOrDefault(x => x.EmoteMatches.Any(val => val == reaction.Emote.Name)); if (languageType == null) { LogHandler.LogMessage("Ignored EasyTranslate Reaction, No Emote Configured", LogSeverity.Verbose); return; } } if (translated.Any(x => x.Key == reaction.MessageId && x.Value.Contains(languageType.Language))) { LogHandler.LogMessage("Ignored EasyTranslate Reaction", LogSeverity.Verbose); return; } var embed = await TranslationMethods.TranslateFullMessageAsync(languageType.Language, message, channel as IGuildChannel, reaction); if (translationSetup.DMTranslations) { try { await reaction.User.Value.SendMessageAsync(embed.Item1 ?? "", false, embed.Item2); } catch { await reaction.Channel.SendMessageAsync($"Unable to send DM Translation to {reaction.User.Value?.Mention}"); } } else { try { await channel.SendMessageAsync(embed.Item1 ?? "", false, embed.Item2); } catch (Exception e) { Console.WriteLine(e.ToString()); } var match = translated.FirstOrDefault(x => x.Key == reaction.MessageId); if (match.Value == null) { translated.TryAdd(reaction.MessageId, new List <LanguageMap.LanguageCode> { languageType.Language }); } else { match.Value.Add(languageType.Language); } } if (embed.Item3) { LogHandler.LogMessage(guildId, reaction.Channel.Name, reaction.UserId, $"Translated Message to {languageType.Language}: {message.Content}"); } }); } catch (Exception e) { LogHandler.LogMessage(e.ToString(), LogSeverity.Error); } }
/// <summary> /// This event is triggered every time the a user sends a Message in a channel, dm etc. that the bot has access to view. /// </summary> /// <param name="socketMessage"> /// The socket Message. /// </param> /// <returns> /// The <see cref="Task"/>. /// </returns> internal async Task MessageReceivedAsync(SocketMessage socketMessage) { if (!(socketMessage is SocketUserMessage Message) || Message.Channel is IDMChannel) { return; } if (string.IsNullOrWhiteSpace(Message.Content) || Message.Content.Length < 3) { return; } if (Message.Author.IsBot || Message.Author.IsWebhook) { // Filter out all bot messages from triggering commands. return; } // Ensure that blacklisted users/guilds are not allowed to run commands if (CheckBlacklist(Message.Author.Id, (Message.Channel as IGuildChannel).Guild.Id)) { return; } await _ChannelHelper.DoMediaChannelAsync(Message); var argPos = 0; var isPrefixed = true; if (prefixOverride) { var config = JsonConvert.DeserializeObject <DatabaseObject>(File.ReadAllText("setup/DBConfig.json")); if (config.PrefixOverride != null) { if (!Message.HasStringPrefix(config.PrefixOverride, ref argPos)) { isPrefixed = false; } } else { LogHandler.LogMessage("Message Handler is being returned as the bot is in prefix override mode and you haven't specified a custom prefix in DBConfig.json", LogSeverity.Warning); isPrefixed = false; } } else { // Filter out all messages that don't start with our Bot PrefixSetup, bot mention or server specific PrefixSetup. if (!(Message.HasMentionPrefix(Client.CurrentUser, ref argPos) || Message.HasStringPrefix(PrefixService.GetPrefix((Message.Channel as IGuildChannel)?.Guild?.Id ?? 0), ref argPos))) { isPrefixed = false; } } // run level check and auto-message channel check if the current message is not a command prefixed message if (!isPrefixed) { var messageTask = Task.Run( async() => { LogHandler.LogMessage($"G: {(Message.Channel as IGuildChannel)?.Guild?.Id} | C: {Message.Channel?.Id} | U: {Message.Author?.Id.ToString()} | M: {Message?.Content.Left(100)}", LogSeverity.Verbose); await _LevelHelper.DoLevelsAsync(Message); await _ChannelHelper.DoAutoMessageAsync(Message); }); await StatHelper.LogMessageAsync(Message); return; } // Here we attempt to execute a command based on the user Message var commandTask = Task.Run(async() => { var context = new Context(Client, Message, Provider); var result = await CommandService.ExecuteAsync(context, argPos, Provider, MultiMatchHandling.Best); // Generate an error Message for users if a command is unsuccessful if (!result.IsSuccess) { await CmdErrorAsync(context, result, argPos); } else { var search = CommandService.Search(context, argPos); var cmd = search.Commands.FirstOrDefault(); StatHelper.LogCommand(cmd.Command, Message); if (Config.LogCommandUsages) { LogHandler.LogMessage(context); } } }); }
/// <summary> /// Triggers when a shard is ready /// </summary> /// <param name="socketClient"> /// The socketClient. /// </param> /// <returns> /// The <see cref="Task"/>. /// </returns> internal async Task ShardReadyAsync(DiscordSocketClient socketClient) { await socketClient.SetActivityAsync(new Game($"Shard: {socketClient.ShardId}", ActivityType.Watching)); if (guildCheck) { if (Client.Shards.All(x => x.Guilds.Any())) { if (prefixOverride) { LogHandler.LogMessage($"Bot is in Prefix Override Mode! Current Prefix is: {DatabaseHandler.Settings.PrefixOverride}", LogSeverity.Warning); } if (await DBLApi.InitializeAsync()) { LogHandler.LogMessage("Discord Bots List API Initialized."); } else { LogHandler.LogMessage("Discord Bots List API Not Initialized.", LogSeverity.Warning); } Provider.GetRequiredService <TimerService>().Restart(); _ = Task.Run( () => { Limits.Initialize(); Waits.Initialize(); var handler = Provider.GetRequiredService <DatabaseHandler>(); if (!DatabaseHandler.Settings.DenyConfigDeletion) { // Returns all stored guild models var guildIds = Client.Guilds.Select(g => g.Id).ToList(); var missingList = handler.Query <GuildModel>().Where(g => !g.Settings.Config.SaveGuildModel && g.ID != 0).Select(x => x.ID).Where(x => !guildIds.Contains(x)).ToList(); foreach (var id in missingList) { handler.Execute <GuildModel>(DatabaseHandler.Operation.DELETE, id: id.ToString()); handler.Execute <GuildModel>(DatabaseHandler.Operation.DELETE, id: $"{id}-Tags"); handler.Execute <GuildModel>(DatabaseHandler.Operation.DELETE, id: $"{id}-Channels"); handler.Execute <GuildModel>(DatabaseHandler.Operation.DELETE, id: $"{id}-Levels"); } /* * // Only to be used if migrating from older database where all items were stored in the same guildModel * var convert = Provider.GetRequiredService<GuildModelToServices>(); * foreach (var guildId in guildIds) * { * var model = handler.Execute<GuildModel>(DatabaseHandler.Operation.LOAD, null, guildId); * if (model != null) * { * convert.SplitModelAsync(model); * } * } */ } else { LogHandler.LogMessage("Server configs for servers which do not contain the bot will be preserved!", LogSeverity.Warning); } }); // Ensure that this is only run once as the bot initially connects. guildCheck = false; } else { // This will check to ensure that all our servers are initialized, whilst also allowing the bot to continue starting _ = Task.Run( () => { var handler = Provider.GetRequiredService <DatabaseHandler>(); // This will load all guild models and retrieve their IDs var Servers = handler.Query <GuildModel>(); var ids = Servers.Select(s => s.ID).ToList(); // Now if the bots server list contains a guild but 'Servers' does not, we create a new object for the guild foreach (var Guild in socketClient.Guilds.Select(x => x.Id)) { if (!ids.Contains(Guild)) { handler.Execute <GuildModel>(DatabaseHandler.Operation.CREATE, new GuildModel(Guild), Guild); } } }); } } LogHandler.LogMessage($"Shard: {socketClient.ShardId} Ready"); if (!hideInvite) { LogHandler.LogMessage($"Invite: https://discordapp.com/oauth2/authorize?client_id={Client.CurrentUser.Id}&scope=bot&permissions=2146958591"); hideInvite = true; } }
/// <summary> /// This logs discord messages to our LogHandler /// </summary> /// <param name="message"> /// The Message. /// </param> /// <returns> /// The <see cref="Task"/>. /// </returns> internal Task LogAsync(LogMessage message) { return(Task.Run(() => LogHandler.LogMessage(message.Message, message.Severity))); }
/// <summary> /// Initializes the database for use /// </summary> /// <returns> /// The Document Store /// </returns> public Task <IDocumentStore> InitializeAsync() { // Ensure that the bots database settings are setup, if not prompt to enter details if (!File.Exists("setup/DBConfig.json")) { LogHandler.LogMessage("Please enter details about your bot and database configuration. NOTE: You can hit enter for a default value. "); LogHandler.LogMessage("Enter the database Name: (ie. MyRavenDatabase) DEFAULT: RavenBOT"); var databaseName = Console.ReadLine(); if (string.IsNullOrEmpty(databaseName)) { databaseName = "RavenBOT"; } LogHandler.LogMessage("Enter the database URL: (typically http://127.0.0.1:8080 if hosting locally) DEFAULT: http://127.0.0.1:8080"); var databaseUrl = Console.ReadLine(); if (string.IsNullOrEmpty(databaseUrl)) { databaseUrl = "http://127.0.0.1:8080"; } File.WriteAllText("setup/DBConfig.json", JsonConvert.SerializeObject( new DatabaseObject { Name = databaseName, Urls = new List <string> { databaseUrl } }, Formatting.Indented), Encoding.UTF8); Settings = JsonConvert.DeserializeObject <DatabaseObject>(File.ReadAllText("setup/DBConfig.json")); } else { Settings = JsonConvert.DeserializeObject <DatabaseObject>(File.ReadAllText("setup/DBConfig.json")); LogHandler.LogMessage("Connecting to Server\n" + $"=> URL: \n{string.Join("\n", Settings.Urls)}\n" + $"=> Name: {Settings.Name}"); } if (Settings.Urls.Count == 0) { throw new Exception("No database urls have been detected in config, please add one"); } // This initializes the document store, and ensures that RavenDB is working properly Store = new Lazy <IDocumentStore>(() => new DocumentStore { Database = Settings.Name, Urls = Settings.Urls.ToArray() }.Initialize(), true).Value; if (Store == null) { LogHandler.LogMessage("Failed to build document store.", LogSeverity.Critical); } // This creates the database if (Store.Maintenance.Server.Send(new GetDatabaseNamesOperation(0, 20)).All(x => x != Settings.Name)) { Store.Maintenance.Server.Send(new CreateDatabaseOperation(new DatabaseRecord(Settings.Name))); LogHandler.LogMessage($"Created Database => {Settings.Name}"); } // To ensure the backup operation is functioning and backing up to our bots directory we update the backup operation on each boot of the bot try { var record = Store.Maintenance.ForDatabase(Settings.Name).Server.Send(new GetDatabaseRecordOperation(Settings.Name)); var backup = record.PeriodicBackups.FirstOrDefault(x => x.Name == "Backup"); if (backup == null) { var newbackup = new PeriodicBackupConfiguration { Name = "Backup", BackupType = BackupType.Backup, FullBackupFrequency = Settings.FullBackup, IncrementalBackupFrequency = Settings.IncrementalBackup, LocalSettings = new LocalSettings { FolderPath = Settings.BackupFolder } }; Store.Maintenance.ForDatabase(Settings.Name).Send(new UpdatePeriodicBackupOperation(newbackup)); } else { // In the case that we already have a backup operation setup, ensure that we update the backup location accordingly backup.LocalSettings = new LocalSettings { FolderPath = Settings.BackupFolder }; Store.Maintenance.ForDatabase(Settings.Name).Send(new UpdatePeriodicBackupOperation(backup)); } } catch { LogHandler.LogMessage("RavenDB: Failed to set Backup operation. Backups may not be saved", LogSeverity.Warning); } // Prompt the user to set up the bots configuration. if (Settings.IsConfigCreated == false) { var configModel = new ConfigModel(); LogHandler.LogMessage("Enter bots token: (You can get this from https://discordapp.com/developers/applications/me)"); var token = Console.ReadLine(); if (string.IsNullOrWhiteSpace(token)) { throw new Exception("You must supply a token for this bot to operate."); } LogHandler.LogMessage("Enter bots prefix: (This will be used to initiate a command, ie. +say or +help) DEFAULT: +"); var prefix = Console.ReadLine(); if (string.IsNullOrEmpty(prefix)) { prefix = "+"; } configModel.Token = token; configModel.Prefix = prefix; // This inserts the config object into the database and writes the DatabaseConfig to file. Execute <ConfigModel>(Operation.CREATE, configModel, "Config"); Settings.IsConfigCreated = true; File.WriteAllText("setup/DBConfig.json", JsonConvert.SerializeObject(Settings, Formatting.Indented)); } LogHandler.PrintApplicationInformation(Settings, Execute <ConfigModel>(Operation.LOAD, null, "Config")); // Note the logger has to be updated/re-set after we set the database up otherwise there will be a null reference when trying to log initially if (Settings.LogToDatabase) { LogHandler.Log = new LoggerConfiguration().MinimumLevel.Is(LogHandler.DiscordLogToEventLevel(Settings.LogSeverity)).WriteTo.Console().WriteTo.RavenDB( Store, defaultDatabase: Settings.Name, expiration: TimeSpan.FromDays(2)).CreateLogger(); } else { LogHandler.Log = new LoggerConfiguration().MinimumLevel.Is(LogHandler.DiscordLogToEventLevel(Settings.LogSeverity)).CreateLogger(); } return(Task.FromResult(Store)); }