public static async Task <bool> PromptYesNo(this ICommandContext ctx, IUserMessage message, IUser user = null, TimeSpan?timeout = null)
        {
            await message.AddReactionsAsync(new[] { new Emoji(Emojis.Success), new Emoji(Emojis.Error) });

            var reaction = await ctx.AwaitReaction(message, user ?? ctx.User, (r) => r.Emote.Name == Emojis.Success || r.Emote.Name == Emojis.Error, timeout ?? TimeSpan.FromMinutes(1));

            return(reaction.Emote.Name == Emojis.Success);
        }
        public static async Task Paginate <T>(this ICommandContext ctx, ICollection <T> items, int itemsPerPage, string title, Action <EmbedBuilder, IEnumerable <T> > renderer)
        {
            // TODO: make this generic enough we can use it in Choose<T> below

            var pageCount = (items.Count / itemsPerPage) + 1;

            Embed MakeEmbedForPage(int page)
            {
                var eb = new EmbedBuilder();

                eb.Title = pageCount > 1 ? $"[{page+1}/{pageCount}] {title}" : title;
                renderer(eb, items.Skip(page * itemsPerPage).Take(itemsPerPage));
                return(eb.Build());
            }

            var msg = await ctx.Channel.SendMessageAsync(embed : MakeEmbedForPage(0));

            if (pageCount == 1)
            {
                return;                 // If we only have one page, don't bother with the reaction/pagination logic, lol
            }
            var botEmojis = new[] { new Emoji("\u23EA"), new Emoji("\u2B05"), new Emoji("\u27A1"), new Emoji("\u23E9"), new Emoji(Emojis.Error) };
            await msg.AddReactionsAsync(botEmojis);

            try {
                var currentPage = 0;
                while (true)
                {
                    var reaction = await ctx.AwaitReaction(msg, ctx.User, timeout : TimeSpan.FromMinutes(5));

                    // Increment/decrement page counter based on which reaction was clicked
                    if (reaction.Emote.Name == "\u23EA")
                    {
                        currentPage = 0;                                  // <<
                    }
                    if (reaction.Emote.Name == "\u2B05")
                    {
                        currentPage = (currentPage - 1) % pageCount;                                  // <
                    }
                    if (reaction.Emote.Name == "\u27A1")
                    {
                        currentPage = (currentPage + 1) % pageCount;                                  // >
                    }
                    if (reaction.Emote.Name == "\u23E9")
                    {
                        currentPage = pageCount - 1;                                  // >>
                    }
                    if (reaction.Emote.Name == Emojis.Error)
                    {
                        break;                                      // X
                    }
                    // C#'s % operator is dumb and wrong, so we fix negative numbers
                    if (currentPage < 0)
                    {
                        currentPage += pageCount;
                    }

                    // If we can, remove the user's reaction (so they can press again quickly)
                    if (await ctx.HasPermission(ChannelPermission.ManageMessages) && reaction.User.IsSpecified)
                    {
                        await msg.RemoveReactionAsync(reaction.Emote, reaction.User.Value);
                    }

                    // Edit the embed with the new page
                    await msg.ModifyAsync((mp) => mp.Embed = MakeEmbedForPage(currentPage));
                }
            } catch (TimeoutException) {
                // "escape hatch", clean up as if we hit X
            }

            if (await ctx.HasPermission(ChannelPermission.ManageMessages))
            {
                await msg.RemoveAllReactionsAsync();
            }
            else
            {
                await msg.RemoveReactionsAsync(ctx.Client.CurrentUser, botEmojis);
            }
        }
        public static async Task <T> Choose <T>(this ICommandContext ctx, string description, IList <T> items, Func <T, string> display = null)
        {
            // Generate a list of :regional_indicator_?: emoji surrogate pairs (starting at codepoint 0x1F1E6)
            // We just do 7 (ABCDEFG), this amount is arbitrary (although sending a lot of emojis takes a while)
            var pageSize   = 7;
            var indicators = new string[pageSize];

            for (var i = 0; i < pageSize; i++)
            {
                indicators[i] = char.ConvertFromUtf32(0x1F1E6 + i);
            }

            // Default to x.ToString()
            if (display == null)
            {
                display = x => x.ToString();
            }

            string MakeOptionList(int page)
            {
                var makeOptionList = string.Join("\n", items
                                                 .Skip(page * pageSize)
                                                 .Take(pageSize)
                                                 .Select((x, i) => $"{indicators[i]} {display(x)}"));

                return(makeOptionList);
            }

            // If we have more items than the page size, we paginate as appropriate
            if (items.Count > pageSize)
            {
                var currPage  = 0;
                var pageCount = (items.Count - 1) / pageSize + 1;

                // Send the original message
                var msg = await ctx.Channel.SendMessageAsync($"**[Page {currPage + 1}/{pageCount}]**\n{description}\n{MakeOptionList(currPage)}");

                // Add back/forward reactions and the actual indicator emojis
                async Task AddEmojis()
                {
                    await msg.AddReactionAsync(new Emoji("\u2B05"));

                    await msg.AddReactionAsync(new Emoji("\u27A1"));

                    for (int i = 0; i < items.Count; i++)
                    {
                        await msg.AddReactionAsync(new Emoji(indicators[i]));
                    }
                }

                var _ = AddEmojis(); // Not concerned about awaiting


                while (true)
                {
                    // Wait for a reaction
                    var reaction = await ctx.AwaitReaction(msg, ctx.User);

                    // If it's a movement reaction, inc/dec the page index
                    if (reaction.Emote.Name == "\u2B05")
                    {
                        currPage -= 1;                                  // <
                    }
                    if (reaction.Emote.Name == "\u27A1")
                    {
                        currPage += 1;                                  // >
                    }
                    if (currPage < 0)
                    {
                        currPage += pageCount;
                    }
                    if (currPage >= pageCount)
                    {
                        currPage -= pageCount;
                    }

                    // If it's an indicator emoji, return the relevant item
                    if (indicators.Contains(reaction.Emote.Name))
                    {
                        var idx = Array.IndexOf(indicators, reaction.Emote.Name) + pageSize * currPage;
                        // only if it's in bounds, though
                        // eg. 8 items, we're on page 2, and I hit D (3 + 1*7 = index 10 on an 8-long list) = boom
                        if (idx < items.Count)
                        {
                            return(items[idx]);
                        }
                    }

                    var __ = msg.RemoveReactionAsync(reaction.Emote, ctx.User); // don't care about awaiting
                    await msg.ModifyAsync(mp => mp.Content = $"**[Page {currPage + 1}/{pageCount}]**\n{description}\n{MakeOptionList(currPage)}");
                }
            }
            else
            {
                var msg = await ctx.Channel.SendMessageAsync($"{description}\n{MakeOptionList(0)}");

                // 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.AddReactionAsync(new Emoji(indicators[i]));
                    }
                }

                var _ = AddEmojis();

                // Then wait for a reaction and return whichever one we found
                var reaction = await ctx.AwaitReaction(msg, ctx.User, rx => indicators.Contains(rx.Emote.Name));

                return(items[Array.IndexOf(indicators, reaction.Emote.Name)]);
            }
        }