Added warn endpoints Reminders should now be able to ping everyone if the user who created the reminder has that permission
490 lines
No EOL
13 KiB
C#
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;
|
|
}
|
|
} |