diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs new file mode 100644 index 00000000..b50c9908 --- /dev/null +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Discord.Commands; +using NadekoBot.Services; +using NadekoBot.Attributes; +using NadekoBot.Services.Database; +using System.Collections.Concurrent; +using NadekoBot.Services.Database.Models; +using Discord; +using NadekoBot.Extensions; + +namespace NadekoBot.Modules.CustomReactions +{ + [NadekoModule("CustomReactions",".")] + public class CustomReactions : DiscordModule + { + public static HashSet GlobalReactions { get; } = new HashSet(); + public static ConcurrentDictionary> AllReactions { get; } = new ConcurrentDictionary>(); + static CustomReactions() + { + using (var uow = DbHandler.UnitOfWork()) + { + var list = uow.CustomReactions.GetList(); + AllReactions = new ConcurrentDictionary>(list.Where(g => g.GuildId != null).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => new HashSet(g))); + GlobalReactions = new HashSet(list.Where(g => g.GuildId == null)); + } + } + public CustomReactions(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + } + + [NadekoCommand, Usage, Description, Aliases] + [RequirePermission(GuildPermission.Administrator)] + public async Task AddCustReact(IUserMessage imsg, string key, [Remainder] string message) + { + var channel = imsg.Channel as ITextChannel; + if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key)) + return; + + if ((channel == null && NadekoBot.Credentials.IsOwner(imsg.Author)) || (channel != null && ((IGuildUser)imsg.Author).GuildPermissions.Administrator)) + { + try { await channel.SendMessageAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { } + return; + } + + var cr = new CustomReaction() + { + GuildId = channel?.Guild.Id, + IsRegex = false, + Trigger = key.ToLowerInvariant(), + Response = message, + }; + + using (var uow = DbHandler.UnitOfWork()) + { + uow.CustomReactions.Add(cr); + + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (channel == null) + { + GlobalReactions.Add(cr); + } + else + { + var reactions = AllReactions.GetOrAdd(channel.Guild.Id, new HashSet()); + reactions.Add(cr); + } + + await channel.SendMessageAsync($"`Added new custom reaction:`\n\t`Trigger:` {key}\n\t`Response:` {message}").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ListCustReact(IUserMessage imsg,int page = 1) + { + var channel = imsg.Channel as ITextChannel; + + if (page < 1 || page > 1000) + return; + HashSet customReactions; + if (channel == null) + customReactions = GlobalReactions; + else + customReactions = AllReactions.GetOrAdd(channel.Guild.Id, new HashSet()); + + if (customReactions == null || !customReactions.Any()) + await channel.SendMessageAsync("`No custom reactions found`").ConfigureAwait(false); + else + await channel.SendTableAsync(customReactions.OrderBy(cr => cr.Trigger).Skip((page - 1) * 10).Take(10), c => c.ToString()) + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task DelCustReact(IUserMessage imsg, int id) + { + var channel = imsg.Channel as ITextChannel; + + if ((channel == null && NadekoBot.Credentials.IsOwner(imsg.Author)) || (channel != null && ((IGuildUser)imsg.Author).GuildPermissions.Administrator)) + { + try { await channel.SendMessageAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { } + return; + } + + var success = false; + CustomReaction toDelete; + using (var uow = DbHandler.UnitOfWork()) + { + toDelete = uow.CustomReactions.Get(id); + if (toDelete == null) //not found + return; + + if (toDelete.GuildId == null && channel == null) + { + uow.CustomReactions.Remove(toDelete); + success = true; + } + else if (toDelete.GuildId != null && channel.Guild.Id == toDelete.GuildId) + { + uow.CustomReactions.Remove(toDelete); + success = true; + } + if(success) + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (success) + await channel.SendMessageAsync("**Successfully deleted custom reaction** " + toDelete.ToString()).ConfigureAwait(false); + else + await channel.SendMessageAsync("Failed to find that custom reaction.").ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs index ae8c725e..164a0b79 100644 --- a/src/NadekoBot/Modules/Help/Help.cs +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -53,30 +53,6 @@ namespace NadekoBot.Modules.Help await umsg.Channel.SendMessageAsync("`List of modules:` ```xl\n• " + string.Join("\n• ", _commands.Modules.Select(m => m.Name)) + $"\n``` `Type \"-commands module_name\" to get a list of commands in that module.`") .ConfigureAwait(false); - - await RunWithTypingIntheBackgorund(async () => - { - await Task.Delay(100000); - }, umsg); - } - - private async Task RunWithTypingIntheBackgorund(Func someFUnc, IUserMessage ctx) - { - var cancelSource = new CancellationTokenSource(); - var cancelToken = cancelSource.Token; - var t = Task.Run(async () => - { - while (!cancelToken.IsCancellationRequested) - { - await Task.Delay(10000); - await ctx.Channel.TriggerTypingAsync(); - } - }, cancelToken); - try - { - await someFUnc(); - } - finally { cancelSource.Cancel(); } } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Services/Database/IUnitOfWork.cs b/src/NadekoBot/Services/Database/IUnitOfWork.cs index 14719461..68d99b0e 100644 --- a/src/NadekoBot/Services/Database/IUnitOfWork.cs +++ b/src/NadekoBot/Services/Database/IUnitOfWork.cs @@ -9,6 +9,8 @@ namespace NadekoBot.Services.Database { public interface IUnitOfWork : IDisposable { + NadekoContext _context { get; } + IQuoteRepository Quotes { get; } IGuildConfigRepository GuildConfigs { get; } IDonatorsRepository Donators { get; } @@ -18,6 +20,10 @@ namespace NadekoBot.Services.Database IBotConfigRepository BotConfig { get; } IRepeaterRepository Repeaters { get; } IUnitConverterRepository ConverterUnits { get; } + ICustomReactionRepository CustomReactions { get; } + ICurrencyRepository Currency { get; } + ITypingArticlesRepository TypingArticles { get; } + IMusicPlaylistRepository MusicPlaylists { get; } int Complete(); Task CompleteAsync(); diff --git a/src/NadekoBot/Services/Database/Models/CustomReaction.cs b/src/NadekoBot/Services/Database/Models/CustomReaction.cs new file mode 100644 index 00000000..180b85b9 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/CustomReaction.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class CustomReaction : DbEntity + { + public ulong? GuildId { get; set; } + [NotMapped] + public Regex Regex { get; set; } + public string Response { get; set; } + public string Trigger { get; set; } + public bool IsRegex { get; set; } + public override string ToString() => $"Id: {Id}\nTrigger: {Trigger}\n Regex: {IsRegex}"; + } + + public class ReactionResponse : DbEntity + { + public bool OwnerOnly { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index 2661bdf8..5df5512a 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -23,6 +23,7 @@ namespace NadekoBot.Services.Database public DbSet ConversionUnits { get; set; } public DbSet TypingArticles { get; set; } public DbSet MusicPlaylists { get; set; } + public DbSet CustomReactions { get; set; } //logging public DbSet LogSettings { get; set; } @@ -60,7 +61,8 @@ namespace NadekoBot.Services.Database new ModulePrefix() { ModuleName = "Gambling", Prefix = "$" }, new ModulePrefix() { ModuleName = "Permissions", Prefix = ";" }, new ModulePrefix() { ModuleName = "Pokemon", Prefix = ">" }, - new ModulePrefix() { ModuleName = "Utility", Prefix = "." } + new ModulePrefix() { ModuleName = "Utility", Prefix = "." }, + new ModulePrefix() { ModuleName = "CustomReactions", Prefix = "." } }); bc.RaceAnimals.AddRange(new HashSet { diff --git a/src/NadekoBot/Services/Database/Repositories/ICustomReactionRepository.cs b/src/NadekoBot/Services/Database/Repositories/ICustomReactionRepository.cs new file mode 100644 index 00000000..200a1a7c --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/ICustomReactionRepository.cs @@ -0,0 +1,14 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface ICustomReactionRepository : IRepository + { + List GetList(); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/CustomReactionRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/CustomReactionRepository.cs new file mode 100644 index 00000000..d0cb46a7 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/CustomReactionRepository.cs @@ -0,0 +1,22 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class CustomReactionsRepository : Repository, ICustomReactionRepository + { + public CustomReactionsRepository(DbContext context) : base(context) + { + } + + public List GetList() + { + return _set.Include(x => x.Responses).ToList(); + } + } +} diff --git a/src/NadekoBot/Services/Database/UnitOfWork.cs b/src/NadekoBot/Services/Database/UnitOfWork.cs index 33f5d1c1..7079b9ee 100644 --- a/src/NadekoBot/Services/Database/UnitOfWork.cs +++ b/src/NadekoBot/Services/Database/UnitOfWork.cs @@ -10,7 +10,7 @@ namespace NadekoBot.Services.Database { public class UnitOfWork : IUnitOfWork { - public NadekoContext _context; + public NadekoContext _context { get; } private IQuoteRepository _quotes; public IQuoteRepository Quotes => _quotes ?? (_quotes = new QuoteRepository(_context)); @@ -48,6 +48,9 @@ namespace NadekoBot.Services.Database private IMusicPlaylistRepository _musicPlaylists; public IMusicPlaylistRepository MusicPlaylists => _musicPlaylists ?? (_musicPlaylists = new MusicPlaylistRepository(_context)); + private ICustomReactionRepository _customReactions; + public ICustomReactionRepository CustomReactions => _customReactions ?? (_customReactions = new CustomReactionsRepository(_context)); + public UnitOfWork(NadekoContext context) { _context = context; diff --git a/src/NadekoBot/Services/DbHandler.cs b/src/NadekoBot/Services/DbHandler.cs index 76437166..ee1833dd 100644 --- a/src/NadekoBot/Services/DbHandler.cs +++ b/src/NadekoBot/Services/DbHandler.cs @@ -36,10 +36,10 @@ namespace NadekoBot.Services public NadekoContext GetDbContext() => Activator.CreateInstance(dbType) as NadekoContext; - public UnitOfWork GetUnitOfWork() => + public IUnitOfWork GetUnitOfWork() => new UnitOfWork(GetDbContext()); - public static UnitOfWork UnitOfWork() => + public static IUnitOfWork UnitOfWork() => DbHandler.Instance.GetUnitOfWork(); } } diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index 0d420528..5a540c5b 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -49,7 +49,11 @@ namespace NadekoBot.Services.Impl Db = new DB(cm.Db.Type, cm.Db.ConnectionString); } else - _log.Fatal("credentials.json is missing. Failed to start."); + { + File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); + _log.Fatal($"credentials.json is missing. Failed to start. Example written to {Path.GetFullPath("./credentials_example.json")}"); + throw new FileNotFoundException(); + } } private class CredentialsModel