Fixed a nullref message when the bot is loading medusae. Added support for multiple embeds in features which support custom embeds in the form of

{plainText:text-here, embeds: [embedObject, embedObject, embedObject]}
This commit is contained in:
Kwoth
2022-04-17 09:58:30 +02:00
parent 29d94640af
commit 18400dc53a
10 changed files with 104 additions and 23 deletions

View File

@@ -1,4 +1,5 @@
using Cloneable; #nullable enable
using Cloneable;
using NadekoBot.Common.Yml; using NadekoBot.Common.Yml;
namespace Nadeko.Medusa; namespace Nadeko.Medusa;
@@ -10,7 +11,7 @@ public sealed partial class MedusaConfig : ICloneable<MedusaConfig>
public int Version { get; set; } = 1; public int Version { get; set; } = 1;
[Comment("List of medusae automatically loaded at startup")] [Comment("List of medusae automatically loaded at startup")]
public List<string> Loaded { get; set; } public List<string>? Loaded { get; set; }
public MedusaConfig() public MedusaConfig()
{ {

View File

@@ -18,7 +18,7 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
} }
public IReadOnlyCollection<string> GetLoadedMedusae() public IReadOnlyCollection<string> GetLoadedMedusae()
=> Data.Loaded.ToList(); => Data.Loaded?.ToList() ?? new List<string>();
public void AddLoadedMedusa(string name) public void AddLoadedMedusa(string name)
{ {
@@ -26,6 +26,9 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
ModifyConfig(conf => ModifyConfig(conf =>
{ {
if (conf.Loaded is null)
conf.Loaded = new();
if(!conf.Loaded.Contains(name)) if(!conf.Loaded.Contains(name))
conf.Loaded.Add(name); conf.Loaded.Add(name);
}); });
@@ -37,6 +40,9 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
ModifyConfig(conf => ModifyConfig(conf =>
{ {
if (conf.Loaded is null)
conf.Loaded = new();
conf.Loaded.Remove(name); conf.Loaded.Remove(name);
}); });
} }

View File

@@ -36,9 +36,17 @@ public class Replacer
{ {
SmartEmbedText embedData => Replace(embedData), SmartEmbedText embedData => Replace(embedData),
SmartPlainText plain => Replace(plain), SmartPlainText plain => Replace(plain),
SmartEmbedTextArray arr => Replace(arr),
_ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type") _ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
}; };
public SmartEmbedTextArray Replace(SmartEmbedTextArray embedArr)
=> new()
{
Embeds = embedArr.Embeds.Map(Replace),
PlainText = Replace(embedArr.PlainText)
};
public SmartPlainText Replace(SmartPlainText plainText) public SmartPlainText Replace(SmartPlainText plainText)
=> Replace(plainText.Text); => Replace(plainText.Text);

View File

@@ -0,0 +1,28 @@
#nullable disable
namespace NadekoBot;
public sealed record SmartEmbedTextArray : SmartText
{
public string PlainText { get; set; }
public SmartEmbedText[] Embeds { get; set; }
public bool IsValid
=> Embeds?.All(x => x.IsValid) ?? false;
public EmbedBuilder[] GetEmbedBuilders()
{
if (Embeds is null)
return Array.Empty<EmbedBuilder>();
return Embeds.Map(em => em.GetEmbed());
}
public void NormalizeFields()
{
if (Embeds is null)
return;
foreach(var eb in Embeds)
eb.NormalizeFields();
}
}

View File

@@ -1,5 +1,5 @@
#nullable disable #nullable disable
using Newtonsoft.Json; using System.Text.Json;
namespace NadekoBot; namespace NadekoBot;
@@ -11,6 +11,15 @@ public abstract record SmartText
public bool IsPlainText public bool IsPlainText
=> this is SmartPlainText; => this is SmartPlainText;
public bool IsEmbedArray
=> this is SmartEmbedTextArray;
private static readonly JsonSerializerOptions _opts = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static SmartText operator +(SmartText text, string input) public static SmartText operator +(SmartText text, string input)
=> text switch => text switch
{ {
@@ -19,6 +28,10 @@ public abstract record SmartText
PlainText = set.PlainText + input PlainText = set.PlainText + input
}, },
SmartPlainText spt => new SmartPlainText(spt.Text + input), SmartPlainText spt => new SmartPlainText(spt.Text + input),
SmartEmbedTextArray arr => arr with
{
PlainText = arr.PlainText + input
},
_ => throw new ArgumentOutOfRangeException(nameof(text)) _ => throw new ArgumentOutOfRangeException(nameof(text))
}; };
@@ -30,27 +43,46 @@ public abstract record SmartText
PlainText = input + set.PlainText PlainText = input + set.PlainText
}, },
SmartPlainText spt => new SmartPlainText(input + spt.Text), SmartPlainText spt => new SmartPlainText(input + spt.Text),
SmartEmbedTextArray arr => arr with
{
PlainText = input + arr.PlainText
},
_ => throw new ArgumentOutOfRangeException(nameof(text)) _ => throw new ArgumentOutOfRangeException(nameof(text))
}; };
[CanBeNull]
public static SmartText CreateFrom(string input) public static SmartText CreateFrom(string input)
{ {
if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{")) if (string.IsNullOrWhiteSpace(input))
return new SmartPlainText(input); return new SmartPlainText(input);
try try
{ {
var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input); var doc = JsonDocument.Parse(input);
var root = doc.RootElement;
if (root.ValueKind == JsonValueKind.Object)
{
if (root.TryGetProperty("embeds", out _))
{
var arr = root.Deserialize<SmartEmbedTextArray>(_opts);
if (smartEmbedText is null) if (arr is null)
throw new FormatException(); return new SmartPlainText(input);
smartEmbedText.NormalizeFields(); arr!.NormalizeFields();
return arr;
}
if (!smartEmbedText.IsValid) var obj = root.Deserialize<SmartEmbedText>(_opts);
return new SmartPlainText(input);
return smartEmbedText; if (obj is null)
return new SmartPlainText(input);
obj.NormalizeFields();
return obj;
}
return new SmartPlainText(input);
} }
catch catch
{ {

View File

@@ -530,7 +530,7 @@ public class UserPunishService : INService, IReadyExecutor
return default; return default;
// if template is an embed, send that embed with replacements // if template is an embed, send that embed with replacements
// otherwise, treat template as a regular string with replacements // otherwise, treat template as a regular string with replacements
else if (!SmartText.CreateFrom(template).IsEmbed) else if (SmartText.CreateFrom(template) is not { IsEmbed: true } or { IsEmbedArray: true })
{ {
template = JsonConvert.SerializeObject(new template = JsonConvert.SerializeObject(new
{ {

View File

@@ -81,8 +81,7 @@ public class HelpService : IExecNoCommand, INService
em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs)); em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs));
em.AddField(_strings.GetText(strs.usage), em.AddField(_strings.GetText(strs.usage),
string.Join("\n", string.Join("\n", com.RealRemarksArr(_strings,_medusae, culture, prefix).Map(arg => Format.Code(arg))))
Array.ConvertAll(com.RealRemarksArr(_strings,_medusae, culture, prefix), arg => Format.Code(arg))))
.WithFooter(GetText(strs.module(com.Module.GetTopLevelModule().Name), guild)) .WithFooter(GetText(strs.module(com.Module.GetTopLevelModule().Name), guild))
.WithOkColor(); .WithOkColor();

View File

@@ -46,7 +46,7 @@ public class RedisBotStringsProvider : IBotStringsProvider
if (descStr == default) if (descStr == default)
return null; return null;
var args = Array.ConvertAll(argsStr.Split('&'), HttpUtility.UrlDecode); var args = argsStr.Split('&').Map(HttpUtility.UrlDecode);
return new() return new()
{ {
Args = args, Args = args,
@@ -68,7 +68,7 @@ public class RedisBotStringsProvider : IBotStringsProvider
{ {
var hashFields = localeStrings var hashFields = localeStrings
.Select(x => new HashEntry($"{x.Key}::args", .Select(x => new HashEntry($"{x.Key}::args",
string.Join('&', Array.ConvertAll(x.Value.Args, HttpUtility.UrlEncode)))) string.Join('&', x.Value.Args.Map(HttpUtility.UrlEncode))))
.Concat(localeStrings.Select(x => new HashEntry($"{x.Key}::desc", x.Value.Desc))) .Concat(localeStrings.Select(x => new HashEntry($"{x.Key}::desc", x.Value.Desc)))
.ToArray(); .ToArray();

View File

@@ -23,6 +23,11 @@ public static class Extensions
x.Embed = set.GetEmbed().Build(); x.Embed = set.GetEmbed().Build();
x.Content = set.PlainText?.SanitizeMentions() ?? ""; x.Content = set.PlainText?.SanitizeMentions() ?? "";
}), }),
SmartEmbedTextArray set => msg.ModifyAsync(x =>
{
x.Embeds = set.GetEmbedBuilders().Map(eb => eb.Build());
x.Content = set.PlainText?.SanitizeMentions() ?? "";
}),
SmartPlainText spt => msg.ModifyAsync(x => SmartPlainText spt => msg.ModifyAsync(x =>
{ {
x.Content = spt.Text.SanitizeMentions(); x.Content = spt.Text.SanitizeMentions();
@@ -116,8 +121,7 @@ public static class Extensions
args = strings.GetCommandStrings(cmd.Summary, culture).Args; args = strings.GetCommandStrings(cmd.Summary, culture).Args;
} }
return Array.ConvertAll(args, return args.Map(arg => GetFullUsage(cmd.Name, arg, prefix));
arg => GetFullUsage(cmd.Name, arg, prefix));
} }
private static string GetFullUsage(string commandName, string args, string prefix) private static string GetFullUsage(string commandName, string args, string prefix)

View File

@@ -16,19 +16,22 @@ public static class MessageChannelExtensions
public static Task<IUserMessage> SendAsync( public static Task<IUserMessage> SendAsync(
this IMessageChannel channel, this IMessageChannel channel,
string? plainText, string? plainText,
Embed? embed, Embed? embed = null,
Embed[]? embeds = null,
bool sanitizeAll = false) bool sanitizeAll = false)
{ {
plainText = sanitizeAll ? plainText?.SanitizeAllMentions() ?? "" : plainText?.SanitizeMentions() ?? ""; plainText = sanitizeAll ? plainText?.SanitizeAllMentions() ?? "" : plainText?.SanitizeMentions() ?? "";
return channel.SendMessageAsync(plainText, embed: embed); return channel.SendMessageAsync(plainText, embed: embed, embeds: embeds);
} }
public static Task<IUserMessage> SendAsync(this IMessageChannel channel, SmartText text, bool sanitizeAll = false) public static Task<IUserMessage> SendAsync(this IMessageChannel channel, SmartText text, bool sanitizeAll = false)
=> text switch => text switch
{ {
SmartEmbedText set => channel.SendAsync(set.PlainText, set.GetEmbed().Build(), sanitizeAll), SmartEmbedText set => channel.SendAsync(set.PlainText, set.GetEmbed().Build(), sanitizeAll: sanitizeAll),
SmartPlainText st => channel.SendAsync(st.Text, null, sanitizeAll), SmartPlainText st => channel.SendAsync(st.Text, null, sanitizeAll: sanitizeAll),
SmartEmbedTextArray arr => channel.SendAsync(arr.PlainText,
embeds: arr.GetEmbedBuilders().Map(e => e.Build())),
_ => throw new ArgumentOutOfRangeException(nameof(text)) _ => throw new ArgumentOutOfRangeException(nameof(text))
}; };