Added Ellie.Bot.Modules.Help
Signed-off-by: Emotion <emotion@emotionchild.com>
This commit is contained in:
parent
6f2ad42b90
commit
f06411a15c
5 changed files with 688 additions and 0 deletions
13
src/Ellie.Bot.Modules.Help/CommandJsonObject.cs
Normal file
13
src/Ellie.Bot.Modules.Help/CommandJsonObject.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
#nullable disable
|
||||
namespace Ellie.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/Ellie.Bot.Modules.Help/CommandsOptions.cs
Normal file
26
src/Ellie.Bot.Modules.Help/CommandsOptions.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
#nullable disable
|
||||
using CommandLine;
|
||||
|
||||
namespace Ellie.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()
|
||||
{
|
||||
}
|
||||
}
|
19
src/Ellie.Bot.Modules.Help/Ellie.Bot.Modules.Help.csproj
Normal file
19
src/Ellie.Bot.Modules.Help/Ellie.Bot.Modules.Help.csproj
Normal file
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ellie.Bot.Common\Ellie.Bot.Common.csproj"/>
|
||||
|
||||
<ProjectReference Include="..\Ellie.Bot.Generators.Cloneable\Ellie.Bot.Generators.Cloneable.csproj" OutputItemType="Analyzer"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.101.58"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
588
src/Ellie.Bot.Modules.Help/Help.cs
Normal file
588
src/Ellie.Bot.Modules.Help/Help.cs
Normal file
|
@ -0,0 +1,588 @@
|
|||
#nullable disable
|
||||
using Amazon.S3;
|
||||
using Ellie.Marmalade;
|
||||
using Ellie.Modules.Help.Common;
|
||||
using Ellie.Modules.Help.Services;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Ellie.Bot.Common;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace Ellie.Modules.Help;
|
||||
|
||||
public sealed class Help : EllieModule<HelpService>
|
||||
{
|
||||
public const string PATREON_URL = "https://patreon.com/emotionchild";
|
||||
public const string PAYPAL_URL = "https://paypal.me/EmotionChild";
|
||||
|
||||
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 IMarmaladeLoaderSevice _marmalades;
|
||||
|
||||
public Help(
|
||||
ICommandsUtilityService _cus,
|
||||
IPermissionChecker perms,
|
||||
CommandService cmds,
|
||||
BotConfigService bss,
|
||||
IServiceProvider services,
|
||||
DiscordSocketClient client,
|
||||
IBotStrings strings,
|
||||
IMarmaladeLoaderSevice 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 r = new ReplacementBuilder().WithDefault(Context)
|
||||
.WithOverride("{0}", () => clientId.ToString())
|
||||
.WithOverride("{1}", () => prefix)
|
||||
.WithOverride("%prefix%", () => prefix)
|
||||
.WithOverride("%bot.prefix%", () => prefix)
|
||||
.Build();
|
||||
|
||||
var text = SmartText.CreateFrom(botSettings.HelpText);
|
||||
return r.Replace(text);
|
||||
}
|
||||
|
||||
[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()).Select(x => x.Key))
|
||||
{
|
||||
var result = await _perms.CheckAsync(ctx.Guild, ctx.Channel, ctx.User,
|
||||
m.Name, null);
|
||||
|
||||
if (result.IsT0)
|
||||
topLevelModules.Add(m);
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
cur =>
|
||||
{
|
||||
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.list_of_modules));
|
||||
|
||||
var localModules = topLevelModules.Skip(12 * cur).Take(12).ToList();
|
||||
|
||||
if (!localModules.Any())
|
||||
{
|
||||
embed = embed.WithOkColor().WithDescription(GetText(strs.module_page_empty));
|
||||
return embed;
|
||||
}
|
||||
|
||||
localModules.OrderBy(module => module.Name)
|
||||
.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;
|
||||
},
|
||||
topLevelModules.Count(),
|
||||
12,
|
||||
false);
|
||||
}
|
||||
|
||||
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 "nsfw":
|
||||
return strs.module_description_nsfw;
|
||||
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 "nsfw":
|
||||
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>();
|
||||
|
||||
foreach (var cmd in _cmds.Commands
|
||||
.Where(c => c.Module.GetTopLevelModule()
|
||||
.Name
|
||||
.StartsWith(module, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
var result = await _perms.CheckAsync(ctx.Guild, ctx.Channel, ctx.User, cmd.Module.GetTopLevelModule().Name,
|
||||
cmd.Name);
|
||||
if (result.IsT0)
|
||||
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 = new((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 ReplyErrorLocalizedAsync(strs.module_not_found);
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.module_not_found_or_cant_exec);
|
||||
return;
|
||||
}
|
||||
|
||||
var cnt = 0;
|
||||
var groups = cmdsWithGroup.GroupBy(_ => cnt++ / 48).ToArray();
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
foreach (var g in groups)
|
||||
{
|
||||
var last = g.Count();
|
||||
for (var i = 0; i < last; i++)
|
||||
{
|
||||
var transformed = g.ElementAt(i)
|
||||
.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.First(),-15} {"[" + x.Aliases.Skip(1).FirstOrDefault() + "]",-8}";
|
||||
}
|
||||
|
||||
return
|
||||
$"{prefix + x.Aliases.First(),-15} {"[" + x.Aliases.Skip(1).FirstOrDefault() + "]",-8}";
|
||||
});
|
||||
|
||||
if (i == last - 1 && (i + 1) % 2 != 0)
|
||||
{
|
||||
transformed = transformed.Chunk(2)
|
||||
.Select(x =>
|
||||
{
|
||||
if (x.Count() == 1)
|
||||
return $"{x.First()}";
|
||||
return string.Concat(x);
|
||||
});
|
||||
}
|
||||
|
||||
embed.AddField(g.ElementAt(i).Key, "```css\n" + string.Join("\n", transformed) + "\n```", true);
|
||||
}
|
||||
}
|
||||
|
||||
embed.WithFooter(GetText(strs.commands_instr(prefix)));
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
private async Task Group(ModuleInfo group)
|
||||
{
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithTitle(GetText(strs.cmd_group_commands(group.Name)))
|
||||
.WithOkColor();
|
||||
|
||||
foreach (var cmd in group.Commands)
|
||||
{
|
||||
eb.AddField(prefix + cmd.Aliases.First(), cmd.RealSummary(_strings, _marmalades, Culture, prefix));
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[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)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.Group))
|
||||
.FirstOrDefault(x => x.Group.Equals(fail, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (group is not null)
|
||||
{
|
||||
await Group(group);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.command_not_found);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public async Task H([Leftover] CommandInfo com = null)
|
||||
{
|
||||
var channel = ctx.Channel;
|
||||
|
||||
if (com is null)
|
||||
{
|
||||
var ch = channel is ITextChannel ? await ctx.User.CreateDMChannelAsync() : channel;
|
||||
try
|
||||
{
|
||||
var data = await GetHelpString();
|
||||
if (data == default)
|
||||
return;
|
||||
await ch.SendAsync(data);
|
||||
try
|
||||
{
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
} // ignore if bot can't react
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.cant_dm);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _cus.GetCommandHelp(com, ctx.Guild);
|
||||
await channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[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);
|
||||
var uploadData = JsonConvert.SerializeObject(cmdData, Formatting.None);
|
||||
|
||||
// for example https://nyc.digitaloceanspaces.com (without your space name)
|
||||
var serviceUrl = Environment.GetEnvironmentVariable("do_spaces_address");
|
||||
|
||||
// generate spaces access key on https://cloud.digitalocean.com/account/api/tokens
|
||||
// you will get 2 keys, first, shorter one is id, longer one is secret
|
||||
var accessKey = Environment.GetEnvironmentVariable("do_access_key_id");
|
||||
var secretAcccessKey = Environment.GetEnvironmentVariable("do_access_key_secret");
|
||||
|
||||
// if all env vars are set, upload the unindented file (to save space) there
|
||||
if (!(serviceUrl is null || accessKey is null || secretAcccessKey is null))
|
||||
{
|
||||
var config = new AmazonS3Config
|
||||
{
|
||||
ServiceURL = serviceUrl
|
||||
};
|
||||
|
||||
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||
|
||||
using (var client = new AmazonS3Client(accessKey, secretAcccessKey, config))
|
||||
{
|
||||
await client.PutObjectAsync(new()
|
||||
{
|
||||
BucketName = "ellie",
|
||||
ContentType = "application/json",
|
||||
ContentBody = uploadData,
|
||||
// either use a path provided in the argument or the default one for public ellie, other/cmds.json
|
||||
Key = $"cmds/{StatsService.BOT_VERSION}.json",
|
||||
CannedACL = S3CannedACL.PublicRead
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var versionListString = "[]";
|
||||
try
|
||||
{
|
||||
using var oldVersionObject = await dlClient.GetObjectAsync(new()
|
||||
{
|
||||
BucketName = "ellie",
|
||||
Key = "cmds/versions.json"
|
||||
});
|
||||
|
||||
await using var ms = new MemoryStream();
|
||||
await oldVersionObject.ResponseStream.CopyToAsync(ms);
|
||||
versionListString = Encoding.UTF8.GetString(ms.ToArray());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Information("No old version list found. Creating a new one");
|
||||
}
|
||||
|
||||
var versionList = JsonSerializer.Deserialize<List<string>>(versionListString);
|
||||
if (versionList is not null && !versionList.Contains(StatsService.BOT_VERSION))
|
||||
{
|
||||
// save the file with new version added
|
||||
// versionList.Add(StatsService.BotVersion);
|
||||
versionListString = JsonSerializer.Serialize(versionList.Prepend(StatsService.BOT_VERSION),
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
// upload the updated version list
|
||||
using var client = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||
await client.PutObjectAsync(new()
|
||||
{
|
||||
BucketName = "ellie",
|
||||
ContentType = "application/json",
|
||||
ContentBody = versionListString,
|
||||
// either use a path provided in the argument or the default one for public ellie, other/cmds.json
|
||||
Key = "cmds/versions.json",
|
||||
CannedACL = S3CannedACL.PublicRead
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
"Version {Version} already exists in the version file. " + "Did you forget to increment it?",
|
||||
StatsService.BOT_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
// also send the file, but indented one, to chat
|
||||
await using var rDataStream = new MemoryStream(Encoding.ASCII.GetBytes(readableData));
|
||||
await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task Guide()
|
||||
=> await ConfirmLocalizedAsync(strs.guide("https://commands.elliebot.net",
|
||||
"https://docs.elliebot.net/"));
|
||||
|
||||
|
||||
private Task SelfhostAction(SocketMessageComponent smc, object _)
|
||||
=> smc.RespondConfirmAsync(_eb,
|
||||
"""
|
||||
- In case you don't want or cannot Donate to Ellie project, but you
|
||||
- Ellie is a completely free and fully [open source](https://toastielab.dev/EllieBotDevs/Ellie-bot) project which means you can run your own "selfhosted" instance on your computer or server for free.
|
||||
|
||||
*Keep in mind that running the bot on your computer means that the bot will be offline when you turn off your computer*
|
||||
|
||||
- You can find the selfhosting guides by using the `.guide` command and clicking on the second link that pops up.
|
||||
- If you decide to selfhost the bot, still consider [supporting the project](https://patreon.com/join/emotionchild) to keep the development going :)
|
||||
""",
|
||||
true);
|
||||
|
||||
[Cmd]
|
||||
[OnlyPublicBot]
|
||||
public async Task Donate()
|
||||
{
|
||||
// => new EllieInteractionData(new Emoji("🖥️"), "donate:selfhosting", "Selfhosting");
|
||||
var selfhostInter = _inter.Create(ctx.User.Id,
|
||||
new SimpleInteraction<object>(new ButtonBuilder(
|
||||
emote: new Emoji("🖥️"),
|
||||
customId: "donate:selfhosting",
|
||||
label: "Selfhosting"),
|
||||
SelfhostAction));
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithTitle("Thank you for considering to donate to the Ellie project!");
|
||||
|
||||
eb
|
||||
.WithDescription("Ellie relies on donations to keep the servers, services and APIs running.\n"
|
||||
+ "Donating will give you access to some exclusive features. You can read about them on the [patreon page](https://patreon.com/join/emotionchild)")
|
||||
.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/emotionchild> 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 in this link:
|
||||
<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 may start using the patron-only commands and features!
|
||||
🎉 **Enjoy!** 🎉
|
||||
")
|
||||
.AddField("Troubleshooting",
|
||||
"""
|
||||
|
||||
*In case you didn't receive the rewards within 5 minutes:*
|
||||
`1.` Make sure your DMs are open to everyone. Maybe your pledge was processed successfully but the bot was unable to DM you. Use the `.patron` command to check your status.
|
||||
`2.` Make sure you've connected the CORRECT Discord account. Quite often users log in to different Discord accounts in their browser. You may also try disconnecting and reconnecting your account.
|
||||
`3.` Make sure your payment has been processed and not declined by Patreon.
|
||||
`4.` If any of the previous steps don't help, you can join the ellie support server <https://discord.elliebot.net> and ask for help in the #help channel
|
||||
""");
|
||||
|
||||
try
|
||||
{
|
||||
await (await ctx.User.CreateDMChannelAsync()).EmbedAsync(eb, inter: selfhostInter);
|
||||
_ = ctx.OkAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.cant_dm);
|
||||
}
|
||||
}
|
||||
}
|
42
src/Ellie.Bot.Modules.Help/HelpService.cs
Normal file
42
src/Ellie.Bot.Modules.Help/HelpService.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
#nullable disable
|
||||
using Ellie.Common.ModuleBehaviors;
|
||||
|
||||
namespace Ellie.Modules.Help.Services;
|
||||
|
||||
public class HelpService : IExecNoCommand, IEService
|
||||
{
|
||||
private readonly BotConfigService _bss;
|
||||
|
||||
public HelpService(BotConfigService bss)
|
||||
{
|
||||
_bss = bss;
|
||||
}
|
||||
|
||||
public Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
var settings = _bss.Data;
|
||||
if (guild is null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(settings.DmHelpText) || settings.DmHelpText == "-")
|
||||
return Task.CompletedTask;
|
||||
|
||||
// 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 Task.CompletedTask;
|
||||
|
||||
var rep = new ReplacementBuilder().WithOverride("%prefix%", () => _bss.Data.Prefix)
|
||||
.WithOverride("%bot.prefix%", () => _bss.Data.Prefix)
|
||||
.WithUser(msg.Author)
|
||||
.Build();
|
||||
|
||||
var text = SmartText.CreateFrom(settings.DmHelpText);
|
||||
text = rep.Replace(text);
|
||||
|
||||
return msg.Channel.SendAsync(text);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue