elliebot/src/EllieBot/_common/Sender/ResponseBuilder.cs
Toastie 17d4d2a925
split warn punishments into a separate table
Added warn endpoints
Reminders should now be able to ping everyone if the user who created the reminder has that permission
2024-10-23 19:20:55 +13:00

490 lines
No EOL
13 KiB
C#

using System.Collections.ObjectModel;
namespace EllieBot.Extensions;
public sealed partial class ResponseBuilder
{
private ICommandContext? ctx;
private IMessageChannel? channel;
private string? plainText;
private IReadOnlyCollection<EmbedBuilder>? embeds;
private IUserMessage? msg;
private IUser? user;
private bool sanitizeMentions = true;
private LocStr? locTxt;
private object[] locParams = [];
private bool shouldReply = true;
private readonly IBotStrings _bs;
private readonly BotConfigService _bcs;
private EmbedBuilder? embedBuilder;
private EllieInteractionBase? inter;
private Stream? fileStream;
private string? fileName;
private EmbedColor color = EmbedColor.Ok;
private LocStr? embedLocDesc;
public DiscordSocketClient Client { get; set; }
public ResponseBuilder(IBotStrings bs, BotConfigService bcs, DiscordSocketClient client)
{
_bs = bs;
_bcs = bcs;
Client = client;
}
private MessageReference? CreateMessageReference(IMessageChannel targetChannel)
{
if (!shouldReply)
return null;
var replyTo = msg ?? ctx?.Message;
// what message are we replying to
if (replyTo is null)
return null;
// we have to have a channel where we are sending the message in order to know whether we can reply to it
if (targetChannel.Id != replyTo.Channel.Id)
return null;
return new(replyTo.Id,
replyTo.Channel.Id,
(replyTo.Channel as ITextChannel)?.GuildId,
failIfNotExists: false);
}
public async Task<ResponseMessageModel> BuildAsync(bool ephemeral)
{
var targetChannel = await InternalResolveChannel() ?? throw new ArgumentNullException(nameof(channel));
var msgReference = CreateMessageReference(targetChannel);
var txt = GetText(locTxt, targetChannel);
if (embedLocDesc is LocStr ls)
{
InternalCreateEmbed(null, GetText(ls, targetChannel));
}
if (embedBuilder is not null)
PaintEmbedInternal(embedBuilder);
var finalEmbed = embedBuilder?.Build();
var buildModel = new ResponseMessageModel()
{
TargetChannel = targetChannel,
MessageReference = msgReference,
Text = txt,
User = user ?? ctx?.User,
Embed = finalEmbed,
Embeds = embeds?.Map(x => x.Build()),
SanitizeMentions = sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All,
Ephemeral = ephemeral,
Interaction = inter
};
return buildModel;
}
public async Task<IUserMessage> SendAsync(bool ephemeral = false)
{
var model = await BuildAsync(ephemeral);
var sentMsg = await SendAsync(model);
return sentMsg;
}
public async Task<IUserMessage> SendAsync(ResponseMessageModel model)
{
IUserMessage sentMsg;
if (fileStream is Stream stream)
{
sentMsg = await model.TargetChannel.SendFileAsync(stream,
filename: fileName,
model.Text,
embed: model.Embed,
components: inter?.CreateComponent(),
allowedMentions: model.SanitizeMentions,
messageReference: model.MessageReference);
}
else
{
sentMsg = await model.TargetChannel.SendMessageAsync(
model.Text,
embed: model.Embed,
embeds: model.Embeds,
components: inter?.CreateComponent(),
allowedMentions: model.SanitizeMentions,
messageReference: model.MessageReference);
}
if (model.Interaction is not null)
{
await model.Interaction.RunAsync(sentMsg);
}
return sentMsg;
}
private EmbedBuilder PaintEmbedInternal(EmbedBuilder eb)
=> color switch
{
EmbedColor.Ok => eb.WithOkColor(),
EmbedColor.Pending => eb.WithPendingColor(),
EmbedColor.Error => eb.WithErrorColor(),
_ => throw new NotSupportedException()
};
private ulong? InternalResolveGuildId(IMessageChannel? targetChannel)
=> ctx?.Guild?.Id ?? (targetChannel as ITextChannel)?.GuildId;
private async Task<IMessageChannel?> InternalResolveChannel()
{
if (user is not null)
{
var ch = await user.CreateDMChannelAsync();
if (ch is not null)
{
return ch;
}
}
return channel ?? ctx?.Channel ?? msg?.Channel;
}
private string? GetText(LocStr? locStr, IMessageChannel targetChannel)
{
var guildId = InternalResolveGuildId(targetChannel);
return locStr is LocStr ls ? _bs.GetText(ls.Key, guildId, locParams) : plainText;
}
private string GetText(LocStr locStr, IMessageChannel targetChannel)
{
var guildId = InternalResolveGuildId(targetChannel);
return _bs.GetText(locStr.Key, guildId, locStr.Params);
}
public ResponseBuilder Text(LocStr str)
{
locTxt = str;
return this;
}
public ResponseBuilder Text(SmartText text)
{
if (text is SmartPlainText spt)
plainText = spt.Text;
else if (text is SmartEmbedText set)
{
plainText = set.PlainText ?? plainText;
embedBuilder = set.GetEmbed();
}
else if (text is SmartEmbedTextArray ser)
{
plainText = ser.Content ?? plainText;
embeds = ser.GetEmbedBuilders();
}
return this;
}
private void InternalCreateEmbed(
string? title,
string text,
string? url = null,
string? footer = null)
{
var eb = new EllieEmbedBuilder(_bcs)
.WithDescription(text);
if (!string.IsNullOrWhiteSpace(title))
eb.WithTitle(title);
if (!string.IsNullOrWhiteSpace(url))
eb = eb.WithUrl(url);
if (!string.IsNullOrWhiteSpace(footer))
eb = eb.WithFooter(footer);
embedBuilder = eb;
}
public ResponseBuilder Confirm(
string? title,
string text,
string? url = null,
string? footer = null)
{
InternalCreateEmbed(title, text, url, footer);
color = EmbedColor.Ok;
return this;
}
public ResponseBuilder Error(
string? title,
string text,
string? url = null,
string? footer = null)
{
InternalCreateEmbed(title, text, url, footer);
color = EmbedColor.Error;
return this;
}
public ResponseBuilder Pending(
string? title,
string text,
string? url = null,
string? footer = null)
{
InternalCreateEmbed(title, text, url, footer);
color = EmbedColor.Pending;
return this;
}
public ResponseBuilder Confirm(string text)
{
InternalCreateEmbed(null, text);
color = EmbedColor.Ok;
return this;
}
public ResponseBuilder Confirm(LocStr str)
{
embedLocDesc = str;
color = EmbedColor.Ok;
return this;
}
public ResponseBuilder Pending(string text)
{
InternalCreateEmbed(null, text);
color = EmbedColor.Pending;
return this;
}
public ResponseBuilder Pending(LocStr str)
{
embedLocDesc = str;
color = EmbedColor.Pending;
return this;
}
public ResponseBuilder Error(string text)
{
InternalCreateEmbed(null, text);
color = EmbedColor.Error;
return this;
}
public ResponseBuilder Error(LocStr str)
{
embedLocDesc = str;
color = EmbedColor.Error;
return this;
}
public ResponseBuilder UserBasedMentions(IGuildUser? permUser = null)
{
sanitizeMentions = !((InternalResolveUser() as IGuildUser ?? permUser)?.GuildPermissions.MentionEveryone ?? false);
return this;
}
private IUser? InternalResolveUser()
=> ctx?.User ?? user ?? msg?.Author;
public ResponseBuilder Embed(EmbedBuilder eb)
{
embedBuilder = eb;
return this;
}
public ResponseBuilder Channel(IMessageChannel ch)
{
channel = ch;
return this;
}
public ResponseBuilder Sanitize(bool shouldSantize = true)
{
sanitizeMentions = shouldSantize;
return this;
}
public ResponseBuilder Context(ICommandContext context)
{
ctx = context;
return this;
}
public ResponseBuilder Message(IUserMessage message)
{
msg = message;
return this;
}
public ResponseBuilder User(IUser usr)
{
user = usr;
return this;
}
public ResponseBuilder NoReply()
{
shouldReply = false;
return this;
}
public ResponseBuilder Interaction(EllieInteractionBase? interaction)
{
inter = interaction;
return this;
}
public ResponseBuilder Embeds(IReadOnlyCollection<EmbedBuilder> inputEmbeds)
{
embeds = inputEmbeds;
return this;
}
public ResponseBuilder File(Stream stream, string name)
{
fileStream = stream;
fileName = name;
return this;
}
public PaginatedResponseBuilder Paginated()
=> new(this);
}
public class PaginatedResponseBuilder
{
protected readonly ResponseBuilder _builder;
public PaginatedResponseBuilder(ResponseBuilder builder)
{
_builder = builder;
}
public SourcedPaginatedResponseBuilder<T> Items<T>(IReadOnlyCollection<T> items)
=> new SourcedPaginatedResponseBuilder<T>(_builder)
.Items(items);
public SourcedPaginatedResponseBuilder<T> PageItems<T>(Func<int, Task<IReadOnlyCollection<T>>> items)
=> new SourcedPaginatedResponseBuilder<T>(_builder)
.PageItems(items);
}
public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilder
{
private IReadOnlyCollection<T>? items;
public Func<IReadOnlyList<T>, int, Task<EmbedBuilder>> PageFunc { get; private set; } = static delegate
{
return Task.FromResult<EmbedBuilder>(new());
};
public Func<int, Task<IReadOnlyCollection<T>>> ItemsFunc { get; set; } = static delegate
{
return Task.FromResult<IReadOnlyCollection<T>>(ReadOnlyCollection<T>.Empty);
};
public Func<int, Task<EllieInteractionBase>>? InteractionFunc { get; private set; }
public int? Elems { get; private set; } = 1;
public int ItemsPerPage { get; private set; } = 9;
public bool AddPaginatedFooter { get; private set; } = true;
public bool IsEphemeral { get; private set; }
public int InitialPage { get; set; }
public SourcedPaginatedResponseBuilder(ResponseBuilder builder)
: base(builder)
{
}
public SourcedPaginatedResponseBuilder<T> Items(IReadOnlyCollection<T> col)
{
items = col;
Elems = col.Count;
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage).ToArray() as IReadOnlyCollection<T>);
return this;
}
public SourcedPaginatedResponseBuilder<T> TotalElements(int i)
{
Elems = i;
return this;
}
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IReadOnlyCollection<T>>> func)
{
Elems = null;
ItemsFunc = func;
return this;
}
public SourcedPaginatedResponseBuilder<T> PageSize(int i)
{
ItemsPerPage = i;
return this;
}
public SourcedPaginatedResponseBuilder<T> CurrentPage(int i)
{
InitialPage = i;
return this;
}
public SourcedPaginatedResponseBuilder<T> Page(Func<IReadOnlyList<T>, int, EmbedBuilder> pageFunc)
{
PageFunc = (xs, x) => Task.FromResult(pageFunc(xs, x));
return this;
}
public SourcedPaginatedResponseBuilder<T> Page(Func<IReadOnlyList<T>, int, Task<EmbedBuilder>> pageFunc)
{
PageFunc = pageFunc;
return this;
}
public SourcedPaginatedResponseBuilder<T> AddFooter(bool addFooter = true)
{
AddPaginatedFooter = addFooter;
return this;
}
public SourcedPaginatedResponseBuilder<T> Ephemeral()
{
IsEphemeral = true;
return this;
}
public Task SendAsync()
{
var paginationSender = new ResponseBuilder.PaginationSender<T>(
this,
_builder);
return paginationSender.SendAsync(IsEphemeral);
}
public SourcedPaginatedResponseBuilder<T> Interaction(Func<int, Task<EllieInteractionBase>> func)
{
InteractionFunc = func; //async (i) => await func(i);
return this;
}
public SourcedPaginatedResponseBuilder<T> Interaction(EllieInteractionBase inter)
{
InteractionFunc = _ => Task.FromResult(inter);
return this;
}
}