Packages can be loaded/unloaded. IUnloadableService interface added whose method Unload, if service implements it, will be called when the module is unloaded.
This commit is contained in:
64
NadekoBot.Core/Modules/Administration/ModuleCommands.cs
Normal file
64
NadekoBot.Core/Modules/Administration/ModuleCommands.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Core.Modules.Administration.Services;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
public partial class Administration
|
||||
{
|
||||
[Group]
|
||||
public class PackagesCommands : NadekoSubmodule<PackagesService>
|
||||
{
|
||||
private readonly NadekoBot _bot;
|
||||
|
||||
public PackagesCommands(NadekoBot bot)
|
||||
{
|
||||
_bot = bot;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task PackageList()
|
||||
{
|
||||
_service.ReloadAvailablePackages();
|
||||
await Context.Channel.SendConfirmAsync(string.Join("\n", _service.Packages));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task PackageUnload(string name)
|
||||
{
|
||||
if (name.Contains(":") || name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
|
||||
return;
|
||||
name = name.ToTitleCase();
|
||||
var package = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory,
|
||||
"modules",
|
||||
$"NadekoBot.Modules.{name}",
|
||||
$"NadekoBot.Modules.{name}.dll"));
|
||||
|
||||
await _bot.UnloadPackage(name).ConfigureAwait(false);
|
||||
await ReplyAsync(":ok:");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task PackageLoad(string name)
|
||||
{
|
||||
if (name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
|
||||
return;
|
||||
name = name.ToTitleCase();
|
||||
|
||||
await _bot.LoadPackage(name);
|
||||
await ReplyAsync(":ok:");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[OwnerOnly]
|
||||
public async Task StartupCommandAdd([Remainder] string cmdText)
|
||||
{
|
||||
//todo don't let .die be a startup command
|
||||
var guser = ((IGuildUser)Context.User);
|
||||
var cmd = new StartupCommand()
|
||||
{
|
||||
|
@ -0,0 +1,31 @@
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NadekoBot.Core.Modules.Administration.Services
|
||||
{
|
||||
public class PackagesService : INService
|
||||
{
|
||||
public IEnumerable<string> Packages { get; private set; }
|
||||
|
||||
public PackagesService()
|
||||
{
|
||||
ReloadAvailablePackages();
|
||||
}
|
||||
|
||||
public void ReloadAvailablePackages()
|
||||
{
|
||||
Packages = Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "modules\\"), "NadekoBot.Modules.*", SearchOption.AllDirectories)
|
||||
.SelectMany(x => Directory.GetFiles(x, "NadekoBot.Modules.*.dll"))
|
||||
.Select(x => Path.GetFileNameWithoutExtension(x))
|
||||
.Select(x =>
|
||||
{
|
||||
var m = Regex.Match(x, @"NadekoBot\.Modules\.(?<name>.*)");
|
||||
return m.Groups["name"].Value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
504
NadekoBot.Core/Modules/CustomReactions/CustomReactions.cs
Normal file
504
NadekoBot.Core/Modules/CustomReactions/CustomReactions.cs
Normal file
@ -0,0 +1,504 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Discord;
|
||||
using NadekoBot.Extensions;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.CustomReactions.Services;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions
|
||||
{
|
||||
public class CustomReactions : NadekoTopLevelModule<CustomReactionsService>
|
||||
{
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public CustomReactions(IBotCredentials creds, DbService db,
|
||||
DiscordSocketClient client)
|
||||
{
|
||||
_creds = creds;
|
||||
_db = db;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task AddCustReact(string key, [Remainder] string message)
|
||||
{
|
||||
var channel = Context.Channel as ITextChannel;
|
||||
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key))
|
||||
return;
|
||||
|
||||
key = key.ToLowerInvariant();
|
||||
|
||||
if ((channel == null && !_creds.IsOwner(Context.User)) || (channel != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
|
||||
{
|
||||
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var cr = new CustomReaction()
|
||||
{
|
||||
GuildId = channel?.Guild.Id,
|
||||
IsRegex = false,
|
||||
Trigger = key,
|
||||
Response = message,
|
||||
};
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.CustomReactions.Add(cr);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (channel == null)
|
||||
{
|
||||
await _service.AddGcr(cr).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_service.GuildReactions.AddOrUpdate(Context.Guild.Id,
|
||||
new CustomReaction[] { cr },
|
||||
(k, old) =>
|
||||
{
|
||||
Array.Resize(ref old, old.Length + 1);
|
||||
old[old.Length - 1] = cr;
|
||||
return old;
|
||||
});
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("new_cust_react"))
|
||||
.WithDescription($"#{cr.Id}")
|
||||
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(key))
|
||||
.AddField(efb => efb.WithName(GetText("response")).WithValue(message.Length > 1024 ? GetText("redacted_too_long") : message))
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task EditCustReact(int id, [Remainder] string message)
|
||||
{
|
||||
var channel = Context.Channel as ITextChannel;
|
||||
if (string.IsNullOrWhiteSpace(message) || id < 0)
|
||||
return;
|
||||
|
||||
if ((channel == null && !_creds.IsOwner(Context.User)) || (channel != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
|
||||
{
|
||||
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomReaction cr;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
cr = uow.CustomReactions.Get(id);
|
||||
|
||||
if (cr != null)
|
||||
{
|
||||
cr.Response = message;
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (cr != null)
|
||||
{
|
||||
if (channel == null)
|
||||
{
|
||||
await _service.EditGcr(id, message).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_service.GuildReactions.TryGetValue(Context.Guild.Id, out var crs))
|
||||
{
|
||||
var oldCr = crs.FirstOrDefault(x => x.Id == id);
|
||||
if (oldCr != null)
|
||||
oldCr.Response = message;
|
||||
}
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("edited_cust_react"))
|
||||
.WithDescription($"#{cr.Id}")
|
||||
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(cr.Trigger))
|
||||
.AddField(efb => efb.WithName(GetText("response")).WithValue(message.Length > 1024 ? GetText("redacted_too_long") : message))
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("edit_fail").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task ListCustReact(int page = 1)
|
||||
{
|
||||
if (--page < 0 || page > 999)
|
||||
return;
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = _service.GlobalReactions.Where(cr => cr != null).ToArray();
|
||||
else
|
||||
customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, Array.Empty<CustomReaction>()).Where(cr => cr != null).ToArray();
|
||||
|
||||
if (customReactions == null || !customReactions.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var lastPage = customReactions.Length / 20;
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page, curPage =>
|
||||
new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("name"))
|
||||
.WithDescription(string.Join("\n", customReactions.OrderBy(cr => cr.Trigger)
|
||||
.Skip(curPage * 20)
|
||||
.Take(20)
|
||||
.Select(cr =>
|
||||
{
|
||||
var str = $"`#{cr.Id}` {cr.Trigger}";
|
||||
if (cr.AutoDeleteTrigger)
|
||||
{
|
||||
str = "🗑" + str;
|
||||
}
|
||||
if (cr.DmResponse)
|
||||
{
|
||||
str = "📪" + str;
|
||||
}
|
||||
return str;
|
||||
}))), lastPage)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum All
|
||||
{
|
||||
All
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public async Task ListCustReact(All x)
|
||||
{
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = _service.GlobalReactions.Where(cr => cr != null).ToArray();
|
||||
else
|
||||
customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
|
||||
|
||||
if (customReactions == null || !customReactions.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var txtStream = await customReactions.GroupBy(cr => cr.Trigger)
|
||||
.OrderBy(cr => cr.Key)
|
||||
.Select(cr => new { Trigger = cr.Key, Responses = cr.Select(y => new { id = y.Id, text = y.Response }).ToList() })
|
||||
.ToJson()
|
||||
.ToStream()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (Context.Guild == null) // its a private one, just send back
|
||||
await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false);
|
||||
else
|
||||
await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", GetText("list_all"), false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ListCustReactG(int page = 1)
|
||||
{
|
||||
if (--page < 0 || page > 9999)
|
||||
return;
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = _service.GlobalReactions.Where(cr => cr != null).ToArray();
|
||||
else
|
||||
customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
|
||||
|
||||
if (customReactions == null || !customReactions.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ordered = customReactions
|
||||
.GroupBy(cr => cr.Trigger)
|
||||
.OrderBy(cr => cr.Key)
|
||||
.ToList();
|
||||
|
||||
var lastPage = ordered.Count / 20;
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
|
||||
new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("name"))
|
||||
.WithDescription(string.Join("\r\n", ordered
|
||||
.Skip(curPage * 20)
|
||||
.Take(20)
|
||||
.Select(cr => $"**{cr.Key.Trim().ToLowerInvariant()}** `x{cr.Count()}`"))), lastPage)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ShowCustReact(int id)
|
||||
{
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = _service.GlobalReactions;
|
||||
else
|
||||
customReactions = _service.GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ });
|
||||
|
||||
var found = customReactions.FirstOrDefault(cr => cr?.Id == id);
|
||||
|
||||
if (found == null)
|
||||
{
|
||||
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(found.Trigger))
|
||||
.AddField(efb => efb.WithName(GetText("response")).WithValue(found.Response + "\n```css\n" + found.Response + "```"))
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task DelCustReact(int id)
|
||||
{
|
||||
if ((Context.Guild == null && !_creds.IsOwner(Context.User)) || (Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
|
||||
{
|
||||
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var success = false;
|
||||
CustomReaction toDelete;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
toDelete = uow.CustomReactions.Get(id);
|
||||
if (toDelete == null) //not found
|
||||
success = false;
|
||||
else
|
||||
{
|
||||
if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null)
|
||||
{
|
||||
uow.CustomReactions.Remove(toDelete);
|
||||
await _service.DelGcr(toDelete.Id);
|
||||
success = true;
|
||||
}
|
||||
else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId)
|
||||
{
|
||||
uow.CustomReactions.Remove(toDelete);
|
||||
_service.GuildReactions.AddOrUpdate(Context.Guild.Id, new CustomReaction[] { }, (key, old) =>
|
||||
{
|
||||
return old.Where(cr => cr?.Id != toDelete.Id).ToArray();
|
||||
});
|
||||
success = true;
|
||||
}
|
||||
if (success)
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("deleted"))
|
||||
.WithDescription("#" + toDelete.Id)
|
||||
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(toDelete.Trigger))
|
||||
.AddField(efb => efb.WithName(GetText("response")).WithValue(toDelete.Response)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task CrCa(int id)
|
||||
{
|
||||
if ((Context.Guild == null && !_creds.IsOwner(Context.User)) ||
|
||||
(Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
|
||||
{
|
||||
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomReaction[] reactions = new CustomReaction[0];
|
||||
|
||||
if (Context.Guild == null)
|
||||
reactions = _service.GlobalReactions;
|
||||
else
|
||||
{
|
||||
_service.GuildReactions.TryGetValue(Context.Guild.Id, out reactions);
|
||||
}
|
||||
if (reactions.Any())
|
||||
{
|
||||
var reaction = reactions.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (reaction == null)
|
||||
{
|
||||
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var setValue = reaction.ContainsAnywhere = !reaction.ContainsAnywhere;
|
||||
|
||||
await _service.SetCrCaAsync(reaction.Id, setValue).ConfigureAwait(false);
|
||||
|
||||
if (setValue)
|
||||
{
|
||||
await ReplyConfirmLocalized("crca_enabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalized("crca_disabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task CrDm(int id)
|
||||
{
|
||||
if ((Context.Guild == null && !_creds.IsOwner(Context.User)) ||
|
||||
(Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
|
||||
{
|
||||
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomReaction[] reactions = new CustomReaction[0];
|
||||
|
||||
if (Context.Guild == null)
|
||||
reactions = _service.GlobalReactions;
|
||||
else
|
||||
{
|
||||
_service.GuildReactions.TryGetValue(Context.Guild.Id, out reactions);
|
||||
}
|
||||
if (reactions.Any())
|
||||
{
|
||||
var reaction = reactions.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (reaction == null)
|
||||
{
|
||||
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var setValue = reaction.DmResponse = !reaction.DmResponse;
|
||||
|
||||
await _service.SetCrDmAsync(reaction.Id, setValue).ConfigureAwait(false);
|
||||
|
||||
if (setValue)
|
||||
{
|
||||
await ReplyConfirmLocalized("crdm_enabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalized("crdm_disabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task CrAd(int id)
|
||||
{
|
||||
if ((Context.Guild == null && !_creds.IsOwner(Context.User)) ||
|
||||
(Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
|
||||
{
|
||||
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomReaction[] reactions = new CustomReaction[0];
|
||||
|
||||
if (Context.Guild == null)
|
||||
reactions = _service.GlobalReactions;
|
||||
else
|
||||
{
|
||||
_service.GuildReactions.TryGetValue(Context.Guild.Id, out reactions);
|
||||
}
|
||||
if (reactions.Any())
|
||||
{
|
||||
var reaction = reactions.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (reaction == null)
|
||||
{
|
||||
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var setValue = reaction.AutoDeleteTrigger = !reaction.AutoDeleteTrigger;
|
||||
|
||||
await _service.SetCrAdAsync(reaction.Id, setValue).ConfigureAwait(false);
|
||||
|
||||
if (setValue)
|
||||
{
|
||||
await ReplyConfirmLocalized("crad_enabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalized("crad_disabled", Format.Code(reaction.Id.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task CrStatsClear(string trigger = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(trigger))
|
||||
{
|
||||
_service.ClearStats();
|
||||
await ReplyConfirmLocalized("all_stats_cleared").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_service.ReactionStats.TryRemove(trigger, out _))
|
||||
{
|
||||
await ReplyErrorLocalized("stats_cleared", Format.Bold(trigger)).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalized("stats_not_found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task CrStats(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
var ordered = _service.ReactionStats.OrderByDescending(x => x.Value).ToArray();
|
||||
if (!ordered.Any())
|
||||
return;
|
||||
var lastPage = ordered.Length / 9;
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page,
|
||||
(curPage) => ordered.Skip(curPage * 9)
|
||||
.Take(9)
|
||||
.Aggregate(new EmbedBuilder().WithOkColor().WithTitle(GetText("stats")),
|
||||
(agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true))), lastPage)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
148
NadekoBot.Core/Modules/CustomReactions/Extensions/Extensions.cs
Normal file
148
NadekoBot.Core/Modules/CustomReactions/Extensions/Extensions.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom.Html;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.CustomReactions.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Replacements;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions.Extensions
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
private static readonly Regex imgRegex = new Regex("%(img|image):(?<tag>.*?)%", RegexOptions.Compiled);
|
||||
|
||||
private static readonly NadekoRandom rng = new NadekoRandom();
|
||||
|
||||
public static Dictionary<Regex, Func<Match, Task<string>>> regexPlaceholders = new Dictionary<Regex, Func<Match, Task<string>>>()
|
||||
{
|
||||
{ imgRegex, async (match) => {
|
||||
var tag = match.Groups["tag"].ToString();
|
||||
if(string.IsNullOrWhiteSpace(tag))
|
||||
return "";
|
||||
|
||||
var fullQueryLink = $"http://imgur.com/search?q={ tag }";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
var elems = document.QuerySelectorAll("a.image-list-link").ToArray();
|
||||
|
||||
if (!elems.Any())
|
||||
return "";
|
||||
|
||||
var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Length))?.Children?.FirstOrDefault() as IHtmlImageElement);
|
||||
|
||||
if (img?.Source == null)
|
||||
return "";
|
||||
|
||||
return " " + img.Source.Replace("b.", ".") + " ";
|
||||
} }
|
||||
};
|
||||
|
||||
private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordSocketClient client)
|
||||
{
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithUser(ctx.Author)
|
||||
.WithClient(client)
|
||||
.Build();
|
||||
|
||||
str = rep.Replace(str.ToLowerInvariant());
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private static async Task<string> ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordSocketClient client, string resolvedTrigger, bool containsAnywhere)
|
||||
{
|
||||
var substringIndex = resolvedTrigger.Length;
|
||||
if (containsAnywhere)
|
||||
{
|
||||
var pos = ctx.Content.GetWordPosition(resolvedTrigger);
|
||||
if (pos == WordPosition.Start)
|
||||
substringIndex += 1;
|
||||
else if (pos == WordPosition.End)
|
||||
substringIndex = ctx.Content.Length;
|
||||
else if (pos == WordPosition.Middle)
|
||||
substringIndex += ctx.Content.IndexOf(resolvedTrigger);
|
||||
}
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client)
|
||||
.WithOverride("%target%", () => ctx.Content.Substring(substringIndex).Trim())
|
||||
.Build();
|
||||
|
||||
str = rep.Replace(str);
|
||||
|
||||
foreach (var ph in regexPlaceholders)
|
||||
{
|
||||
str = await ph.Key.ReplaceAsync(str, ph.Value);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client)
|
||||
=> cr.Trigger.ResolveTriggerString(ctx, client);
|
||||
|
||||
public static Task<string > ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, bool containsAnywhere)
|
||||
=> cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client), containsAnywhere);
|
||||
|
||||
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, CustomReactionsService crs)
|
||||
{
|
||||
var channel = cr.DmResponse ? await ctx.Author.GetOrCreateDMChannelAsync() : ctx.Channel;
|
||||
|
||||
crs.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old);
|
||||
|
||||
if (CREmbed.TryParse(cr.Response, out CREmbed crembed))
|
||||
{
|
||||
var trigger = cr.Trigger.ResolveTriggerString(ctx, client);
|
||||
var substringIndex = trigger.Length;
|
||||
if (cr.ContainsAnywhere)
|
||||
{
|
||||
var pos = ctx.Content.GetWordPosition(trigger);
|
||||
if (pos == WordPosition.Start)
|
||||
substringIndex += 1;
|
||||
else if (pos == WordPosition.End)
|
||||
substringIndex = ctx.Content.Length;
|
||||
else if (pos == WordPosition.Middle)
|
||||
substringIndex += ctx.Content.IndexOf(trigger);
|
||||
}
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client)
|
||||
.WithOverride("%target%", () => ctx.Content.Substring(substringIndex).Trim())
|
||||
.Build();
|
||||
|
||||
rep.Replace(crembed);
|
||||
|
||||
return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "");
|
||||
}
|
||||
return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(ctx, client, cr.ContainsAnywhere)).SanitizeMentions());
|
||||
}
|
||||
|
||||
public static WordPosition GetWordPosition(this string str, string word)
|
||||
{
|
||||
if (str.StartsWith(word + " "))
|
||||
return WordPosition.Start;
|
||||
else if (str.EndsWith(" " + word))
|
||||
return WordPosition.End;
|
||||
else if (str.Contains(" " + word + " "))
|
||||
return WordPosition.Middle;
|
||||
else
|
||||
return WordPosition.None;
|
||||
}
|
||||
}
|
||||
|
||||
public enum WordPosition
|
||||
{
|
||||
None,
|
||||
Start,
|
||||
Middle,
|
||||
End,
|
||||
}
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services.Database;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Modules.CustomReactions.Extensions;
|
||||
using NadekoBot.Modules.Permissions.Common;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Services.Impl;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions.Services
|
||||
{
|
||||
public class CustomReactionsService : IEarlyBlockingExecutor, INService
|
||||
{
|
||||
public CustomReaction[] GlobalReactions = new CustomReaction[] { };
|
||||
public ConcurrentDictionary<ulong, CustomReaction[]> GuildReactions { get; } = new ConcurrentDictionary<ulong, CustomReaction[]>();
|
||||
|
||||
public ConcurrentDictionary<string, uint> ReactionStats { get; } = new ConcurrentDictionary<string, uint>();
|
||||
|
||||
private readonly Logger _log;
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly PermissionService _perms;
|
||||
private readonly CommandHandler _cmd;
|
||||
private readonly IBotConfigProvider _bc;
|
||||
private readonly NadekoStrings _strings;
|
||||
private readonly IDataCache _cache;
|
||||
|
||||
public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings,
|
||||
DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow,
|
||||
IDataCache cache)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_db = db;
|
||||
_client = client;
|
||||
_perms = perms;
|
||||
_cmd = cmd;
|
||||
_bc = bc;
|
||||
_strings = strings;
|
||||
_cache = cache;
|
||||
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
sub.Subscribe(_client.CurrentUser.Id + "_gcr.added", (ch, msg) =>
|
||||
{
|
||||
Array.Resize(ref GlobalReactions, GlobalReactions.Length + 1);
|
||||
GlobalReactions[GlobalReactions.Length - 1] = JsonConvert.DeserializeObject<CustomReaction>(msg);
|
||||
}, StackExchange.Redis.CommandFlags.FireAndForget);
|
||||
sub.Subscribe(_client.CurrentUser.Id + "_gcr.deleted", (ch, msg) =>
|
||||
{
|
||||
var id = int.Parse(msg);
|
||||
GlobalReactions = GlobalReactions.Where(cr => cr?.Id != id).ToArray();
|
||||
}, StackExchange.Redis.CommandFlags.FireAndForget);
|
||||
sub.Subscribe(_client.CurrentUser.Id + "_gcr.edited", (ch, msg) =>
|
||||
{
|
||||
var obj = new { Id = 0, Message = "" };
|
||||
obj = JsonConvert.DeserializeAnonymousType(msg, obj);
|
||||
var gcr = GlobalReactions.FirstOrDefault(x => x.Id == obj.Id);
|
||||
if (gcr != null)
|
||||
gcr.Response = obj.Message;
|
||||
}, StackExchange.Redis.CommandFlags.FireAndForget);
|
||||
sub.Subscribe(_client.CurrentUser.Id + "_crad.toggle", (ch, msg) =>
|
||||
{
|
||||
var obj = new { Id = 0, Value = false };
|
||||
obj = JsonConvert.DeserializeAnonymousType(msg, obj);
|
||||
var gcr = GlobalReactions.FirstOrDefault(x => x.Id == obj.Id);
|
||||
if (gcr != null)
|
||||
gcr.AutoDeleteTrigger = obj.Value;
|
||||
}, StackExchange.Redis.CommandFlags.FireAndForget);
|
||||
sub.Subscribe(_client.CurrentUser.Id + "_crdm.toggle", (ch, msg) =>
|
||||
{
|
||||
var obj = new { Id = 0, Value = false };
|
||||
obj = JsonConvert.DeserializeAnonymousType(msg, obj);
|
||||
var gcr = GlobalReactions.FirstOrDefault(x => x.Id == obj.Id);
|
||||
if(gcr != null)
|
||||
gcr.DmResponse = obj.Value;
|
||||
}, StackExchange.Redis.CommandFlags.FireAndForget);
|
||||
sub.Subscribe(_client.CurrentUser.Id + "_crca.toggle", (ch, msg) =>
|
||||
{
|
||||
var obj = new { Id = 0, Value = false };
|
||||
obj = JsonConvert.DeserializeAnonymousType(msg, obj);
|
||||
var gcr = GlobalReactions.FirstOrDefault(x => x.Id == obj.Id);
|
||||
if (gcr != null)
|
||||
gcr.ContainsAnywhere = obj.Value;
|
||||
}, StackExchange.Redis.CommandFlags.FireAndForget);
|
||||
|
||||
|
||||
var items = uow.CustomReactions.GetAll();
|
||||
|
||||
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
|
||||
GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
|
||||
}
|
||||
|
||||
public Task EditGcr(int id, string message)
|
||||
{
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
|
||||
return sub.PublishAsync(_client.CurrentUser.Id + "_gcr.edited", JsonConvert.SerializeObject(new
|
||||
{
|
||||
Id = id,
|
||||
Message = message,
|
||||
}));
|
||||
}
|
||||
|
||||
public Task AddGcr(CustomReaction cr)
|
||||
{
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
return sub.PublishAsync(_client.CurrentUser.Id + "_gcr.added", JsonConvert.SerializeObject(cr));
|
||||
}
|
||||
|
||||
public Task DelGcr(int id)
|
||||
{
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
return sub.PublishAsync(_client.CurrentUser.Id + "_gcr.deleted", id);
|
||||
}
|
||||
|
||||
public void ClearStats() => ReactionStats.Clear();
|
||||
|
||||
public CustomReaction TryGetCustomReaction(IUserMessage umsg)
|
||||
{
|
||||
var channel = umsg.Channel as SocketTextChannel;
|
||||
if (channel == null)
|
||||
return null;
|
||||
|
||||
var content = umsg.Content.Trim().ToLowerInvariant();
|
||||
|
||||
if (GuildReactions.TryGetValue(channel.Guild.Id, out CustomReaction[] reactions))
|
||||
if (reactions != null && reactions.Any())
|
||||
{
|
||||
var rs = reactions.Where(cr =>
|
||||
{
|
||||
if (cr == null)
|
||||
return false;
|
||||
|
||||
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
|
||||
var trigger = cr.TriggerWithContext(umsg, _client).Trim().ToLowerInvariant();
|
||||
return ((cr.ContainsAnywhere &&
|
||||
(content.GetWordPosition(trigger) != WordPosition.None))
|
||||
|| (hasTarget && content.StartsWith(trigger + " "))
|
||||
|| (_bc.BotConfig.CustomReactionsStartWith && content.StartsWith(trigger + " "))
|
||||
|| content == trigger);
|
||||
}).ToArray();
|
||||
|
||||
if (rs.Length != 0)
|
||||
{
|
||||
var reaction = rs[new NadekoRandom().Next(0, rs.Length)];
|
||||
if (reaction != null)
|
||||
{
|
||||
if (reaction.Response == "-")
|
||||
return null;
|
||||
return reaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var grs = GlobalReactions.Where(cr =>
|
||||
{
|
||||
if (cr == null)
|
||||
return false;
|
||||
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
|
||||
var trigger = cr.TriggerWithContext(umsg, _client).Trim().ToLowerInvariant();
|
||||
return ((cr.ContainsAnywhere &&
|
||||
(content.GetWordPosition(trigger) != WordPosition.None))
|
||||
|| (hasTarget && content.StartsWith(trigger + " "))
|
||||
|| (_bc.BotConfig.CustomReactionsStartWith && content.StartsWith(trigger + " "))
|
||||
|| content == trigger);
|
||||
}).ToArray();
|
||||
if (grs.Length == 0)
|
||||
return null;
|
||||
var greaction = grs[new NadekoRandom().Next(0, grs.Length)];
|
||||
|
||||
return greaction;
|
||||
}
|
||||
|
||||
public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg)
|
||||
{
|
||||
// maybe this message is a custom reaction
|
||||
var cr = await Task.Run(() => TryGetCustomReaction(msg)).ConfigureAwait(false);
|
||||
if (cr != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (guild is SocketGuild sg)
|
||||
{
|
||||
var pc = _perms.GetCache(guild.Id);
|
||||
if (!pc.Permissions.CheckPermissions(msg, cr.Trigger, "ActualCustomReactions",
|
||||
out int index))
|
||||
{
|
||||
if (pc.Verbose)
|
||||
{
|
||||
var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)));
|
||||
try { await msg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
|
||||
_log.Info(returnMsg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
await cr.Send(msg, _client, this).ConfigureAwait(false);
|
||||
|
||||
if (cr.AutoDeleteTrigger)
|
||||
{
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Sending CREmbed failed");
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task SetCrDmAsync(int id, bool setValue)
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.CustomReactions.Get(id).DmResponse = setValue;
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
var data = new { Id = id, Value = setValue };
|
||||
return sub.PublishAsync(_client.CurrentUser.Id + "_crdm.toggle", JsonConvert.SerializeObject(data));
|
||||
}
|
||||
|
||||
public Task SetCrAdAsync(int id, bool setValue)
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.CustomReactions.Get(id).AutoDeleteTrigger = setValue;
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
var data = new { Id = id, Value = setValue };
|
||||
return sub.PublishAsync(_client.CurrentUser.Id + "_crad.toggle", JsonConvert.SerializeObject(data));
|
||||
}
|
||||
|
||||
public Task SetCrCaAsync(int id, bool setValue)
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.CustomReactions.Get(id).ContainsAnywhere = setValue;
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
var data = new { Id = id, Value = setValue };
|
||||
return sub.PublishAsync(_client.CurrentUser.Id + "_crca.toggle", JsonConvert.SerializeObject(data));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user