Port more things!

This commit is contained in:
Ske
2020-12-24 14:52:44 +01:00
parent f6fb8204bb
commit 47b16dc51b
26 changed files with 332 additions and 186 deletions

View File

@@ -6,19 +6,17 @@ using System.Threading.Tasks;
using Autofac;
using DSharpPlus;
using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
using DSharpPlus.Exceptions;
using Myriad.Builders;
using Myriad.Gateway;
using Myriad.Rest.Exceptions;
using Myriad.Rest.Types;
using Myriad.Rest.Types.Requests;
using Myriad.Types;
using NodaTime;
using PluralKit.Core;
using Permissions = DSharpPlus.Permissions;
namespace PluralKit.Bot {
public static class ContextUtils {
public static async Task<bool> ConfirmClear(this Context ctx, string toClear)
@@ -27,52 +25,45 @@ namespace PluralKit.Bot {
else return true;
}
public static async Task<bool> PromptYesNo(this Context ctx, String msgString, DiscordUser user = null, Duration? timeout = null, IEnumerable<IMention> mentions = null, bool matchFlag = true)
public static async Task<bool> PromptYesNo(this Context ctx, string msgString, User user = null, Duration? timeout = null, AllowedMentions mentions = null, bool matchFlag = true)
{
DiscordMessage message;
Message message;
if (matchFlag && ctx.MatchFlag("y", "yes")) return true;
else message = await ctx.Reply(msgString, mentions: mentions);
var cts = new CancellationTokenSource();
if (user == null) user = ctx.Author;
if (user == null) user = ctx.AuthorNew;
if (timeout == null) timeout = Duration.FromMinutes(5);
// "Fork" the task adding the reactions off so we don't have to wait for them to be finished to start listening for presses
var _ = message.CreateReactionsBulk(new[] {Emojis.Success, Emojis.Error});
await ctx.RestNew.CreateReactionsBulk(message, new[] {Emojis.Success, Emojis.Error});
bool ReactionPredicate(MessageReactionAddEventArgs e)
bool ReactionPredicate(MessageReactionAddEvent e)
{
if (e.Channel.Id != message.ChannelId || e.Message.Id != message.Id) return false;
if (e.User.Id != user.Id) return false;
if (e.ChannelId != message.ChannelId || e.MessageId != message.Id) return false;
if (e.UserId != user.Id) return false;
return true;
}
bool MessagePredicate(MessageCreateEventArgs e)
bool MessagePredicate(MessageCreateEvent e)
{
if (e.Channel.Id != message.ChannelId) return false;
if (e.ChannelId != message.ChannelId) return false;
if (e.Author.Id != user.Id) return false;
var strings = new [] {"y", "yes", "n", "no"};
foreach (var str in strings)
if (e.Message.Content.Equals(str, StringComparison.InvariantCultureIgnoreCase))
return true;
return false;
return strings.Any(str => string.Equals(e.Content, str, StringComparison.InvariantCultureIgnoreCase));
}
var messageTask = ctx.Services.Resolve<HandlerQueue<MessageCreateEventArgs>>().WaitFor(MessagePredicate, timeout, cts.Token);
var reactionTask = ctx.Services.Resolve<HandlerQueue<MessageReactionAddEventArgs>>().WaitFor(ReactionPredicate, timeout, cts.Token);
var messageTask = ctx.Services.Resolve<HandlerQueue<MessageCreateEvent>>().WaitFor(MessagePredicate, timeout, cts.Token);
var reactionTask = ctx.Services.Resolve<HandlerQueue<MessageReactionAddEvent>>().WaitFor(ReactionPredicate, timeout, cts.Token);
var theTask = await Task.WhenAny(messageTask, reactionTask);
cts.Cancel();
if (theTask == messageTask)
{
var responseMsg = (await messageTask).Message;
var responseMsg = (await messageTask);
var positives = new[] {"y", "yes"};
foreach (var p in positives)
if (responseMsg.Content.Equals(p, StringComparison.InvariantCultureIgnoreCase))
return true;
return false;
return positives.Any(p => string.Equals(responseMsg.Content, p, StringComparison.InvariantCultureIgnoreCase));
}
if (theTask == reactionTask)
@@ -81,50 +72,45 @@ namespace PluralKit.Bot {
return false;
}
public static async Task<MessageReactionAddEventArgs> AwaitReaction(this Context ctx, DiscordMessage message, DiscordUser user = null, Func<MessageReactionAddEventArgs, bool> predicate = null, TimeSpan? timeout = null) {
var tcs = new TaskCompletionSource<MessageReactionAddEventArgs>();
Task Inner(DiscordClient _, MessageReactionAddEventArgs args) {
if (message.Id != args.Message.Id) return Task.CompletedTask; // Ignore reactions for different messages
if (user != null && user.Id != args.User.Id) return Task.CompletedTask; // Ignore messages from other users if a user was defined
if (predicate != null && !predicate.Invoke(args)) return Task.CompletedTask; // Check predicate
tcs.SetResult(args);
return Task.CompletedTask;
}
ctx.Shard.MessageReactionAdded += Inner;
try {
return await tcs.Task.TimeoutAfter(timeout);
} finally {
ctx.Shard.MessageReactionAdded -= Inner;
public static async Task<MessageReactionAddEvent> AwaitReaction(this Context ctx, Message message, User user = null, Func<MessageReactionAddEvent, bool> predicate = null, Duration? timeout = null)
{
bool ReactionPredicate(MessageReactionAddEvent evt)
{
if (message.Id != evt.MessageId) return false; // Ignore reactions for different messages
if (user != null && user.Id != evt.UserId) return false; // Ignore messages from other users if a user was defined
if (predicate != null && !predicate.Invoke(evt)) return false; // Check predicate
return true;
}
return await ctx.Services.Resolve<HandlerQueue<MessageReactionAddEvent>>().WaitFor(ReactionPredicate, timeout);
}
public static async Task<bool> ConfirmWithReply(this Context ctx, string expectedReply)
{
bool Predicate(MessageCreateEventArgs e) =>
e.Author == ctx.Author && e.Channel.Id == ctx.Channel.Id;
bool Predicate(MessageCreateEvent e) =>
e.Author.Id == ctx.AuthorNew.Id && e.ChannelId == ctx.Channel.Id;
var msg = await ctx.Services.Resolve<HandlerQueue<MessageCreateEventArgs>>()
var msg = await ctx.Services.Resolve<HandlerQueue<MessageCreateEvent>>()
.WaitFor(Predicate, Duration.FromMinutes(1));
return string.Equals(msg.Message.Content, expectedReply, StringComparison.InvariantCultureIgnoreCase);
return string.Equals(msg.Content, expectedReply, StringComparison.InvariantCultureIgnoreCase);
}
public static async Task Paginate<T>(this Context ctx, IAsyncEnumerable<T> items, int totalCount, int itemsPerPage, string title, Func<DiscordEmbedBuilder, IEnumerable<T>, Task> renderer) {
public static async Task Paginate<T>(this Context ctx, IAsyncEnumerable<T> items, int totalCount, int itemsPerPage, string title, Func<EmbedBuilder, IEnumerable<T>, Task> renderer) {
// TODO: make this generic enough we can use it in Choose<T> below
var buffer = new List<T>();
await using var enumerator = items.GetAsyncEnumerator();
var pageCount = (int) Math.Ceiling(totalCount / (double) itemsPerPage);
async Task<DiscordEmbed> MakeEmbedForPage(int page)
async Task<Embed> MakeEmbedForPage(int page)
{
var bufferedItemsNeeded = (page + 1) * itemsPerPage;
while (buffer.Count < bufferedItemsNeeded && await enumerator.MoveNextAsync())
buffer.Add(enumerator.Current);
var eb = new DiscordEmbedBuilder();
eb.Title = pageCount > 1 ? $"[{page+1}/{pageCount}] {title}" : title;
var eb = new EmbedBuilder();
eb.Title(pageCount > 1 ? $"[{page+1}/{pageCount}] {title}" : title);
await renderer(eb, buffer.Skip(page*itemsPerPage).Take(itemsPerPage));
return eb.Build();
}
@@ -134,13 +120,13 @@ namespace PluralKit.Bot {
var msg = await ctx.Reply(embed: await MakeEmbedForPage(0));
if (pageCount <= 1) return; // If we only have one (or no) page, don't bother with the reaction/pagination logic, lol
string[] botEmojis = { "\u23EA", "\u2B05", "\u27A1", "\u23E9", Emojis.Error };
var _ = msg.CreateReactionsBulk(botEmojis); // Again, "fork"
var _ = ctx.RestNew.CreateReactionsBulk(msg, botEmojis); // Again, "fork"
try {
var currentPage = 0;
while (true) {
var reaction = await ctx.AwaitReaction(msg, ctx.Author, timeout: TimeSpan.FromMinutes(5));
var reaction = await ctx.AwaitReaction(msg, ctx.AuthorNew, timeout: Duration.FromMinutes(5));
// Increment/decrement page counter based on which reaction was clicked
if (reaction.Emoji.Name == "\u23EA") currentPage = 0; // <<
@@ -154,18 +140,18 @@ namespace PluralKit.Bot {
// If we can, remove the user's reaction (so they can press again quickly)
if (ctx.BotPermissions.HasFlag(PermissionSet.ManageMessages))
await msg.DeleteReactionAsync(reaction.Emoji, reaction.User);
await ctx.RestNew.DeleteUserReaction(msg.ChannelId, msg.Id, reaction.Emoji, reaction.UserId);
// Edit the embed with the new page
var embed = await MakeEmbedForPage(currentPage);
await msg.ModifyAsync(embed: embed);
await ctx.RestNew.EditMessage(msg.ChannelId, msg.Id, new MessageEditRequest {Embed = embed});
}
} catch (TimeoutException) {
// "escape hatch", clean up as if we hit X
}
if (ctx.BotPermissions.HasFlag(PermissionSet.ManageMessages))
await msg.DeleteAllReactionsAsync();
await ctx.RestNew.DeleteAllReactions(msg.ChannelId, msg.Id);
}
// If we get a "NotFound" error, the message has been deleted and thus not our problem
catch (NotFoundException) { }
@@ -203,9 +189,10 @@ namespace PluralKit.Bot {
// Add back/forward reactions and the actual indicator emojis
async Task AddEmojis()
{
await msg.CreateReactionAsync(DiscordEmoji.FromUnicode("\u2B05"));
await msg.CreateReactionAsync(DiscordEmoji.FromUnicode("\u27A1"));
for (int i = 0; i < items.Count; i++) await msg.CreateReactionAsync(DiscordEmoji.FromUnicode(indicators[i]));
await ctx.RestNew.CreateReaction(msg.ChannelId, msg.Id, new() { Name = "\u2B05" });
await ctx.RestNew.CreateReaction(msg.ChannelId, msg.Id, new() { Name = "\u27A1" });
for (int i = 0; i < items.Count; i++)
await ctx.RestNew.CreateReaction(msg.ChannelId, msg.Id, new() { Name = indicators[i] });
}
var _ = AddEmojis(); // Not concerned about awaiting
@@ -213,7 +200,7 @@ namespace PluralKit.Bot {
while (true)
{
// Wait for a reaction
var reaction = await ctx.AwaitReaction(msg, ctx.Author);
var reaction = await ctx.AwaitReaction(msg, ctx.AuthorNew);
// If it's a movement reaction, inc/dec the page index
if (reaction.Emoji.Name == "\u2B05") currPage -= 1; // <
@@ -230,8 +217,13 @@ namespace PluralKit.Bot {
if (idx < items.Count) return items[idx];
}
var __ = msg.DeleteReactionAsync(reaction.Emoji, ctx.Author); // don't care about awaiting
await msg.ModifyAsync($"**[Page {currPage + 1}/{pageCount}]**\n{description}\n{MakeOptionList(currPage)}");
var __ = ctx.RestNew.DeleteUserReaction(msg.ChannelId, msg.Id, reaction.Emoji, ctx.Author.Id);
await ctx.RestNew.EditMessage(msg.ChannelId, msg.Id,
new()
{
Content =
$"**[Page {currPage + 1}/{pageCount}]**\n{description}\n{MakeOptionList(currPage)}"
});
}
}
else
@@ -241,13 +233,14 @@ namespace PluralKit.Bot {
// Add the relevant reactions (we don't care too much about awaiting)
async Task AddEmojis()
{
for (int i = 0; i < items.Count; i++) await msg.CreateReactionAsync(DiscordEmoji.FromUnicode(indicators[i]));
for (int i = 0; i < items.Count; i++)
await ctx.RestNew.CreateReaction(msg.ChannelId, msg.Id, new() {Name = indicators[i]});
}
var _ = AddEmojis();
// Then wait for a reaction and return whichever one we found
var reaction = await ctx.AwaitReaction(msg, ctx.Author,rx => indicators.Contains(rx.Emoji.Name));
var reaction = await ctx.AwaitReaction(msg, ctx.AuthorNew,rx => indicators.Contains(rx.Emoji.Name));
return items[Array.IndexOf(indicators, reaction.Emoji.Name)];
}
}
@@ -271,12 +264,12 @@ namespace PluralKit.Bot {
try
{
await Task.WhenAll(ctx.Message.CreateReactionAsync(DiscordEmoji.FromUnicode(emoji)), task);
await Task.WhenAll(ctx.RestNew.CreateReaction(ctx.MessageNew.ChannelId, ctx.MessageNew.Id, new() {Name = emoji}), task);
return await task;
}
finally
{
var _ = ctx.Message.DeleteReactionAsync(DiscordEmoji.FromUnicode(emoji), ctx.Shard.CurrentUser);
var _ = ctx.RestNew.DeleteOwnReaction(ctx.MessageNew.ChannelId, ctx.MessageNew.Id, new() { Name = emoji });
}
}
}

View File

@@ -12,7 +12,9 @@ using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
using DSharpPlus.Exceptions;
using Myriad.Builders;
using Myriad.Extensions;
using Myriad.Rest;
using Myriad.Rest.Types;
using Myriad.Types;
@@ -116,11 +118,11 @@ namespace PluralKit.Bot
public static ulong InstantToSnowflake(DateTimeOffset time) =>
(ulong) (time - new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalMilliseconds << 22;
public static async Task CreateReactionsBulk(this DiscordMessage msg, string[] reactions)
public static async Task CreateReactionsBulk(this DiscordApiClient rest, Message msg, string[] reactions)
{
foreach (var reaction in reactions)
{
await msg.CreateReactionAsync(DiscordEmoji.FromUnicode(reaction));
await rest.CreateReaction(msg.ChannelId, msg.Id, new() {Name = reaction});
}
}
@@ -329,7 +331,7 @@ namespace PluralKit.Bot
}
}
public static DiscordEmbedBuilder WithSimpleLineContent(this DiscordEmbedBuilder eb, IEnumerable<string> lines)
public static EmbedBuilder WithSimpleLineContent(this EmbedBuilder eb, IEnumerable<string> lines)
{
static int CharacterLimit(int pageNumber) =>
// First chunk goes in description (2048 chars), rest go in embed values (1000 chars)
@@ -340,11 +342,11 @@ namespace PluralKit.Bot
// Add the first page to the embed description
if (pages.Count > 0)
eb.WithDescription(pages[0]);
eb.Description(pages[0]);
// Add the rest to blank-named (\u200B) fields
for (var i = 1; i < pages.Count; i++)
eb.AddField("\u200B", pages[i]);
eb.Field(new("\u200B", pages[i]));
return eb;
}