Added Help module
This commit is contained in:
parent
4b5bfed33a
commit
6b44f9f5b7
4 changed files with 651 additions and 0 deletions
13
src/EllieBot/Modules/Help/CommandJsonObject.cs
Normal file
13
src/EllieBot/Modules/Help/CommandJsonObject.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#nullable disable
|
||||||
|
namespace EllieBot.Modules.Help;
|
||||||
|
|
||||||
|
internal class CommandJsonObject
|
||||||
|
{
|
||||||
|
public string[] Aliases { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string[] Usage { get; set; }
|
||||||
|
public string Submodule { get; set; }
|
||||||
|
public string Module { get; set; }
|
||||||
|
public List<string> Options { get; set; }
|
||||||
|
public string[] Requirements { get; set; }
|
||||||
|
}
|
26
src/EllieBot/Modules/Help/CommandsOptions.cs
Normal file
26
src/EllieBot/Modules/Help/CommandsOptions.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#nullable disable
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Help.Common;
|
||||||
|
|
||||||
|
public class CommandsOptions : IEllieCommandOptions
|
||||||
|
{
|
||||||
|
public enum ViewType
|
||||||
|
{
|
||||||
|
Hide,
|
||||||
|
Cross,
|
||||||
|
All
|
||||||
|
}
|
||||||
|
|
||||||
|
[Option('v',
|
||||||
|
"view",
|
||||||
|
Required = false,
|
||||||
|
Default = ViewType.Hide,
|
||||||
|
HelpText =
|
||||||
|
"Specifies how to output the list of commands. 0 - Hide commands which you can't use, 1 - Cross out commands which you can't use, 2 - Show all.")]
|
||||||
|
public ViewType View { get; set; } = ViewType.Hide;
|
||||||
|
|
||||||
|
public void NormalizeOptions()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
568
src/EllieBot/Modules/Help/Help.cs
Normal file
568
src/EllieBot/Modules/Help/Help.cs
Normal file
|
@ -0,0 +1,568 @@
|
||||||
|
#nullable disable
|
||||||
|
using EllieBot.Modules.Help.Common;
|
||||||
|
using EllieBot.Modules.Help.Services;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Text;
|
||||||
|
using Ellie.Common.Marmalade;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Help;
|
||||||
|
|
||||||
|
public sealed partial class Help : EllieModule<HelpService>
|
||||||
|
{
|
||||||
|
public const string PATREON_URL = "https://patreon.com/elliebot";
|
||||||
|
public const string PAYPAL_URL = "https://paypal.me/toastie_t0ast";
|
||||||
|
|
||||||
|
private readonly ICommandsUtilityService _cus;
|
||||||
|
private readonly CommandService _cmds;
|
||||||
|
private readonly BotConfigService _bss;
|
||||||
|
private readonly IPermissionChecker _perms;
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IBotStrings _strings;
|
||||||
|
|
||||||
|
private readonly AsyncLazy<ulong> _lazyClientId;
|
||||||
|
private readonly IMarmaladeLoaderService _marmalades;
|
||||||
|
|
||||||
|
public Help(
|
||||||
|
ICommandsUtilityService _cus,
|
||||||
|
IPermissionChecker perms,
|
||||||
|
CommandService cmds,
|
||||||
|
BotConfigService bss,
|
||||||
|
IServiceProvider services,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
IBotStrings strings,
|
||||||
|
IMarmaladeLoaderService marmalades)
|
||||||
|
{
|
||||||
|
this._cus = _cus;
|
||||||
|
_cmds = cmds;
|
||||||
|
_bss = bss;
|
||||||
|
_perms = perms;
|
||||||
|
_services = services;
|
||||||
|
_client = client;
|
||||||
|
_strings = strings;
|
||||||
|
_marmalades = marmalades;
|
||||||
|
|
||||||
|
_lazyClientId = new(async () => (await _client.GetApplicationInfoAsync()).Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SmartText> GetHelpString()
|
||||||
|
{
|
||||||
|
var botSettings = _bss.Data;
|
||||||
|
if (string.IsNullOrWhiteSpace(botSettings.HelpText) || botSettings.HelpText == "-")
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var clientId = await _lazyClientId.Value;
|
||||||
|
var repCtx = new ReplacementContext(Context)
|
||||||
|
.WithOverride("{0}", () => clientId.ToString())
|
||||||
|
.WithOverride("{1}", () => prefix)
|
||||||
|
.WithOverride("%prefix%", () => prefix)
|
||||||
|
.WithOverride("%bot.prefix%", () => prefix);
|
||||||
|
|
||||||
|
var text = SmartText.CreateFrom(botSettings.HelpText);
|
||||||
|
return await repSvc.ReplaceAsync(text, repCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task Modules(int page = 1)
|
||||||
|
{
|
||||||
|
if (--page < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var topLevelModules = new List<ModuleInfo>();
|
||||||
|
foreach (var m in _cmds.Modules.GroupBy(x => x.GetTopLevelModule()).OrderBy(x => x.Key.Name).Select(x => x.Key))
|
||||||
|
{
|
||||||
|
var result = await _perms.CheckPermsAsync(ctx.Guild,
|
||||||
|
ctx.Channel,
|
||||||
|
ctx.User,
|
||||||
|
m.Name,
|
||||||
|
null);
|
||||||
|
|
||||||
|
#if GLOBAL_NADEKO
|
||||||
|
if (m.Preconditions.Any(x => x is NoPublicBotAttribute))
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (result.IsAllowed)
|
||||||
|
topLevelModules.Add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu = new SelectMenuBuilder()
|
||||||
|
.WithPlaceholder("Select a module to see its commands")
|
||||||
|
.WithCustomId("cmds:modules_select");
|
||||||
|
|
||||||
|
foreach (var m in topLevelModules)
|
||||||
|
menu.AddOption(m.Name, m.Name, GetModuleEmoji(m.Name));
|
||||||
|
|
||||||
|
var inter = _inter.Create(ctx.User.Id,
|
||||||
|
menu,
|
||||||
|
async (smc) =>
|
||||||
|
{
|
||||||
|
await smc.DeferAsync();
|
||||||
|
var val = smc.Data.Values.FirstOrDefault();
|
||||||
|
if (val is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Commands(val);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.Items(topLevelModules)
|
||||||
|
.PageSize(12)
|
||||||
|
.CurrentPage(page)
|
||||||
|
.Interaction(inter)
|
||||||
|
.AddFooter(false)
|
||||||
|
.Page((items, _) =>
|
||||||
|
{
|
||||||
|
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.list_of_modules));
|
||||||
|
|
||||||
|
if (!items.Any())
|
||||||
|
{
|
||||||
|
embed = embed.WithOkColor().WithDescription(GetText(strs.module_page_empty));
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
.ToList()
|
||||||
|
.ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}",
|
||||||
|
GetModuleDescription(module.Name)
|
||||||
|
+ "\n"
|
||||||
|
+ Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))),
|
||||||
|
true));
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetModuleDescription(string moduleName)
|
||||||
|
{
|
||||||
|
var key = GetModuleLocStr(moduleName);
|
||||||
|
|
||||||
|
if (key.Key == strs.module_description_missing.Key)
|
||||||
|
{
|
||||||
|
var desc = _marmalades
|
||||||
|
.GetLoadedMarmalades(Culture)
|
||||||
|
.FirstOrDefault(m => m.Canaries
|
||||||
|
.Any(x => x.Name.Equals(moduleName,
|
||||||
|
StringComparison.InvariantCultureIgnoreCase)))
|
||||||
|
?.Description;
|
||||||
|
|
||||||
|
if (desc is not null)
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetText(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocStr GetModuleLocStr(string moduleName)
|
||||||
|
{
|
||||||
|
switch (moduleName.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "help":
|
||||||
|
return strs.module_description_help;
|
||||||
|
case "administration":
|
||||||
|
return strs.module_description_administration;
|
||||||
|
case "expressions":
|
||||||
|
return strs.module_description_expressions;
|
||||||
|
case "searches":
|
||||||
|
return strs.module_description_searches;
|
||||||
|
case "utility":
|
||||||
|
return strs.module_description_utility;
|
||||||
|
case "games":
|
||||||
|
return strs.module_description_games;
|
||||||
|
case "gambling":
|
||||||
|
return strs.module_description_gambling;
|
||||||
|
case "music":
|
||||||
|
return strs.module_description_music;
|
||||||
|
case "permissions":
|
||||||
|
return strs.module_description_permissions;
|
||||||
|
case "xp":
|
||||||
|
return strs.module_description_xp;
|
||||||
|
case "marmalade":
|
||||||
|
return strs.module_description_marmalade;
|
||||||
|
case "patronage":
|
||||||
|
return strs.module_description_patronage;
|
||||||
|
default:
|
||||||
|
return strs.module_description_missing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetModuleEmoji(string moduleName)
|
||||||
|
{
|
||||||
|
moduleName = moduleName.ToLowerInvariant();
|
||||||
|
switch (moduleName)
|
||||||
|
{
|
||||||
|
case "help":
|
||||||
|
return "❓";
|
||||||
|
case "administration":
|
||||||
|
return "🛠️";
|
||||||
|
case "expressions":
|
||||||
|
return "🗣️";
|
||||||
|
case "searches":
|
||||||
|
return "🔍";
|
||||||
|
case "utility":
|
||||||
|
return "🔧";
|
||||||
|
case "games":
|
||||||
|
return "🎲";
|
||||||
|
case "gambling":
|
||||||
|
return "💰";
|
||||||
|
case "music":
|
||||||
|
return "🎶";
|
||||||
|
case "permissions":
|
||||||
|
return "🚓";
|
||||||
|
case "xp":
|
||||||
|
return "📝";
|
||||||
|
case "patronage":
|
||||||
|
return "💝";
|
||||||
|
default:
|
||||||
|
return "📖";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[EllieOptions<CommandsOptions>]
|
||||||
|
public async Task Commands(string module = null, params string[] args)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(module))
|
||||||
|
{
|
||||||
|
await Modules();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (opts, _) = OptionsParser.ParseFrom(new CommandsOptions(), args);
|
||||||
|
|
||||||
|
// Find commands for that module
|
||||||
|
// don't show commands which are blocked
|
||||||
|
// order by name
|
||||||
|
var allowed = new List<CommandInfo>();
|
||||||
|
|
||||||
|
var mdls = _cmds.Commands
|
||||||
|
.Where(c => c.Module.GetTopLevelModule()
|
||||||
|
.Name
|
||||||
|
.StartsWith(module, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (mdls.Length == 0)
|
||||||
|
{
|
||||||
|
var group = _cmds.Modules
|
||||||
|
.Where(x => x.Parent is not null)
|
||||||
|
.FirstOrDefault(x => string.Equals(x.Name.Replace("Commands", ""),
|
||||||
|
module,
|
||||||
|
StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
|
if (group is not null)
|
||||||
|
{
|
||||||
|
await Group(group);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cmd in mdls)
|
||||||
|
{
|
||||||
|
var result = await _perms.CheckPermsAsync(ctx.Guild,
|
||||||
|
ctx.Channel,
|
||||||
|
ctx.User,
|
||||||
|
cmd.Module.GetTopLevelModule().Name,
|
||||||
|
cmd.Name);
|
||||||
|
|
||||||
|
if (result.IsAllowed)
|
||||||
|
allowed.Add(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var cmds = allowed.OrderBy(c => c.Aliases[0])
|
||||||
|
.DistinctBy(x => x.Aliases[0])
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
// check preconditions for all commands, but only if it's not 'all'
|
||||||
|
// because all will show all commands anyway, no need to check
|
||||||
|
var succ = new HashSet<CommandInfo>();
|
||||||
|
if (opts.View != CommandsOptions.ViewType.All)
|
||||||
|
{
|
||||||
|
succ =
|
||||||
|
[
|
||||||
|
..(await cmds.Select(async x =>
|
||||||
|
{
|
||||||
|
var pre = await x.CheckPreconditionsAsync(Context, _services);
|
||||||
|
return (Cmd: x, Succ: pre.IsSuccess);
|
||||||
|
})
|
||||||
|
.WhenAll()).Where(x => x.Succ)
|
||||||
|
.Select(x => x.Cmd)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (opts.View == CommandsOptions.ViewType.Hide)
|
||||||
|
// if hidden is specified, completely remove these commands from the list
|
||||||
|
cmds = cmds.Where(x => succ.Contains(x)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdsWithGroup = cmds.GroupBy(c => c.Module.GetGroupName())
|
||||||
|
.OrderBy(x => x.Key == x.First().Module.Name ? int.MaxValue : x.Count())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (cmdsWithGroup.Count == 0)
|
||||||
|
{
|
||||||
|
if (opts.View != CommandsOptions.ViewType.Hide)
|
||||||
|
await Response().Error(strs.module_not_found).SendAsync();
|
||||||
|
else
|
||||||
|
await Response().Error(strs.module_not_found_or_cant_exec).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new SelectMenuBuilder()
|
||||||
|
.WithCustomId("cmds:submodule_select")
|
||||||
|
.WithPlaceholder("Select a submodule to see detailed commands");
|
||||||
|
|
||||||
|
var groups = cmdsWithGroup.ToArray();
|
||||||
|
var embed = _sender.CreateEmbed().WithOkColor();
|
||||||
|
foreach (var g in groups)
|
||||||
|
{
|
||||||
|
sb.AddOption(g.Key, g.Key);
|
||||||
|
var transformed = g
|
||||||
|
.Select(x =>
|
||||||
|
{
|
||||||
|
//if cross is specified, and the command doesn't satisfy the requirements, cross it out
|
||||||
|
if (opts.View == CommandsOptions.ViewType.Cross)
|
||||||
|
{
|
||||||
|
return $"{(succ.Contains(x) ? "✅" : "❌")} {prefix + x.Aliases[0]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (x.Aliases.Count == 1)
|
||||||
|
return prefix + x.Aliases[0];
|
||||||
|
|
||||||
|
return prefix + x.Aliases[0] + " | " + prefix + x.Aliases[1];
|
||||||
|
});
|
||||||
|
|
||||||
|
embed.AddField(g.Key, "" + string.Join("\n", transformed) + "", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
embed.WithFooter(GetText(strs.commands_instr(prefix)));
|
||||||
|
|
||||||
|
|
||||||
|
var inter = _inter.Create(ctx.User.Id,
|
||||||
|
sb,
|
||||||
|
async (smc) =>
|
||||||
|
{
|
||||||
|
var groupName = smc.Data.Values.FirstOrDefault();
|
||||||
|
var mdl = _cmds.Modules.FirstOrDefault(x
|
||||||
|
=> string.Equals(x.Name.Replace("Commands", ""), groupName, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
await smc.DeferAsync();
|
||||||
|
await Group(mdl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Response().Embed(embed).Interaction(inter).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Group(ModuleInfo group)
|
||||||
|
{
|
||||||
|
var menu = new SelectMenuBuilder()
|
||||||
|
.WithCustomId("cmds:group_select")
|
||||||
|
.WithPlaceholder("Select a command to see its details");
|
||||||
|
|
||||||
|
foreach (var cmd in group.Commands.DistinctBy(x => x.Aliases[0]))
|
||||||
|
{
|
||||||
|
menu.AddOption(prefix + cmd.Aliases[0], cmd.Aliases[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var inter = _inter.Create(ctx.User.Id,
|
||||||
|
menu,
|
||||||
|
async (smc) =>
|
||||||
|
{
|
||||||
|
await smc.DeferAsync();
|
||||||
|
|
||||||
|
await H(smc.Data.Values.FirstOrDefault());
|
||||||
|
});
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.Items(group.Commands.DistinctBy(x => x.Aliases[0]).ToArray())
|
||||||
|
.PageSize(25)
|
||||||
|
.Interaction(inter)
|
||||||
|
.Page((items, _) =>
|
||||||
|
{
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithTitle(GetText(strs.cmd_group_commands(group.Name)))
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
foreach (var cmd in items)
|
||||||
|
{
|
||||||
|
string cmdName;
|
||||||
|
if (cmd.Aliases.Count > 1)
|
||||||
|
cmdName = Format.Code(prefix + cmd.Aliases[0]) + " | " + Format.Code(prefix + cmd.Aliases[1]);
|
||||||
|
else
|
||||||
|
cmdName = Format.Code(prefix + cmd.Aliases.First());
|
||||||
|
|
||||||
|
eb.AddField(cmdName, cmd.RealSummary(_strings, _marmalades, Culture, prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[Priority(0)]
|
||||||
|
public async Task H([Leftover] string fail)
|
||||||
|
{
|
||||||
|
var prefixless =
|
||||||
|
_cmds.Commands.FirstOrDefault(x => x.Aliases.Any(cmdName => cmdName.ToLowerInvariant() == fail));
|
||||||
|
if (prefixless is not null)
|
||||||
|
{
|
||||||
|
await H(prefixless);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail.StartsWith(prefix))
|
||||||
|
fail = fail.Substring(prefix.Length);
|
||||||
|
|
||||||
|
var group = _cmds.Modules
|
||||||
|
.SelectMany(x => x.Submodules)
|
||||||
|
.FirstOrDefault(x => string.Equals(x.Group,
|
||||||
|
fail,
|
||||||
|
StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
|
if (group is not null)
|
||||||
|
{
|
||||||
|
await Group(group);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response().Error(strs.command_not_found).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[Priority(1)]
|
||||||
|
public async Task H([Leftover] CommandInfo com = null)
|
||||||
|
{
|
||||||
|
var channel = ctx.Channel;
|
||||||
|
if (com is null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ch = channel is ITextChannel ? await ctx.User.CreateDMChannelAsync() : channel;
|
||||||
|
var data = await GetHelpString();
|
||||||
|
if (data == default)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Response().Channel(ch).Text(data).SendAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
} // ignore if bot can't react
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.cant_dm).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var embed = _cus.GetCommandHelp(com, ctx.Guild);
|
||||||
|
await _sender.Response(channel).Embed(embed).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task GenCmdList()
|
||||||
|
{
|
||||||
|
_ = ctx.Channel.TriggerTypingAsync();
|
||||||
|
|
||||||
|
// order commands by top level module name
|
||||||
|
// and make a dictionary of <ModuleName, Array<JsonCommandData>>
|
||||||
|
var cmdData = _cmds.Commands.GroupBy(x => x.Module.GetTopLevelModule().Name)
|
||||||
|
.OrderBy(x => x.Key)
|
||||||
|
.ToDictionary(x => x.Key,
|
||||||
|
x => x.DistinctBy(c => c.Aliases.First())
|
||||||
|
.Select(com =>
|
||||||
|
{
|
||||||
|
List<string> optHelpStr = null;
|
||||||
|
|
||||||
|
var opt = CommandsUtilityService.GetEllieOptionType(com.Attributes);
|
||||||
|
if (opt is not null)
|
||||||
|
optHelpStr = CommandsUtilityService.GetCommandOptionHelpList(opt);
|
||||||
|
|
||||||
|
return new CommandJsonObject
|
||||||
|
{
|
||||||
|
Aliases = com.Aliases.Select(alias => prefix + alias).ToArray(),
|
||||||
|
Description = com.RealSummary(_strings, _marmalades, Culture, prefix),
|
||||||
|
Usage = com.RealRemarksArr(_strings, _marmalades, Culture, prefix),
|
||||||
|
Submodule = com.Module.Name,
|
||||||
|
Module = com.Module.GetTopLevelModule().Name,
|
||||||
|
Options = optHelpStr,
|
||||||
|
Requirements = CommandsUtilityService.GetCommandRequirements(com)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToList());
|
||||||
|
|
||||||
|
var readableData = JsonConvert.SerializeObject(cmdData, Formatting.Indented);
|
||||||
|
|
||||||
|
// send the indented file to chat
|
||||||
|
await using var rDataStream = new MemoryStream(Encoding.ASCII.GetBytes(readableData));
|
||||||
|
await File.WriteAllTextAsync("data/commandlist.json", readableData);
|
||||||
|
await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task Guide()
|
||||||
|
=> await Response()
|
||||||
|
.Confirm(strs.guide("https://nadeko.bot/commands",
|
||||||
|
"https://nadekobot.readthedocs.io/en/latest/"))
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OnlyPublicBot]
|
||||||
|
public async Task Donate()
|
||||||
|
{
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle("Thank you for considering to donate to the EllieBot project!");
|
||||||
|
|
||||||
|
eb
|
||||||
|
.WithDescription("""
|
||||||
|
EllieBot relies on donations to keep the servers, services and APIs running.
|
||||||
|
Donating will give you access to some exclusive features. You can read about them on the [patreon page](https://patreon.com/join/nadekobot)
|
||||||
|
""")
|
||||||
|
.AddField("Donation Instructions",
|
||||||
|
$"""
|
||||||
|
🗒️ Before pledging it is recommended to open your DMs as Ellie will send you a welcome message with instructions after you pledge has been processed and confirmed.
|
||||||
|
|
||||||
|
**Step 1:** ❤️ Pledge on Patreon ❤️
|
||||||
|
|
||||||
|
`1.` Go to <https://patreon.com/join/nadekobot> and choose a tier.
|
||||||
|
`2.` Make sure your payment is processed and accepted.
|
||||||
|
|
||||||
|
**Step 2** 🤝 Connect your Discord account 🤝
|
||||||
|
|
||||||
|
`1.` Go to your profile settings on Patreon and connect your Discord account to it.
|
||||||
|
*please make sure you're logged into the correct Discord account*
|
||||||
|
|
||||||
|
If you do not know how to do it, you may [follow instructions here](https://support.patreon.com/hc/en-us/articles/212052266-How-do-I-connect-Discord-to-Patreon-Patron-)
|
||||||
|
|
||||||
|
**Step 3** ⏰ Wait a short while (usually 1-3 minutes) ⏰
|
||||||
|
|
||||||
|
Ellie will DM you the welcome instructions, and you will receive your rewards!
|
||||||
|
🎉 **Enjoy!** 🎉
|
||||||
|
""");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Channel(await ctx.User.CreateDMChannelAsync())
|
||||||
|
.Embed(eb)
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
_ = ctx.OkAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await Response().Error(strs.cant_dm).SendAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/EllieBot/Modules/Help/HelpService.cs
Normal file
44
src/EllieBot/Modules/Help/HelpService.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Help.Services;
|
||||||
|
|
||||||
|
public class HelpService : IExecNoCommand, IEService
|
||||||
|
{
|
||||||
|
private readonly BotConfigService _bss;
|
||||||
|
private readonly IReplacementService _rs;
|
||||||
|
private readonly IMessageSenderService _sender;
|
||||||
|
|
||||||
|
public HelpService(BotConfigService bss, IReplacementService repSvc, IMessageSenderService sender)
|
||||||
|
{
|
||||||
|
_bss = bss;
|
||||||
|
_rs = repSvc;
|
||||||
|
_sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecOnNoCommandAsync(IGuild? guild, IUserMessage msg)
|
||||||
|
{
|
||||||
|
var settings = _bss.Data;
|
||||||
|
if (guild is null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(settings.DmHelpText) || settings.DmHelpText == "-")
|
||||||
|
return;
|
||||||
|
|
||||||
|
// only send dm help text if it contains one of the keywords, if they're specified
|
||||||
|
// if they're not, then reply to every DM
|
||||||
|
if (settings.DmHelpTextKeywords is not null
|
||||||
|
&& !settings.DmHelpTextKeywords.Any(k => msg.Content.Contains(k)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var repCtx = new ReplacementContext(guild: guild, channel: msg.Channel, user: msg.Author)
|
||||||
|
.WithOverride("%prefix%", () => _bss.Data.Prefix)
|
||||||
|
.WithOverride("%bot.prefix%", () => _bss.Data.Prefix);
|
||||||
|
|
||||||
|
var text = SmartText.CreateFrom(settings.DmHelpText);
|
||||||
|
text = await _rs.ReplaceAsync(text, repCtx);
|
||||||
|
|
||||||
|
await _sender.Response(msg.Channel).Text(text).SendAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue