mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Global usings and file scoped namespaces
This commit is contained in:
		@@ -1,24 +1,21 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
// made for customreactions because they almost never get added
 | 
			
		||||
// and they get looped through constantly
 | 
			
		||||
public static class ArrayExtensions
 | 
			
		||||
{
 | 
			
		||||
    // made for customreactions because they almost never get added
 | 
			
		||||
    // and they get looped through constantly
 | 
			
		||||
    public static class ArrayExtensions
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Create a new array from the old array + new element at the end
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="input">Input array</param>
 | 
			
		||||
    /// <param name="added">Item to add to the end of the output array</param>
 | 
			
		||||
    /// <typeparam name="T">Type of the array</typeparam>
 | 
			
		||||
    /// <returns>A new array with the new element at the end</returns>
 | 
			
		||||
    public static T[] With<T>(this T[] input, T added)
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Create a new array from the old array + new element at the end
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="input">Input array</param>
 | 
			
		||||
        /// <param name="added">Item to add to the end of the output array</param>
 | 
			
		||||
        /// <typeparam name="T">Type of the array</typeparam>
 | 
			
		||||
        /// <returns>A new array with the new element at the end</returns>
 | 
			
		||||
        public static T[] With<T>(this T[] input, T added)
 | 
			
		||||
        {
 | 
			
		||||
            var newCrs = new T[input.Length + 1];
 | 
			
		||||
            Array.Copy(input, 0, newCrs, 0, input.Length);
 | 
			
		||||
            newCrs[input.Length] = added;
 | 
			
		||||
            return newCrs;
 | 
			
		||||
        }
 | 
			
		||||
        var newCrs = new T[input.Length + 1];
 | 
			
		||||
        Array.Copy(input, 0, newCrs, 0, input.Length);
 | 
			
		||||
        newCrs[input.Length] = added;
 | 
			
		||||
        return newCrs;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,6 @@ using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Common.Collections;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Modules.Administration.Services;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using SixLabors.Fonts;
 | 
			
		||||
using SixLabors.ImageSharp;
 | 
			
		||||
using SixLabors.ImageSharp.Drawing;
 | 
			
		||||
@@ -14,11 +13,8 @@ using SixLabors.ImageSharp.Formats;
 | 
			
		||||
using SixLabors.ImageSharp.Formats.Png;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
using SixLabors.ImageSharp.Processing;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net.Http.Headers;
 | 
			
		||||
using System.Text;
 | 
			
		||||
@@ -29,360 +25,359 @@ using NadekoBot.Common.Attributes;
 | 
			
		||||
using Color = Discord.Color;
 | 
			
		||||
using JsonSerializer = System.Text.Json.JsonSerializer;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class Extensions
 | 
			
		||||
{
 | 
			
		||||
    public static class Extensions
 | 
			
		||||
    public static Regex UrlRegex = new Regex(@"^(https?|ftp)://(?<path>[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
    public static TOut[] Map<TIn, TOut>(this TIn[] arr, Func<TIn, TOut> f)
 | 
			
		||||
        => Array.ConvertAll(arr, x => f(x));
 | 
			
		||||
 | 
			
		||||
    public static Task EditAsync(this IUserMessage msg, SmartText text)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
            SmartEmbedText set => msg.ModifyAsync(x =>
 | 
			
		||||
            {
 | 
			
		||||
                x.Embed = set.GetEmbed().Build();
 | 
			
		||||
                x.Content = set.PlainText?.SanitizeMentions() ?? "";
 | 
			
		||||
            }),
 | 
			
		||||
            SmartPlainText spt => msg.ModifyAsync(x =>
 | 
			
		||||
            {
 | 
			
		||||
                x.Content = spt.Text.SanitizeMentions();
 | 
			
		||||
                x.Embed = null;
 | 
			
		||||
            }),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
    public static Task<IUserMessage> SendAsync(this IMessageChannel channel, string plainText, Embed embed, bool sanitizeAll = false)
 | 
			
		||||
    {
 | 
			
		||||
        public static Regex UrlRegex = new Regex(@"^(https?|ftp)://(?<path>[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled);
 | 
			
		||||
        plainText = sanitizeAll
 | 
			
		||||
            ? plainText?.SanitizeAllMentions() ?? ""
 | 
			
		||||
            : plainText?.SanitizeMentions() ?? "";
 | 
			
		||||
 | 
			
		||||
        public static TOut[] Map<TIn, TOut>(this TIn[] arr, Func<TIn, TOut> f)
 | 
			
		||||
            => Array.ConvertAll(arr, x => f(x));
 | 
			
		||||
 | 
			
		||||
        public static Task EditAsync(this IUserMessage msg, SmartText text)
 | 
			
		||||
            => text switch
 | 
			
		||||
            {
 | 
			
		||||
                SmartEmbedText set => msg.ModifyAsync(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.Embed = set.GetEmbed().Build();
 | 
			
		||||
                    x.Content = set.PlainText?.SanitizeMentions() ?? "";
 | 
			
		||||
                }),
 | 
			
		||||
                SmartPlainText spt => msg.ModifyAsync(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.Content = spt.Text.SanitizeMentions();
 | 
			
		||||
                    x.Embed = null;
 | 
			
		||||
                }),
 | 
			
		||||
                _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
            };
 | 
			
		||||
        
 | 
			
		||||
        public static Task<IUserMessage> SendAsync(this IMessageChannel channel, string plainText, Embed embed, bool sanitizeAll = false)
 | 
			
		||||
        {
 | 
			
		||||
            plainText = sanitizeAll
 | 
			
		||||
                ? plainText?.SanitizeAllMentions() ?? ""
 | 
			
		||||
                : plainText?.SanitizeMentions() ?? "";
 | 
			
		||||
 | 
			
		||||
            return channel.SendMessageAsync(plainText, embed: embed);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static Task<IUserMessage> SendAsync(this IMessageChannel channel, SmartText text, bool sanitizeAll = false)
 | 
			
		||||
            => text switch
 | 
			
		||||
            {
 | 
			
		||||
                SmartEmbedText set => channel.SendAsync(set.PlainText, set.GetEmbed().Build(), sanitizeAll),
 | 
			
		||||
                SmartPlainText st => channel.SendAsync(st.Text, null, sanitizeAll),
 | 
			
		||||
                _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        public static List<ulong> GetGuildIds(this DiscordSocketClient client)
 | 
			
		||||
            => client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Generates a string in the format HHH:mm if timespan is <= 2m.
 | 
			
		||||
        /// Generates a string in the format 00:mm:ss if timespan is less than 2m.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="span">Timespan to convert to string</param>
 | 
			
		||||
        /// <returns>Formatted duration string</returns>
 | 
			
		||||
        public static string ToPrettyStringHM(this TimeSpan span)
 | 
			
		||||
        {
 | 
			
		||||
            if (span < TimeSpan.FromMinutes(2))
 | 
			
		||||
                return $"{span:mm}m {span:ss}s";
 | 
			
		||||
            return $"{(int) span.TotalHours:D2}h {span:mm}m";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool TryGetUrlPath(this string input, out string path)
 | 
			
		||||
        {
 | 
			
		||||
            var match = UrlRegex.Match(input);
 | 
			
		||||
            if (match.Success)
 | 
			
		||||
            {
 | 
			
		||||
                path = match.Groups["path"].Value;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            path = string.Empty;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IEmote ToIEmote(this string emojiStr)
 | 
			
		||||
            => Emote.TryParse(emojiStr, out var maybeEmote)
 | 
			
		||||
                    ? (IEmote)maybeEmote
 | 
			
		||||
                    : new Emoji(emojiStr);
 | 
			
		||||
 | 
			
		||||
        // https://github.com/SixLabors/Samples/blob/master/ImageSharp/AvatarWithRoundedCorner/Program.cs
 | 
			
		||||
        public static IImageProcessingContext ApplyRoundedCorners(this IImageProcessingContext ctx, float cornerRadius)
 | 
			
		||||
        {
 | 
			
		||||
            Size size = ctx.GetCurrentSize();
 | 
			
		||||
            IPathCollection corners = BuildCorners(size.Width, size.Height, cornerRadius);
 | 
			
		||||
 | 
			
		||||
            ctx.SetGraphicsOptions(new GraphicsOptions()
 | 
			
		||||
            {
 | 
			
		||||
                Antialias = true,
 | 
			
		||||
                AlphaCompositionMode = PixelAlphaCompositionMode.DestOut // enforces that any part of this shape that has color is punched out of the background
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            foreach (var c in corners)
 | 
			
		||||
            {
 | 
			
		||||
                ctx = ctx.Fill(SixLabors.ImageSharp.Color.Red, c);
 | 
			
		||||
            }
 | 
			
		||||
            return ctx;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius)
 | 
			
		||||
        {
 | 
			
		||||
            // first create a square
 | 
			
		||||
            var rect = new RectangularPolygon(-0.5f, -0.5f, cornerRadius, cornerRadius);
 | 
			
		||||
 | 
			
		||||
            // then cut out of the square a circle so we are left with a corner
 | 
			
		||||
            IPath cornerTopLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius));
 | 
			
		||||
 | 
			
		||||
            // corner is now a corner shape positions top left
 | 
			
		||||
            //lets make 3 more positioned correctly, we can do that by translating the original around the center of the image
 | 
			
		||||
 | 
			
		||||
            float rightPos = imageWidth - cornerTopLeft.Bounds.Width + 1;
 | 
			
		||||
            float bottomPos = imageHeight - cornerTopLeft.Bounds.Height + 1;
 | 
			
		||||
 | 
			
		||||
            // move it across the width of the image - the width of the shape
 | 
			
		||||
            IPath cornerTopRight = cornerTopLeft.RotateDegree(90).Translate(rightPos, 0);
 | 
			
		||||
            IPath cornerBottomLeft = cornerTopLeft.RotateDegree(-90).Translate(0, bottomPos);
 | 
			
		||||
            IPath cornerBottomRight = cornerTopLeft.RotateDegree(180).Translate(rightPos, bottomPos);
 | 
			
		||||
 | 
			
		||||
            return new PathCollection(cornerTopLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// First 10 characters of teh bot token.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static string RedisKey(this IBotCredentials bc)
 | 
			
		||||
        {
 | 
			
		||||
            return bc.Token.Substring(0, 10);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> replacementFn)
 | 
			
		||||
        {
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            var lastIndex = 0;
 | 
			
		||||
 | 
			
		||||
            foreach (Match match in regex.Matches(input))
 | 
			
		||||
            {
 | 
			
		||||
                sb.Append(input, lastIndex, match.Index - lastIndex)
 | 
			
		||||
                  .Append(await replacementFn(match).ConfigureAwait(false));
 | 
			
		||||
 | 
			
		||||
                lastIndex = match.Index + match.Length;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sb.Append(input, lastIndex, input.Length - lastIndex);
 | 
			
		||||
            return sb.ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void ThrowIfNull<T>(this T o, string name) where T : class
 | 
			
		||||
        {
 | 
			
		||||
            if (o is null)
 | 
			
		||||
                throw new ArgumentNullException(nameof(name));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool IsAuthor(this IMessage msg, IDiscordClient client)
 | 
			
		||||
            => msg.Author?.Id == client.CurrentUser.Id;
 | 
			
		||||
 | 
			
		||||
        public static string RealSummary(this CommandInfo cmd, IBotStrings strings, ulong? guildId, string prefix)
 | 
			
		||||
            => string.Format(strings.GetCommandStrings(cmd.Name, guildId).Desc, prefix);
 | 
			
		||||
 | 
			
		||||
        public static string[] RealRemarksArr(this CommandInfo cmd, IBotStrings strings, ulong? guildId, string prefix)
 | 
			
		||||
            => Array.ConvertAll(strings.GetCommandStrings(cmd.MethodName(), guildId).Args,
 | 
			
		||||
                arg => GetFullUsage(cmd.Name, arg, prefix));
 | 
			
		||||
 | 
			
		||||
        public static string MethodName(this CommandInfo cmd)
 | 
			
		||||
            => ((NadekoCommandAttribute) cmd.Attributes.FirstOrDefault(x => x is NadekoCommandAttribute))?.MethodName
 | 
			
		||||
               ?? cmd.Name;
 | 
			
		||||
        // public static string RealRemarks(this CommandInfo cmd, IBotStrings strings, string prefix)
 | 
			
		||||
        //     => string.Join('\n', cmd.RealRemarksArr(strings, prefix));
 | 
			
		||||
 | 
			
		||||
        public static string GetFullUsage(string commandName, string args, string prefix)
 | 
			
		||||
            => $"{prefix}{commandName} {string.Format(args, prefix)}";
 | 
			
		||||
 | 
			
		||||
        public static IEmbedBuilder AddPaginatedFooter(this IEmbedBuilder embed, int curPage, int? lastPage)
 | 
			
		||||
        {
 | 
			
		||||
            if (lastPage != null)
 | 
			
		||||
                return embed.WithFooter($"{curPage + 1} / {lastPage + 1}");
 | 
			
		||||
            else
 | 
			
		||||
                return embed.WithFooter(curPage.ToString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Color ToDiscordColor(this Rgba32 color)
 | 
			
		||||
            => new Color(color.R, color.G, color.B);
 | 
			
		||||
        
 | 
			
		||||
        public static IEmbedBuilder WithOkColor(this IEmbedBuilder eb) =>
 | 
			
		||||
            eb.WithColor(EmbedColor.Ok);
 | 
			
		||||
 | 
			
		||||
        public static IEmbedBuilder WithPendingColor(this IEmbedBuilder eb) =>
 | 
			
		||||
            eb.WithColor(EmbedColor.Pending);
 | 
			
		||||
        
 | 
			
		||||
        public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb) =>
 | 
			
		||||
            eb.WithColor(EmbedColor.Error);
 | 
			
		||||
 | 
			
		||||
        public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Func<SocketReaction, Task> reactionAdded, Func<SocketReaction, Task> reactionRemoved = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (reactionRemoved is null)
 | 
			
		||||
                reactionRemoved = _ => Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            var wrap = new ReactionEventWrapper(client, msg);
 | 
			
		||||
            wrap.OnReactionAdded += (r) => { var _ = Task.Run(() => reactionAdded(r)); };
 | 
			
		||||
            wrap.OnReactionRemoved += (r) => { var _ = Task.Run(() => reactionRemoved(r)); };
 | 
			
		||||
            return wrap;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static HttpClient AddFakeHeaders(this HttpClient http)
 | 
			
		||||
        {
 | 
			
		||||
            AddFakeHeaders(http.DefaultRequestHeaders);
 | 
			
		||||
            return http;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void AddFakeHeaders(this HttpHeaders dict)
 | 
			
		||||
        {
 | 
			
		||||
            dict.Clear();
 | 
			
		||||
            dict.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
 | 
			
		||||
            dict.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IMessage DeleteAfter(this IUserMessage msg, int seconds, ILogCommandService logService = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (msg is null)
 | 
			
		||||
                return null;
 | 
			
		||||
            
 | 
			
		||||
            Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                await Task.Delay(seconds * 1000).ConfigureAwait(false);
 | 
			
		||||
                if(logService != null)
 | 
			
		||||
                {
 | 
			
		||||
                    logService.AddDeleteIgnore(msg.Id);
 | 
			
		||||
                }
 | 
			
		||||
                try { await msg.DeleteAsync().ConfigureAwait(false); }
 | 
			
		||||
                catch { }
 | 
			
		||||
            });
 | 
			
		||||
            return msg;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static ModuleInfo GetTopLevelModule(this ModuleInfo module)
 | 
			
		||||
        {
 | 
			
		||||
            while (module.Parent != null)
 | 
			
		||||
            {
 | 
			
		||||
                module = module.Parent;
 | 
			
		||||
            }
 | 
			
		||||
            return module;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void AddRange<T>(this HashSet<T> target, IEnumerable<T> elements) where T : class
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in elements)
 | 
			
		||||
            {
 | 
			
		||||
                target.Add(item);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void AddRange<T>(this ConcurrentHashSet<T> target, IEnumerable<T> elements) where T : class
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in elements)
 | 
			
		||||
            {
 | 
			
		||||
                target.Add(item);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static double UnixTimestamp(this DateTime dt) 
 | 
			
		||||
            => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
 | 
			
		||||
 | 
			
		||||
        public static async Task<IEnumerable<IGuildUser>> GetMembersAsync(this IRole role) =>
 | 
			
		||||
            (await role.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false)).Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty<IGuildUser>();
 | 
			
		||||
 | 
			
		||||
        public static string ToJson<T>(this T any, JsonSerializerOptions options = null) =>
 | 
			
		||||
            JsonSerializer.Serialize(any, options);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Adds fallback fonts to <see cref="TextOptions"/>
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="opts"><see cref="TextOptions"/> to which fallback fonts will be added to</param>
 | 
			
		||||
        /// <param name="fallback">List of fallback Font Families to add</param>
 | 
			
		||||
        /// <returns>The same <see cref="TextOptions"/> to allow chaining</returns>
 | 
			
		||||
        public static TextOptions WithFallbackFonts(this TextOptions opts, List<FontFamily> fallback)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var ff in fallback)
 | 
			
		||||
            {
 | 
			
		||||
                opts.FallbackFonts.Add(ff);
 | 
			
		||||
            }
 | 
			
		||||
            return opts;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Adds fallback fonts to <see cref="TextGraphicsOptions"/>
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="opts"><see cref="TextGraphicsOptions"/> to which fallback fonts will be added to</param>
 | 
			
		||||
        /// <param name="fallback">List of fallback Font Families to add</param>
 | 
			
		||||
        /// <returns>The same <see cref="TextGraphicsOptions"/> to allow chaining</returns>
 | 
			
		||||
        public static TextGraphicsOptions WithFallbackFonts(this TextGraphicsOptions opts, List<FontFamily> fallback)
 | 
			
		||||
        {
 | 
			
		||||
            opts.TextOptions.WithFallbackFonts(fallback);
 | 
			
		||||
            return opts;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static MemoryStream ToStream(this Image<Rgba32> img, IImageFormat format = null)
 | 
			
		||||
        {
 | 
			
		||||
            var imageStream = new MemoryStream();
 | 
			
		||||
            if (format?.Name == "GIF")
 | 
			
		||||
            {
 | 
			
		||||
                img.SaveAsGif(imageStream);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                img.SaveAsPng(imageStream, new PngEncoder()
 | 
			
		||||
                {
 | 
			
		||||
                    ColorType = PngColorType.RgbWithAlpha,
 | 
			
		||||
                    CompressionLevel = PngCompressionLevel.BestCompression
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            imageStream.Position = 0;
 | 
			
		||||
            return imageStream;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
 | 
			
		||||
        {
 | 
			
		||||
            var ms = new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite);
 | 
			
		||||
            ms.Seek(0, SeekOrigin.Begin);
 | 
			
		||||
            return ms;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IEnumerable<IRole> GetRoles(this IGuildUser user) =>
 | 
			
		||||
            user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r != null);
 | 
			
		||||
 | 
			
		||||
        public static async Task<IMessage> SendMessageToOwnerAsync(this IGuild guild, string message)
 | 
			
		||||
        {
 | 
			
		||||
            var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).GetOrCreateDMChannelAsync()
 | 
			
		||||
                                .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            return await ownerPrivate.SendMessageAsync(message).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static bool IsImage(this HttpResponseMessage msg) => IsImage(msg, out _);
 | 
			
		||||
 | 
			
		||||
        public static bool IsImage(this HttpResponseMessage msg, out string mimeType)
 | 
			
		||||
        {
 | 
			
		||||
            mimeType = msg.Content.Headers.ContentType?.MediaType;
 | 
			
		||||
            if (mimeType == "image/png"
 | 
			
		||||
                    || mimeType == "image/jpeg"
 | 
			
		||||
                    || mimeType == "image/gif")
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static long? GetImageSize(this HttpResponseMessage msg)
 | 
			
		||||
        {
 | 
			
		||||
            if (msg.Content.Headers.ContentLength is null)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return msg.Content.Headers.ContentLength / 1.MB();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetText(this IBotStrings strings, in LocStr str, ulong? guildId = null)
 | 
			
		||||
            => strings.GetText(str.Key, guildId, str.Params);
 | 
			
		||||
        
 | 
			
		||||
        public static string GetText(this IBotStrings strings, in LocStr str, CultureInfo culture)
 | 
			
		||||
            => strings.GetText(str.Key, culture, str.Params);
 | 
			
		||||
        return channel.SendMessageAsync(plainText, embed: embed);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
        
 | 
			
		||||
    public static Task<IUserMessage> SendAsync(this IMessageChannel channel, SmartText text, bool sanitizeAll = false)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
            SmartEmbedText set => channel.SendAsync(set.PlainText, set.GetEmbed().Build(), sanitizeAll),
 | 
			
		||||
            SmartPlainText st => channel.SendAsync(st.Text, null, sanitizeAll),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public static List<ulong> GetGuildIds(this DiscordSocketClient client)
 | 
			
		||||
        => client.Guilds.Select(x => x.Id).ToList();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Generates a string in the format HHH:mm if timespan is <= 2m.
 | 
			
		||||
    /// Generates a string in the format 00:mm:ss if timespan is less than 2m.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="span">Timespan to convert to string</param>
 | 
			
		||||
    /// <returns>Formatted duration string</returns>
 | 
			
		||||
    public static string ToPrettyStringHM(this TimeSpan span)
 | 
			
		||||
    {
 | 
			
		||||
        if (span < TimeSpan.FromMinutes(2))
 | 
			
		||||
            return $"{span:mm}m {span:ss}s";
 | 
			
		||||
        return $"{(int) span.TotalHours:D2}h {span:mm}m";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static bool TryGetUrlPath(this string input, out string path)
 | 
			
		||||
    {
 | 
			
		||||
        var match = UrlRegex.Match(input);
 | 
			
		||||
        if (match.Success)
 | 
			
		||||
        {
 | 
			
		||||
            path = match.Groups["path"].Value;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        path = string.Empty;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IEmote ToIEmote(this string emojiStr)
 | 
			
		||||
        => Emote.TryParse(emojiStr, out var maybeEmote)
 | 
			
		||||
            ? (IEmote)maybeEmote
 | 
			
		||||
            : new Emoji(emojiStr);
 | 
			
		||||
 | 
			
		||||
    // https://github.com/SixLabors/Samples/blob/master/ImageSharp/AvatarWithRoundedCorner/Program.cs
 | 
			
		||||
    public static IImageProcessingContext ApplyRoundedCorners(this IImageProcessingContext ctx, float cornerRadius)
 | 
			
		||||
    {
 | 
			
		||||
        Size size = ctx.GetCurrentSize();
 | 
			
		||||
        IPathCollection corners = BuildCorners(size.Width, size.Height, cornerRadius);
 | 
			
		||||
 | 
			
		||||
        ctx.SetGraphicsOptions(new GraphicsOptions()
 | 
			
		||||
        {
 | 
			
		||||
            Antialias = true,
 | 
			
		||||
            AlphaCompositionMode = PixelAlphaCompositionMode.DestOut // enforces that any part of this shape that has color is punched out of the background
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        foreach (var c in corners)
 | 
			
		||||
        {
 | 
			
		||||
            ctx = ctx.Fill(SixLabors.ImageSharp.Color.Red, c);
 | 
			
		||||
        }
 | 
			
		||||
        return ctx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius)
 | 
			
		||||
    {
 | 
			
		||||
        // first create a square
 | 
			
		||||
        var rect = new RectangularPolygon(-0.5f, -0.5f, cornerRadius, cornerRadius);
 | 
			
		||||
 | 
			
		||||
        // then cut out of the square a circle so we are left with a corner
 | 
			
		||||
        IPath cornerTopLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius));
 | 
			
		||||
 | 
			
		||||
        // corner is now a corner shape positions top left
 | 
			
		||||
        //lets make 3 more positioned correctly, we can do that by translating the original around the center of the image
 | 
			
		||||
 | 
			
		||||
        float rightPos = imageWidth - cornerTopLeft.Bounds.Width + 1;
 | 
			
		||||
        float bottomPos = imageHeight - cornerTopLeft.Bounds.Height + 1;
 | 
			
		||||
 | 
			
		||||
        // move it across the width of the image - the width of the shape
 | 
			
		||||
        IPath cornerTopRight = cornerTopLeft.RotateDegree(90).Translate(rightPos, 0);
 | 
			
		||||
        IPath cornerBottomLeft = cornerTopLeft.RotateDegree(-90).Translate(0, bottomPos);
 | 
			
		||||
        IPath cornerBottomRight = cornerTopLeft.RotateDegree(180).Translate(rightPos, bottomPos);
 | 
			
		||||
 | 
			
		||||
        return new PathCollection(cornerTopLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// First 10 characters of teh bot token.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static string RedisKey(this IBotCredentials bc)
 | 
			
		||||
    {
 | 
			
		||||
        return bc.Token.Substring(0, 10);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task<string> ReplaceAsync(this Regex regex, string input, Func<Match, Task<string>> replacementFn)
 | 
			
		||||
    {
 | 
			
		||||
        var sb = new StringBuilder();
 | 
			
		||||
        var lastIndex = 0;
 | 
			
		||||
 | 
			
		||||
        foreach (Match match in regex.Matches(input))
 | 
			
		||||
        {
 | 
			
		||||
            sb.Append(input, lastIndex, match.Index - lastIndex)
 | 
			
		||||
                .Append(await replacementFn(match).ConfigureAwait(false));
 | 
			
		||||
 | 
			
		||||
            lastIndex = match.Index + match.Length;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sb.Append(input, lastIndex, input.Length - lastIndex);
 | 
			
		||||
        return sb.ToString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void ThrowIfNull<T>(this T o, string name) where T : class
 | 
			
		||||
    {
 | 
			
		||||
        if (o is null)
 | 
			
		||||
            throw new ArgumentNullException(nameof(name));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static bool IsAuthor(this IMessage msg, IDiscordClient client)
 | 
			
		||||
        => msg.Author?.Id == client.CurrentUser.Id;
 | 
			
		||||
 | 
			
		||||
    public static string RealSummary(this CommandInfo cmd, IBotStrings strings, ulong? guildId, string prefix)
 | 
			
		||||
        => string.Format(strings.GetCommandStrings(cmd.Name, guildId).Desc, prefix);
 | 
			
		||||
 | 
			
		||||
    public static string[] RealRemarksArr(this CommandInfo cmd, IBotStrings strings, ulong? guildId, string prefix)
 | 
			
		||||
        => Array.ConvertAll(strings.GetCommandStrings(cmd.MethodName(), guildId).Args,
 | 
			
		||||
            arg => GetFullUsage(cmd.Name, arg, prefix));
 | 
			
		||||
 | 
			
		||||
    public static string MethodName(this CommandInfo cmd)
 | 
			
		||||
        => ((NadekoCommandAttribute) cmd.Attributes.FirstOrDefault(x => x is NadekoCommandAttribute))?.MethodName
 | 
			
		||||
           ?? cmd.Name;
 | 
			
		||||
    // public static string RealRemarks(this CommandInfo cmd, IBotStrings strings, string prefix)
 | 
			
		||||
    //     => string.Join('\n', cmd.RealRemarksArr(strings, prefix));
 | 
			
		||||
 | 
			
		||||
    public static string GetFullUsage(string commandName, string args, string prefix)
 | 
			
		||||
        => $"{prefix}{commandName} {string.Format(args, prefix)}";
 | 
			
		||||
 | 
			
		||||
    public static IEmbedBuilder AddPaginatedFooter(this IEmbedBuilder embed, int curPage, int? lastPage)
 | 
			
		||||
    {
 | 
			
		||||
        if (lastPage != null)
 | 
			
		||||
            return embed.WithFooter($"{curPage + 1} / {lastPage + 1}");
 | 
			
		||||
        else
 | 
			
		||||
            return embed.WithFooter(curPage.ToString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Color ToDiscordColor(this Rgba32 color)
 | 
			
		||||
        => new Color(color.R, color.G, color.B);
 | 
			
		||||
        
 | 
			
		||||
    public static IEmbedBuilder WithOkColor(this IEmbedBuilder eb) =>
 | 
			
		||||
        eb.WithColor(EmbedColor.Ok);
 | 
			
		||||
 | 
			
		||||
    public static IEmbedBuilder WithPendingColor(this IEmbedBuilder eb) =>
 | 
			
		||||
        eb.WithColor(EmbedColor.Pending);
 | 
			
		||||
        
 | 
			
		||||
    public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb) =>
 | 
			
		||||
        eb.WithColor(EmbedColor.Error);
 | 
			
		||||
 | 
			
		||||
    public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Func<SocketReaction, Task> reactionAdded, Func<SocketReaction, Task> reactionRemoved = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (reactionRemoved is null)
 | 
			
		||||
            reactionRemoved = _ => Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
        var wrap = new ReactionEventWrapper(client, msg);
 | 
			
		||||
        wrap.OnReactionAdded += (r) => { var _ = Task.Run(() => reactionAdded(r)); };
 | 
			
		||||
        wrap.OnReactionRemoved += (r) => { var _ = Task.Run(() => reactionRemoved(r)); };
 | 
			
		||||
        return wrap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static HttpClient AddFakeHeaders(this HttpClient http)
 | 
			
		||||
    {
 | 
			
		||||
        AddFakeHeaders(http.DefaultRequestHeaders);
 | 
			
		||||
        return http;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void AddFakeHeaders(this HttpHeaders dict)
 | 
			
		||||
    {
 | 
			
		||||
        dict.Clear();
 | 
			
		||||
        dict.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
 | 
			
		||||
        dict.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IMessage DeleteAfter(this IUserMessage msg, int seconds, ILogCommandService logService = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (msg is null)
 | 
			
		||||
            return null;
 | 
			
		||||
            
 | 
			
		||||
        Task.Run(async () =>
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Delay(seconds * 1000).ConfigureAwait(false);
 | 
			
		||||
            if(logService != null)
 | 
			
		||||
            {
 | 
			
		||||
                logService.AddDeleteIgnore(msg.Id);
 | 
			
		||||
            }
 | 
			
		||||
            try { await msg.DeleteAsync().ConfigureAwait(false); }
 | 
			
		||||
            catch { }
 | 
			
		||||
        });
 | 
			
		||||
        return msg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ModuleInfo GetTopLevelModule(this ModuleInfo module)
 | 
			
		||||
    {
 | 
			
		||||
        while (module.Parent != null)
 | 
			
		||||
        {
 | 
			
		||||
            module = module.Parent;
 | 
			
		||||
        }
 | 
			
		||||
        return module;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void AddRange<T>(this HashSet<T> target, IEnumerable<T> elements) where T : class
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var item in elements)
 | 
			
		||||
        {
 | 
			
		||||
            target.Add(item);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void AddRange<T>(this ConcurrentHashSet<T> target, IEnumerable<T> elements) where T : class
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var item in elements)
 | 
			
		||||
        {
 | 
			
		||||
            target.Add(item);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static double UnixTimestamp(this DateTime dt) 
 | 
			
		||||
        => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
 | 
			
		||||
 | 
			
		||||
    public static async Task<IEnumerable<IGuildUser>> GetMembersAsync(this IRole role) =>
 | 
			
		||||
        (await role.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false)).Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty<IGuildUser>();
 | 
			
		||||
 | 
			
		||||
    public static string ToJson<T>(this T any, JsonSerializerOptions options = null) =>
 | 
			
		||||
        JsonSerializer.Serialize(any, options);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Adds fallback fonts to <see cref="TextOptions"/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="opts"><see cref="TextOptions"/> to which fallback fonts will be added to</param>
 | 
			
		||||
    /// <param name="fallback">List of fallback Font Families to add</param>
 | 
			
		||||
    /// <returns>The same <see cref="TextOptions"/> to allow chaining</returns>
 | 
			
		||||
    public static TextOptions WithFallbackFonts(this TextOptions opts, List<FontFamily> fallback)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var ff in fallback)
 | 
			
		||||
        {
 | 
			
		||||
            opts.FallbackFonts.Add(ff);
 | 
			
		||||
        }
 | 
			
		||||
        return opts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Adds fallback fonts to <see cref="TextGraphicsOptions"/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="opts"><see cref="TextGraphicsOptions"/> to which fallback fonts will be added to</param>
 | 
			
		||||
    /// <param name="fallback">List of fallback Font Families to add</param>
 | 
			
		||||
    /// <returns>The same <see cref="TextGraphicsOptions"/> to allow chaining</returns>
 | 
			
		||||
    public static TextGraphicsOptions WithFallbackFonts(this TextGraphicsOptions opts, List<FontFamily> fallback)
 | 
			
		||||
    {
 | 
			
		||||
        opts.TextOptions.WithFallbackFonts(fallback);
 | 
			
		||||
        return opts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static MemoryStream ToStream(this Image<Rgba32> img, IImageFormat format = null)
 | 
			
		||||
    {
 | 
			
		||||
        var imageStream = new MemoryStream();
 | 
			
		||||
        if (format?.Name == "GIF")
 | 
			
		||||
        {
 | 
			
		||||
            img.SaveAsGif(imageStream);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            img.SaveAsPng(imageStream, new PngEncoder()
 | 
			
		||||
            {
 | 
			
		||||
                ColorType = PngColorType.RgbWithAlpha,
 | 
			
		||||
                CompressionLevel = PngCompressionLevel.BestCompression
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        imageStream.Position = 0;
 | 
			
		||||
        return imageStream;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
 | 
			
		||||
    {
 | 
			
		||||
        var ms = new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite);
 | 
			
		||||
        ms.Seek(0, SeekOrigin.Begin);
 | 
			
		||||
        return ms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<IRole> GetRoles(this IGuildUser user) =>
 | 
			
		||||
        user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r != null);
 | 
			
		||||
 | 
			
		||||
    public static async Task<IMessage> SendMessageToOwnerAsync(this IGuild guild, string message)
 | 
			
		||||
    {
 | 
			
		||||
        var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).GetOrCreateDMChannelAsync()
 | 
			
		||||
            .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        return await ownerPrivate.SendMessageAsync(message).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static bool IsImage(this HttpResponseMessage msg) => IsImage(msg, out _);
 | 
			
		||||
 | 
			
		||||
    public static bool IsImage(this HttpResponseMessage msg, out string mimeType)
 | 
			
		||||
    {
 | 
			
		||||
        mimeType = msg.Content.Headers.ContentType?.MediaType;
 | 
			
		||||
        if (mimeType == "image/png"
 | 
			
		||||
            || mimeType == "image/jpeg"
 | 
			
		||||
            || mimeType == "image/gif")
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static long? GetImageSize(this HttpResponseMessage msg)
 | 
			
		||||
    {
 | 
			
		||||
        if (msg.Content.Headers.ContentLength is null)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return msg.Content.Headers.ContentLength / 1.MB();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static string GetText(this IBotStrings strings, in LocStr str, ulong? guildId = null)
 | 
			
		||||
        => strings.GetText(str.Key, guildId, str.Params);
 | 
			
		||||
        
 | 
			
		||||
    public static string GetText(this IBotStrings strings, in LocStr str, CultureInfo culture)
 | 
			
		||||
        => strings.GetText(str.Key, culture, str.Params);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,151 +1,80 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Common.Collections;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class IEnumerableExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class IEnumerableExtensions
 | 
			
		||||
    public static string JoinWith<T>(this IEnumerable<T> data, char separator, Func<T, string> func = null)
 | 
			
		||||
    {
 | 
			
		||||
        public static string JoinWith<T>(this IEnumerable<T> data, char separator, Func<T, string> func = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (func is null)
 | 
			
		||||
                func = x => x?.ToString() ?? string.Empty;
 | 
			
		||||
        if (func is null)
 | 
			
		||||
            func = x => x?.ToString() ?? string.Empty;
 | 
			
		||||
 | 
			
		||||
            return string.Join(separator, data.Select(func));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static string JoinWith<T>(this IEnumerable<T> data, string separator, Func<T, string> func = null)
 | 
			
		||||
        {
 | 
			
		||||
            func ??= x => x?.ToString() ?? string.Empty;
 | 
			
		||||
 | 
			
		||||
            return string.Join(separator, data.Select(func));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static IEnumerable<T> Distinct<T, U>(this IEnumerable<T> data, Func<T, U> getKey) =>
 | 
			
		||||
            data.GroupBy(x => getKey(x))
 | 
			
		||||
                .Select(x => x.First());
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Randomize element order by performing the Fisher-Yates shuffle
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="T">Item type</typeparam>
 | 
			
		||||
        /// <param name="items">Items to shuffle</param>
 | 
			
		||||
        public static IReadOnlyList<T> Shuffle<T>(this IEnumerable<T> items)
 | 
			
		||||
        {
 | 
			
		||||
            using var provider = RandomNumberGenerator.Create();
 | 
			
		||||
            var list = items.ToList();
 | 
			
		||||
            var n = list.Count;
 | 
			
		||||
            while (n > 1)
 | 
			
		||||
            {
 | 
			
		||||
                var box = new byte[(n / Byte.MaxValue) + 1];
 | 
			
		||||
                int boxSum;
 | 
			
		||||
                do
 | 
			
		||||
                {
 | 
			
		||||
                    provider.GetBytes(box);
 | 
			
		||||
                    boxSum = box.Sum(b => b);
 | 
			
		||||
                } while (!(boxSum < n * ((Byte.MaxValue * box.Length) / n)));
 | 
			
		||||
 | 
			
		||||
                var k = (boxSum % n);
 | 
			
		||||
                n--;
 | 
			
		||||
                var value = list[k];
 | 
			
		||||
                list[k] = list[n];
 | 
			
		||||
                list[n] = value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return list;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> elems, Action<T> exec)
 | 
			
		||||
        {
 | 
			
		||||
            var realElems = elems.ToList();
 | 
			
		||||
            foreach (var elem in realElems)
 | 
			
		||||
            {
 | 
			
		||||
                exec(elem);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return realElems;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static ConcurrentDictionary<TKey, TValue> ToConcurrent<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> dict)
 | 
			
		||||
            => new ConcurrentDictionary<TKey, TValue>(dict);
 | 
			
		||||
 | 
			
		||||
        public static IndexedCollection<T> ToIndexed<T>(this IEnumerable<T> enumerable)
 | 
			
		||||
            where T : class, IIndexed
 | 
			
		||||
            => new IndexedCollection<T>(enumerable);
 | 
			
		||||
        
 | 
			
		||||
        // Licensed to the .NET Foundation under one or more agreements.
 | 
			
		||||
        // The .NET Foundation licenses this file to you under the MIT license.
 | 
			
		||||
        
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Split the elements of a sequence into chunks of size at most <paramref name="size"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>
 | 
			
		||||
        /// Every chunk except the last will be of size <paramref name="size"/>.
 | 
			
		||||
        /// The last chunk will contain the remaining elements and may be of a smaller size.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <param name="source">
 | 
			
		||||
        /// An <see cref="IEnumerable{T}"/> whose elements to chunk.
 | 
			
		||||
        /// </param>
 | 
			
		||||
        /// <param name="size">
 | 
			
		||||
        /// Maximum size of each chunk.
 | 
			
		||||
        /// </param>
 | 
			
		||||
        /// <typeparam name="TSource">
 | 
			
		||||
        /// The type of the elements of source.
 | 
			
		||||
        /// </typeparam>
 | 
			
		||||
        /// <returns>
 | 
			
		||||
        /// An <see cref="IEnumerable{T}"/> that contains the elements the input sequence split into chunks of size <paramref name="size"/>.
 | 
			
		||||
        /// </returns>
 | 
			
		||||
        /// <exception cref="ArgumentNullException">
 | 
			
		||||
        /// <paramref name="source"/> is null.
 | 
			
		||||
        /// </exception>
 | 
			
		||||
        /// <exception cref="ArgumentOutOfRangeException">
 | 
			
		||||
        /// <paramref name="size"/> is below 1.
 | 
			
		||||
        /// </exception>
 | 
			
		||||
        public static IEnumerable<TSource[]> Chunk<TSource>(this IEnumerable<TSource> source, int size)
 | 
			
		||||
        {
 | 
			
		||||
            if (source is null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(source));
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
            if (size < 1)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(size));
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
            return ChunkIterator(source, size);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
        private static IEnumerable<TSource[]> ChunkIterator<TSource>(IEnumerable<TSource> source, int size)
 | 
			
		||||
        {
 | 
			
		||||
            using IEnumerator<TSource> e = source.GetEnumerator();
 | 
			
		||||
            while (e.MoveNext())
 | 
			
		||||
            {
 | 
			
		||||
                TSource[] chunk = new TSource[size];
 | 
			
		||||
                chunk[0] = e.Current;
 | 
			
		||||
 
 | 
			
		||||
                for (int i = 1; i < size; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (!e.MoveNext())
 | 
			
		||||
                    {
 | 
			
		||||
                        Array.Resize(ref chunk, i);
 | 
			
		||||
                        yield return chunk;
 | 
			
		||||
                        yield break;
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
                    chunk[i] = e.Current;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
                yield return chunk;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Task<TData[]> WhenAll<TData>(this IEnumerable<Task<TData>> items)
 | 
			
		||||
            => Task.WhenAll(items);
 | 
			
		||||
        return string.Join(separator, data.Select(func));
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public static string JoinWith<T>(this IEnumerable<T> data, string separator, Func<T, string> func = null)
 | 
			
		||||
    {
 | 
			
		||||
        func ??= x => x?.ToString() ?? string.Empty;
 | 
			
		||||
 | 
			
		||||
        return string.Join(separator, data.Select(func));
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public static IEnumerable<T> Distinct<T, U>(this IEnumerable<T> data, Func<T, U> getKey) =>
 | 
			
		||||
        data.GroupBy(x => getKey(x))
 | 
			
		||||
            .Select(x => x.First());
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Randomize element order by performing the Fisher-Yates shuffle
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="T">Item type</typeparam>
 | 
			
		||||
    /// <param name="items">Items to shuffle</param>
 | 
			
		||||
    public static IReadOnlyList<T> Shuffle<T>(this IEnumerable<T> items)
 | 
			
		||||
    {
 | 
			
		||||
        using var provider = RandomNumberGenerator.Create();
 | 
			
		||||
        var list = items.ToList();
 | 
			
		||||
        var n = list.Count;
 | 
			
		||||
        while (n > 1)
 | 
			
		||||
        {
 | 
			
		||||
            var box = new byte[(n / Byte.MaxValue) + 1];
 | 
			
		||||
            int boxSum;
 | 
			
		||||
            do
 | 
			
		||||
            {
 | 
			
		||||
                provider.GetBytes(box);
 | 
			
		||||
                boxSum = box.Sum(b => b);
 | 
			
		||||
            } while (!(boxSum < n * ((Byte.MaxValue * box.Length) / n)));
 | 
			
		||||
 | 
			
		||||
            var k = (boxSum % n);
 | 
			
		||||
            n--;
 | 
			
		||||
            var value = list[k];
 | 
			
		||||
            list[k] = list[n];
 | 
			
		||||
            list[n] = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return list;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> elems, Action<T> exec)
 | 
			
		||||
    {
 | 
			
		||||
        var realElems = elems.ToList();
 | 
			
		||||
        foreach (var elem in realElems)
 | 
			
		||||
        {
 | 
			
		||||
            exec(elem);
 | 
			
		||||
        }
 | 
			
		||||
            
 | 
			
		||||
        return realElems;
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    public static ConcurrentDictionary<TKey, TValue> ToConcurrent<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> dict)
 | 
			
		||||
        => new ConcurrentDictionary<TKey, TValue>(dict);
 | 
			
		||||
 | 
			
		||||
    public static IndexedCollection<T> ToIndexed<T>(this IEnumerable<T> enumerable)
 | 
			
		||||
        where T : class, IIndexed
 | 
			
		||||
        => new IndexedCollection<T>(enumerable);
 | 
			
		||||
 | 
			
		||||
    public static Task<TData[]> WhenAll<TData>(this IEnumerable<Task<TData>> items)
 | 
			
		||||
        => Task.WhenAll(items);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,189 +1,185 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
{
 | 
			
		||||
    public static class IMessageChannelExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "")
 | 
			
		||||
            => ch.SendMessageAsync(msg, embed: embed.Build(),
 | 
			
		||||
                options: new RequestOptions() { RetryMode  = RetryMode.AlwaysRetry });
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
        // this is a huge problem, because now i don't have
 | 
			
		||||
        // access to embed builder service
 | 
			
		||||
        // as this is an extension of the message channel
 | 
			
		||||
        public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string title, string error, string url = null, string footer = null)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = eb.Create()
 | 
			
		||||
public static class IMessageChannelExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "")
 | 
			
		||||
        => ch.SendMessageAsync(msg, embed: embed.Build(),
 | 
			
		||||
            options: new RequestOptions() { RetryMode  = RetryMode.AlwaysRetry });
 | 
			
		||||
 | 
			
		||||
    // this is a huge problem, because now i don't have
 | 
			
		||||
    // access to embed builder service
 | 
			
		||||
    // as this is an extension of the message channel
 | 
			
		||||
    public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string title, string error, string url = null, string footer = null)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = eb.Create()
 | 
			
		||||
            .WithErrorColor()
 | 
			
		||||
            .WithDescription(error)
 | 
			
		||||
            .WithTitle(title);
 | 
			
		||||
 | 
			
		||||
        if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
            embed.WithUrl(url);
 | 
			
		||||
                    
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(footer))
 | 
			
		||||
            embed.WithFooter(footer);
 | 
			
		||||
            
 | 
			
		||||
        return ch.SendMessageAsync("", embed: embed.Build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string error)
 | 
			
		||||
        => ch.SendMessageAsync("",
 | 
			
		||||
            embed: eb.Create()
 | 
			
		||||
                .WithErrorColor()
 | 
			
		||||
                .WithDescription(error)
 | 
			
		||||
                .WithTitle(title);
 | 
			
		||||
 | 
			
		||||
            if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
                embed.WithUrl(url);
 | 
			
		||||
                    
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(footer))
 | 
			
		||||
                embed.WithFooter(footer);
 | 
			
		||||
            
 | 
			
		||||
            return ch.SendMessageAsync("", embed: embed.Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string error)
 | 
			
		||||
            => ch.SendMessageAsync("",
 | 
			
		||||
                embed: eb.Create()
 | 
			
		||||
                    .WithErrorColor()
 | 
			
		||||
                    .WithDescription(error)
 | 
			
		||||
                    .Build());
 | 
			
		||||
 | 
			
		||||
        public static Task<IUserMessage> SendPendingAsync(this IMessageChannel ch, IEmbedBuilderService eb, string message)
 | 
			
		||||
            => ch.SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
                .WithPendingColor()
 | 
			
		||||
                .WithDescription(message)
 | 
			
		||||
                .Build());
 | 
			
		||||
 | 
			
		||||
    public static Task<IUserMessage> SendPendingAsync(this IMessageChannel ch, IEmbedBuilderService eb, string message)
 | 
			
		||||
        => ch.SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
            .WithPendingColor()
 | 
			
		||||
            .WithDescription(message)
 | 
			
		||||
            .Build());
 | 
			
		||||
        
 | 
			
		||||
        public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string title, string text, string url = null, string footer = null)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = eb.Create().WithOkColor().WithDescription(text)
 | 
			
		||||
                .WithTitle(title);
 | 
			
		||||
    public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string title, string text, string url = null, string footer = null)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = eb.Create().WithOkColor().WithDescription(text)
 | 
			
		||||
            .WithTitle(title);
 | 
			
		||||
            
 | 
			
		||||
            if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
                embed.WithUrl(url);
 | 
			
		||||
        if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
            embed.WithUrl(url);
 | 
			
		||||
            
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(footer))
 | 
			
		||||
                embed.WithFooter(footer);
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(footer))
 | 
			
		||||
            embed.WithFooter(footer);
 | 
			
		||||
            
 | 
			
		||||
            return ch.SendMessageAsync("", embed: embed.Build());
 | 
			
		||||
        }
 | 
			
		||||
        return ch.SendMessageAsync("", embed: embed.Build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string text)
 | 
			
		||||
            => ch.SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithDescription(text)
 | 
			
		||||
                .Build());
 | 
			
		||||
    public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string text)
 | 
			
		||||
        => ch.SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
            .WithOkColor()
 | 
			
		||||
            .WithDescription(text)
 | 
			
		||||
            .Build());
 | 
			
		||||
 | 
			
		||||
        public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, string seed, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3)
 | 
			
		||||
        {
 | 
			
		||||
            return ch.SendMessageAsync($@"{seed}```css
 | 
			
		||||
    public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, string seed, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3)
 | 
			
		||||
    {
 | 
			
		||||
        return ch.SendMessageAsync($@"{seed}```css
 | 
			
		||||
{string.Join("\n", items.Chunk(columns)
 | 
			
		||||
                        .Select(ig => string.Concat(ig.Select(el => howToPrint(el)))))}
 | 
			
		||||
    .Select(ig => string.Concat(ig.Select(el => howToPrint(el)))))}
 | 
			
		||||
```");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3) =>
 | 
			
		||||
            ch.SendTableAsync("", items, howToPrint, columns);
 | 
			
		||||
    public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3) =>
 | 
			
		||||
        ch.SendTableAsync("", items, howToPrint, columns);
 | 
			
		||||
        
 | 
			
		||||
        private static readonly IEmote arrow_left = new Emoji("⬅");
 | 
			
		||||
        private static readonly IEmote arrow_right = new Emoji("➡");
 | 
			
		||||
    private static readonly IEmote arrow_left = new Emoji("⬅");
 | 
			
		||||
    private static readonly IEmote arrow_right = new Emoji("➡");
 | 
			
		||||
 | 
			
		||||
        public static Task SendPaginatedConfirmAsync(this ICommandContext ctx,
 | 
			
		||||
            int currentPage, Func<int, IEmbedBuilder> pageFunc, int totalElements,
 | 
			
		||||
            int itemsPerPage, bool addPaginatedFooter = true)
 | 
			
		||||
            => ctx.SendPaginatedConfirmAsync(currentPage,
 | 
			
		||||
                (x) => Task.FromResult(pageFunc(x)), totalElements, itemsPerPage, addPaginatedFooter);
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// danny kamisama
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static async Task SendPaginatedConfirmAsync(this ICommandContext ctx, int currentPage, 
 | 
			
		||||
            Func<int, Task<IEmbedBuilder>> pageFunc, int totalElements, int itemsPerPage, bool addPaginatedFooter = true)
 | 
			
		||||
    public static Task SendPaginatedConfirmAsync(this ICommandContext ctx,
 | 
			
		||||
        int currentPage, Func<int, IEmbedBuilder> pageFunc, int totalElements,
 | 
			
		||||
        int itemsPerPage, bool addPaginatedFooter = true)
 | 
			
		||||
        => ctx.SendPaginatedConfirmAsync(currentPage,
 | 
			
		||||
            (x) => Task.FromResult(pageFunc(x)), totalElements, itemsPerPage, addPaginatedFooter);
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// danny kamisama
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static async Task SendPaginatedConfirmAsync(this ICommandContext ctx, int currentPage, 
 | 
			
		||||
        Func<int, Task<IEmbedBuilder>> pageFunc, int totalElements, int itemsPerPage, bool addPaginatedFooter = true)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = await pageFunc(currentPage).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        var lastPage = (totalElements - 1) / itemsPerPage;
 | 
			
		||||
            
 | 
			
		||||
        var canPaginate = true;
 | 
			
		||||
        var sg = ctx.Guild as SocketGuild;
 | 
			
		||||
        if (sg is not null && !sg.CurrentUser.GetPermissions((IGuildChannel) ctx.Channel).AddReactions)
 | 
			
		||||
            canPaginate = false;
 | 
			
		||||
            
 | 
			
		||||
        if (!canPaginate)
 | 
			
		||||
            embed.WithFooter("⚠️ AddReaction permission required for pagination.");
 | 
			
		||||
        else if (addPaginatedFooter)
 | 
			
		||||
            embed.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
 | 
			
		||||
        var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false) as IUserMessage;
 | 
			
		||||
 | 
			
		||||
        if (lastPage == 0 || !canPaginate)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        await msg.AddReactionAsync(arrow_left).ConfigureAwait(false);
 | 
			
		||||
        await msg.AddReactionAsync(arrow_right).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        await Task.Delay(2000).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        var lastPageChange = DateTime.MinValue;
 | 
			
		||||
 | 
			
		||||
        async Task changePage(SocketReaction r)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = await pageFunc(currentPage).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var lastPage = (totalElements - 1) / itemsPerPage;
 | 
			
		||||
            
 | 
			
		||||
            var canPaginate = true;
 | 
			
		||||
            var sg = ctx.Guild as SocketGuild;
 | 
			
		||||
            if (sg is not null && !sg.CurrentUser.GetPermissions((IGuildChannel) ctx.Channel).AddReactions)
 | 
			
		||||
                canPaginate = false;
 | 
			
		||||
            
 | 
			
		||||
            if (!canPaginate)
 | 
			
		||||
                embed.WithFooter("⚠️ AddReaction permission required for pagination.");
 | 
			
		||||
            else if (addPaginatedFooter)
 | 
			
		||||
                embed.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
 | 
			
		||||
            var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false) as IUserMessage;
 | 
			
		||||
 | 
			
		||||
            if (lastPage == 0 || !canPaginate)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await msg.AddReactionAsync(arrow_left).ConfigureAwait(false);
 | 
			
		||||
            await msg.AddReactionAsync(arrow_right).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            await Task.Delay(2000).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var lastPageChange = DateTime.MinValue;
 | 
			
		||||
 | 
			
		||||
            async Task changePage(SocketReaction r)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                if (r.UserId != ctx.User.Id)
 | 
			
		||||
                    return;
 | 
			
		||||
                if (DateTime.UtcNow - lastPageChange < TimeSpan.FromSeconds(1))
 | 
			
		||||
                    return;
 | 
			
		||||
                if (r.Emote.Name == arrow_left.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    if (r.UserId != ctx.User.Id)
 | 
			
		||||
                    if (currentPage == 0)
 | 
			
		||||
                        return;
 | 
			
		||||
                    if (DateTime.UtcNow - lastPageChange < TimeSpan.FromSeconds(1))
 | 
			
		||||
                        return;
 | 
			
		||||
                    if (r.Emote.Name == arrow_left.Name)
 | 
			
		||||
                    lastPageChange = DateTime.UtcNow;
 | 
			
		||||
                    var toSend = await pageFunc(--currentPage).ConfigureAwait(false);
 | 
			
		||||
                    if (addPaginatedFooter)
 | 
			
		||||
                        toSend.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
                    await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                else if (r.Emote.Name == arrow_right.Name)
 | 
			
		||||
                {
 | 
			
		||||
                    if (lastPage > currentPage)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (currentPage == 0)
 | 
			
		||||
                            return;
 | 
			
		||||
                        lastPageChange = DateTime.UtcNow;
 | 
			
		||||
                        var toSend = await pageFunc(--currentPage).ConfigureAwait(false);
 | 
			
		||||
                        var toSend = await pageFunc(++currentPage).ConfigureAwait(false);
 | 
			
		||||
                        if (addPaginatedFooter)
 | 
			
		||||
                            toSend.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
                        await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (r.Emote.Name == arrow_right.Name)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (lastPage > currentPage)
 | 
			
		||||
                        {
 | 
			
		||||
                            lastPageChange = DateTime.UtcNow;
 | 
			
		||||
                            var toSend = await pageFunc(++currentPage).ConfigureAwait(false);
 | 
			
		||||
                            if (addPaginatedFooter)
 | 
			
		||||
                                toSend.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
                            await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception)
 | 
			
		||||
                {
 | 
			
		||||
                    //ignored
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (msg.OnReaction((DiscordSocketClient)ctx.Client, changePage, changePage))
 | 
			
		||||
            catch (Exception)
 | 
			
		||||
            {
 | 
			
		||||
                await Task.Delay(30000).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if(msg.Channel is ITextChannel && ((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
 | 
			
		||||
                {
 | 
			
		||||
                    await msg.RemoveAllReactionsAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    await Task.WhenAll(msg.Reactions.Where(x => x.Value.IsMe)
 | 
			
		||||
                        .Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser)));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                // ignored
 | 
			
		||||
                //ignored
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Task OkAsync(this ICommandContext ctx)
 | 
			
		||||
            => ctx.Message.AddReactionAsync(new Emoji("✅"));
 | 
			
		||||
        
 | 
			
		||||
        public static Task ErrorAsync(this ICommandContext ctx)
 | 
			
		||||
            => ctx.Message.AddReactionAsync(new Emoji("❌"));
 | 
			
		||||
        
 | 
			
		||||
        public static Task WarningAsync(this ICommandContext ctx)
 | 
			
		||||
            => ctx.Message.AddReactionAsync(new Emoji("⚠️"));
 | 
			
		||||
        using (msg.OnReaction((DiscordSocketClient)ctx.Client, changePage, changePage))
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Delay(30000).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if(msg.Channel is ITextChannel && ((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
 | 
			
		||||
            {
 | 
			
		||||
                await msg.RemoveAllReactionsAsync().ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await Task.WhenAll(msg.Reactions.Where(x => x.Value.IsMe)
 | 
			
		||||
                    .Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            // ignored
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public static Task OkAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.Message.AddReactionAsync(new Emoji("✅"));
 | 
			
		||||
        
 | 
			
		||||
    public static Task ErrorAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.Message.AddReactionAsync(new Emoji("❌"));
 | 
			
		||||
        
 | 
			
		||||
    public static Task WarningAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.Message.AddReactionAsync(new Emoji("⚠️"));
 | 
			
		||||
}
 | 
			
		||||
@@ -1,73 +1,70 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class IUserExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class IUserExtensions
 | 
			
		||||
    public static async Task<IUserMessage> SendConfirmAsync(this IUser user, IEmbedBuilderService eb, string text)
 | 
			
		||||
        => await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
            .WithOkColor()
 | 
			
		||||
            .WithDescription(text)
 | 
			
		||||
            .Build());
 | 
			
		||||
 | 
			
		||||
    public static async Task<IUserMessage> SendConfirmAsync(this IUser user, IEmbedBuilderService eb, string title, string text, string url = null)
 | 
			
		||||
    {
 | 
			
		||||
        public static async Task<IUserMessage> SendConfirmAsync(this IUser user, IEmbedBuilderService eb, string text)
 | 
			
		||||
             => await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
                 .WithOkColor()
 | 
			
		||||
                 .WithDescription(text)
 | 
			
		||||
                 .Build());
 | 
			
		||||
 | 
			
		||||
        public static async Task<IUserMessage> SendConfirmAsync(this IUser user, IEmbedBuilderService eb, string title, string text, string url = null)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = eb.Create().WithOkColor().WithDescription(text).WithTitle(title);
 | 
			
		||||
            if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
                embed.WithUrl(url);
 | 
			
		||||
        var embed = eb.Create().WithOkColor().WithDescription(text).WithTitle(title);
 | 
			
		||||
        if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
            embed.WithUrl(url);
 | 
			
		||||
            
 | 
			
		||||
            return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: embed.Build());
 | 
			
		||||
        }
 | 
			
		||||
        return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: embed.Build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public static async Task<IUserMessage> SendErrorAsync(this IUser user, IEmbedBuilderService eb, string title, string error, string url = null)
 | 
			
		||||
    public static async Task<IUserMessage> SendErrorAsync(this IUser user, IEmbedBuilderService eb, string title, string error, string url = null)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = eb.Create().WithErrorColor().WithDescription(error).WithTitle(title);
 | 
			
		||||
        if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
            embed.WithUrl(url);
 | 
			
		||||
 | 
			
		||||
        return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync("", embed: embed.Build()).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task<IUserMessage> SendErrorAsync(this IUser user, IEmbedBuilderService eb, string error)
 | 
			
		||||
        => await (await user.GetOrCreateDMChannelAsync())
 | 
			
		||||
            .SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
                .WithErrorColor()
 | 
			
		||||
                .WithDescription(error)
 | 
			
		||||
                .Build());
 | 
			
		||||
 | 
			
		||||
    public static async Task<IUserMessage> SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false)
 | 
			
		||||
    {
 | 
			
		||||
        using (var file = File.Open(filePath, FileMode.Open))
 | 
			
		||||
        {
 | 
			
		||||
            var embed = eb.Create().WithErrorColor().WithDescription(error).WithTitle(title);
 | 
			
		||||
            if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
 | 
			
		||||
                embed.WithUrl(url);
 | 
			
		||||
 | 
			
		||||
            return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync("", embed: embed.Build()).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<IUserMessage> SendErrorAsync(this IUser user, IEmbedBuilderService eb, string error)
 | 
			
		||||
            => await (await user.GetOrCreateDMChannelAsync())
 | 
			
		||||
                .SendMessageAsync("", embed: eb.Create()
 | 
			
		||||
                    .WithErrorColor()
 | 
			
		||||
                    .WithDescription(error)
 | 
			
		||||
                    .Build());
 | 
			
		||||
 | 
			
		||||
        public static async Task<IUserMessage> SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false)
 | 
			
		||||
        {
 | 
			
		||||
            using (var file = File.Open(filePath, FileMode.Open))
 | 
			
		||||
            {
 | 
			
		||||
                return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(file, caption ?? "x", text, isTTS).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<IUserMessage> SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) =>
 | 
			
		||||
            await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
        // This method is used by everything that fetches the avatar from a user
 | 
			
		||||
        public static Uri RealAvatarUrl(this IUser usr, ushort size = 128)
 | 
			
		||||
        {
 | 
			
		||||
            return usr.AvatarId is null
 | 
			
		||||
                ? new Uri(usr.GetDefaultAvatarUrl())
 | 
			
		||||
                : new Uri(usr.GetAvatarUrl(ImageFormat.Auto, size));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This method is only used for the xp card
 | 
			
		||||
        public static Uri RealAvatarUrl(this DiscordUser usr)
 | 
			
		||||
        {
 | 
			
		||||
            return usr.AvatarId is null
 | 
			
		||||
                ? null
 | 
			
		||||
                : new Uri(usr.AvatarId.StartsWith("a_", StringComparison.InvariantCulture)
 | 
			
		||||
                    ? $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.gif"
 | 
			
		||||
                    : $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.png");
 | 
			
		||||
            return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(file, caption ?? "x", text, isTTS).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task<IUserMessage> SendFileAsync(this IUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) =>
 | 
			
		||||
        await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
    // This method is used by everything that fetches the avatar from a user
 | 
			
		||||
    public static Uri RealAvatarUrl(this IUser usr, ushort size = 128)
 | 
			
		||||
    {
 | 
			
		||||
        return usr.AvatarId is null
 | 
			
		||||
            ? new Uri(usr.GetDefaultAvatarUrl())
 | 
			
		||||
            : new Uri(usr.GetAvatarUrl(ImageFormat.Auto, size));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This method is only used for the xp card
 | 
			
		||||
    public static Uri RealAvatarUrl(this DiscordUser usr)
 | 
			
		||||
    {
 | 
			
		||||
        return usr.AvatarId is null
 | 
			
		||||
            ? null
 | 
			
		||||
            : new Uri(usr.AvatarId.StartsWith("a_", StringComparison.InvariantCulture)
 | 
			
		||||
                ? $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.gif"
 | 
			
		||||
                : $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.png");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +1,19 @@
 | 
			
		||||
#nullable enable
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
public static class LinkedListExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class LinkedListExtensions
 | 
			
		||||
    public static LinkedListNode<T>? FindNode<T>(this LinkedList<T> list, Func<T, bool> predicate)
 | 
			
		||||
    {
 | 
			
		||||
        public static LinkedListNode<T>? FindNode<T>(this LinkedList<T> list, Func<T, bool> predicate)
 | 
			
		||||
        var node = list.First;
 | 
			
		||||
        while (node is not null)
 | 
			
		||||
        {
 | 
			
		||||
            var node = list.First;
 | 
			
		||||
            while (node is not null)
 | 
			
		||||
            {
 | 
			
		||||
                if (predicate(node.Value))
 | 
			
		||||
                    return node;
 | 
			
		||||
            if (predicate(node.Value))
 | 
			
		||||
                return node;
 | 
			
		||||
                
 | 
			
		||||
                node = node.Next;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
            node = node.Next;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class MusicExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class MusicExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static EmbedAuthorBuilder WithMusicIcon(this EmbedAuthorBuilder eab) =>
 | 
			
		||||
            eab.WithIconUrl("http://i.imgur.com/nhKS3PT.png");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    public static EmbedAuthorBuilder WithMusicIcon(this EmbedAuthorBuilder eab) =>
 | 
			
		||||
        eab.WithIconUrl("http://i.imgur.com/nhKS3PT.png");
 | 
			
		||||
}
 | 
			
		||||
@@ -1,30 +1,27 @@
 | 
			
		||||
using System;
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
public static class NumberExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class NumberExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static int KiB(this int value) => value * 1024;
 | 
			
		||||
        public static int KB(this int value) => value * 1000;
 | 
			
		||||
    public static int KiB(this int value) => value * 1024;
 | 
			
		||||
    public static int KB(this int value) => value * 1000;
 | 
			
		||||
 | 
			
		||||
        public static int MiB(this int value) => value.KiB() * 1024;
 | 
			
		||||
        public static int MB(this int value) => value.KB() * 1000;
 | 
			
		||||
    public static int MiB(this int value) => value.KiB() * 1024;
 | 
			
		||||
    public static int MB(this int value) => value.KB() * 1000;
 | 
			
		||||
 | 
			
		||||
        public static int GiB(this int value) => value.MiB() * 1024;
 | 
			
		||||
        public static int GB(this int value) => value.MB() * 1000;
 | 
			
		||||
    public static int GiB(this int value) => value.MiB() * 1024;
 | 
			
		||||
    public static int GB(this int value) => value.MB() * 1000;
 | 
			
		||||
 | 
			
		||||
        public static ulong KiB(this ulong value) => value * 1024;
 | 
			
		||||
        public static ulong KB(this ulong value) => value * 1000;
 | 
			
		||||
    public static ulong KiB(this ulong value) => value * 1024;
 | 
			
		||||
    public static ulong KB(this ulong value) => value * 1000;
 | 
			
		||||
 | 
			
		||||
        public static ulong MiB(this ulong value) => value.KiB() * 1024;
 | 
			
		||||
        public static ulong MB(this ulong value) => value.KB() * 1000;
 | 
			
		||||
    public static ulong MiB(this ulong value) => value.KiB() * 1024;
 | 
			
		||||
    public static ulong MB(this ulong value) => value.KB() * 1000;
 | 
			
		||||
 | 
			
		||||
        public static ulong GiB(this ulong value) => value.MiB() * 1024;
 | 
			
		||||
        public static ulong GB(this ulong value) => value.MB() * 1000;
 | 
			
		||||
    public static ulong GiB(this ulong value) => value.MiB() * 1024;
 | 
			
		||||
    public static ulong GB(this ulong value) => value.MB() * 1000;
 | 
			
		||||
 | 
			
		||||
        public static bool IsInteger(this decimal number) => number == Math.Truncate(number);
 | 
			
		||||
    public static bool IsInteger(this decimal number) => number == Math.Truncate(number);
 | 
			
		||||
 | 
			
		||||
        public static DateTimeOffset ToUnixTimestamp(this double number)
 | 
			
		||||
            => new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(number);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    public static DateTimeOffset ToUnixTimestamp(this double number)
 | 
			
		||||
        => new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(number);
 | 
			
		||||
}
 | 
			
		||||
@@ -2,109 +2,106 @@
 | 
			
		||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 | 
			
		||||
// https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class ProcessExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class ProcessExtensions
 | 
			
		||||
    private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
 | 
			
		||||
    private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
 | 
			
		||||
 | 
			
		||||
    public static void KillTree(this Process process)
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
 | 
			
		||||
        private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
 | 
			
		||||
        process.KillTree(_defaultTimeout);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        public static void KillTree(this Process process)
 | 
			
		||||
    public static void KillTree(this Process process, TimeSpan timeout)
 | 
			
		||||
    {
 | 
			
		||||
        if (_isWindows)
 | 
			
		||||
        {
 | 
			
		||||
            process.KillTree(_defaultTimeout);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void KillTree(this Process process, TimeSpan timeout)
 | 
			
		||||
        {
 | 
			
		||||
            if (_isWindows)
 | 
			
		||||
            {
 | 
			
		||||
                RunProcessAndWaitForExit(
 | 
			
		||||
                    "taskkill",
 | 
			
		||||
                    $"/T /F /PID {process.Id}",
 | 
			
		||||
                    timeout,
 | 
			
		||||
                    out var stdout);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var children = new HashSet<int>();
 | 
			
		||||
                GetAllChildIdsUnix(process.Id, children, timeout);
 | 
			
		||||
                foreach (var childId in children)
 | 
			
		||||
                {
 | 
			
		||||
                    KillProcessUnix(childId, timeout);
 | 
			
		||||
                }
 | 
			
		||||
                KillProcessUnix(process.Id, timeout);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
 | 
			
		||||
        {
 | 
			
		||||
            var exitCode = RunProcessAndWaitForExit(
 | 
			
		||||
                "pgrep",
 | 
			
		||||
                $"-P {parentId}",
 | 
			
		||||
            RunProcessAndWaitForExit(
 | 
			
		||||
                "taskkill",
 | 
			
		||||
                $"/T /F /PID {process.Id}",
 | 
			
		||||
                timeout,
 | 
			
		||||
                out var stdout);
 | 
			
		||||
 | 
			
		||||
            if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var children = new HashSet<int>();
 | 
			
		||||
            GetAllChildIdsUnix(process.Id, children, timeout);
 | 
			
		||||
            foreach (var childId in children)
 | 
			
		||||
            {
 | 
			
		||||
                using (var reader = new StringReader(stdout))
 | 
			
		||||
                {
 | 
			
		||||
                    while (true)
 | 
			
		||||
                    {
 | 
			
		||||
                        var text = reader.ReadLine();
 | 
			
		||||
                        if (text is null)
 | 
			
		||||
                        {
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                KillProcessUnix(childId, timeout);
 | 
			
		||||
            }
 | 
			
		||||
            KillProcessUnix(process.Id, timeout);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
                        if (int.TryParse(text, out var id))
 | 
			
		||||
                        {
 | 
			
		||||
                            children.Add(id);
 | 
			
		||||
                            // Recursively get the children
 | 
			
		||||
                            GetAllChildIdsUnix(id, children, timeout);
 | 
			
		||||
                        }
 | 
			
		||||
    private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
 | 
			
		||||
    {
 | 
			
		||||
        var exitCode = RunProcessAndWaitForExit(
 | 
			
		||||
            "pgrep",
 | 
			
		||||
            $"-P {parentId}",
 | 
			
		||||
            timeout,
 | 
			
		||||
            out var stdout);
 | 
			
		||||
 | 
			
		||||
        if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
 | 
			
		||||
        {
 | 
			
		||||
            using (var reader = new StringReader(stdout))
 | 
			
		||||
            {
 | 
			
		||||
                while (true)
 | 
			
		||||
                {
 | 
			
		||||
                    var text = reader.ReadLine();
 | 
			
		||||
                    if (text is null)
 | 
			
		||||
                    {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (int.TryParse(text, out var id))
 | 
			
		||||
                    {
 | 
			
		||||
                        children.Add(id);
 | 
			
		||||
                        // Recursively get the children
 | 
			
		||||
                        GetAllChildIdsUnix(id, children, timeout);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void KillProcessUnix(int processId, TimeSpan timeout)
 | 
			
		||||
        {
 | 
			
		||||
            RunProcessAndWaitForExit(
 | 
			
		||||
                "kill",
 | 
			
		||||
                $"-TERM {processId}",
 | 
			
		||||
                timeout,
 | 
			
		||||
                out var stdout);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
 | 
			
		||||
        {
 | 
			
		||||
            var startInfo = new ProcessStartInfo
 | 
			
		||||
            {
 | 
			
		||||
                FileName = fileName,
 | 
			
		||||
                Arguments = arguments,
 | 
			
		||||
                RedirectStandardOutput = true,
 | 
			
		||||
                UseShellExecute = false
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var process = Process.Start(startInfo);
 | 
			
		||||
 | 
			
		||||
            stdout = null;
 | 
			
		||||
            if (process.WaitForExit((int)timeout.TotalMilliseconds))
 | 
			
		||||
            {
 | 
			
		||||
                stdout = process.StandardOutput.ReadToEnd();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                process.Kill();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return process.ExitCode;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private static void KillProcessUnix(int processId, TimeSpan timeout)
 | 
			
		||||
    {
 | 
			
		||||
        RunProcessAndWaitForExit(
 | 
			
		||||
            "kill",
 | 
			
		||||
            $"-TERM {processId}",
 | 
			
		||||
            timeout,
 | 
			
		||||
            out var stdout);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
 | 
			
		||||
    {
 | 
			
		||||
        var startInfo = new ProcessStartInfo
 | 
			
		||||
        {
 | 
			
		||||
            FileName = fileName,
 | 
			
		||||
            Arguments = arguments,
 | 
			
		||||
            RedirectStandardOutput = true,
 | 
			
		||||
            UseShellExecute = false
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var process = Process.Start(startInfo);
 | 
			
		||||
 | 
			
		||||
        stdout = null;
 | 
			
		||||
        if (process.WaitForExit((int)timeout.TotalMilliseconds))
 | 
			
		||||
        {
 | 
			
		||||
            stdout = process.StandardOutput.ReadToEnd();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            process.Kill();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return process.ExitCode;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,61 +1,58 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using SixLabors.ImageSharp;
 | 
			
		||||
using SixLabors.ImageSharp;
 | 
			
		||||
using SixLabors.ImageSharp.Formats;
 | 
			
		||||
using SixLabors.ImageSharp.Formats.Gif;
 | 
			
		||||
using SixLabors.ImageSharp.Formats.Png;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
using SixLabors.ImageSharp.Processing;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class Rgba32Extensions
 | 
			
		||||
{
 | 
			
		||||
    public static class Rgba32Extensions
 | 
			
		||||
    public static Image<Rgba32> Merge(this IEnumerable<Image<Rgba32>> images)
 | 
			
		||||
    {
 | 
			
		||||
        public static Image<Rgba32> Merge(this IEnumerable<Image<Rgba32>> images)
 | 
			
		||||
        return images.Merge(out _);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Image<Rgba32> Merge(this IEnumerable<Image<Rgba32>> images, out IImageFormat format)
 | 
			
		||||
    {
 | 
			
		||||
        format = PngFormat.Instance;
 | 
			
		||||
 | 
			
		||||
        void DrawFrame(Image<Rgba32>[] imgArray, Image<Rgba32> imgFrame, int frameNumber)
 | 
			
		||||
        {
 | 
			
		||||
            return images.Merge(out _);
 | 
			
		||||
            var xOffset = 0;
 | 
			
		||||
            for (var i = 0; i < imgArray.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
 | 
			
		||||
                imgFrame.Mutate(x => x.DrawImage(frame, new Point(xOffset, 0), new GraphicsOptions()));
 | 
			
		||||
                xOffset += imgArray[i].Bounds().Width;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Image<Rgba32> Merge(this IEnumerable<Image<Rgba32>> images, out IImageFormat format)
 | 
			
		||||
        var imgs = images.ToArray();
 | 
			
		||||
        var frames = images.Max(x => x.Frames.Count);
 | 
			
		||||
 | 
			
		||||
        var width = imgs.Sum(img => img.Width);
 | 
			
		||||
        var height = imgs.Max(img => img.Height);
 | 
			
		||||
        var canvas = new Image<Rgba32>(width, height);
 | 
			
		||||
        if (frames == 1)
 | 
			
		||||
        {
 | 
			
		||||
            format = PngFormat.Instance;
 | 
			
		||||
 | 
			
		||||
            void DrawFrame(Image<Rgba32>[] imgArray, Image<Rgba32> imgFrame, int frameNumber)
 | 
			
		||||
            {
 | 
			
		||||
                var xOffset = 0;
 | 
			
		||||
                for (var i = 0; i < imgArray.Length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
 | 
			
		||||
                    imgFrame.Mutate(x => x.DrawImage(frame, new Point(xOffset, 0), new GraphicsOptions()));
 | 
			
		||||
                    xOffset += imgArray[i].Bounds().Width;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var imgs = images.ToArray();
 | 
			
		||||
            var frames = images.Max(x => x.Frames.Count);
 | 
			
		||||
 | 
			
		||||
            var width = imgs.Sum(img => img.Width);
 | 
			
		||||
            var height = imgs.Max(img => img.Height);
 | 
			
		||||
            var canvas = new Image<Rgba32>(width, height);
 | 
			
		||||
            if (frames == 1)
 | 
			
		||||
            {
 | 
			
		||||
                DrawFrame(imgs, canvas, 0);
 | 
			
		||||
                return canvas;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            format = GifFormat.Instance;
 | 
			
		||||
            for (var j = 0; j < frames; j++)
 | 
			
		||||
            {
 | 
			
		||||
                using var imgFrame = new Image<Rgba32>(width, height);
 | 
			
		||||
                DrawFrame(imgs, imgFrame, j);
 | 
			
		||||
 | 
			
		||||
                var frameToAdd = imgFrame.Frames[0];
 | 
			
		||||
                frameToAdd.Metadata.GetGifMetadata().DisposalMethod = GifDisposalMethod.RestoreToBackground;
 | 
			
		||||
                canvas.Frames.AddFrame(frameToAdd);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            canvas.Frames.RemoveFrame(0);
 | 
			
		||||
            DrawFrame(imgs, canvas, 0);
 | 
			
		||||
            return canvas;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        format = GifFormat.Instance;
 | 
			
		||||
        for (var j = 0; j < frames; j++)
 | 
			
		||||
        {
 | 
			
		||||
            using var imgFrame = new Image<Rgba32>(width, height);
 | 
			
		||||
            DrawFrame(imgs, imgFrame, j);
 | 
			
		||||
 | 
			
		||||
            var frameToAdd = imgFrame.Frames[0];
 | 
			
		||||
            frameToAdd.Metadata.GetGifMetadata().DisposalMethod = GifDisposalMethod.RestoreToBackground;
 | 
			
		||||
            canvas.Frames.AddFrame(frameToAdd);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        canvas.Frames.RemoveFrame(0);
 | 
			
		||||
        return canvas;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,191 +1,187 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class StringExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static class StringExtensions
 | 
			
		||||
    public static string PadBoth(this string str, int length)
 | 
			
		||||
    {
 | 
			
		||||
        public static string PadBoth(this string str, int length)
 | 
			
		||||
        {
 | 
			
		||||
            int spaces = length - str.Length;
 | 
			
		||||
            int padLeft = spaces / 2 + str.Length;
 | 
			
		||||
            return str.PadLeft(padLeft).PadRight(length);
 | 
			
		||||
        }
 | 
			
		||||
        int spaces = length - str.Length;
 | 
			
		||||
        int padLeft = spaces / 2 + str.Length;
 | 
			
		||||
        return str.PadLeft(padLeft).PadRight(length);
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
        public static T MapJson<T>(this string str)
 | 
			
		||||
            => JsonConvert.DeserializeObject<T>(str);
 | 
			
		||||
    public static T MapJson<T>(this string str)
 | 
			
		||||
        => JsonConvert.DeserializeObject<T>(str);
 | 
			
		||||
 | 
			
		||||
        private static readonly HashSet<char> lettersAndDigits = new HashSet<char>(Enumerable.Range(48, 10)
 | 
			
		||||
            .Concat(Enumerable.Range(65, 26))
 | 
			
		||||
            .Concat(Enumerable.Range(97, 26))
 | 
			
		||||
            .Select(x => (char)x));
 | 
			
		||||
    private static readonly HashSet<char> lettersAndDigits = new HashSet<char>(Enumerable.Range(48, 10)
 | 
			
		||||
        .Concat(Enumerable.Range(65, 26))
 | 
			
		||||
        .Concat(Enumerable.Range(97, 26))
 | 
			
		||||
        .Select(x => (char)x));
 | 
			
		||||
 | 
			
		||||
        public static string StripHTML(this string input)
 | 
			
		||||
        {
 | 
			
		||||
            return Regex.Replace(input, "<.*?>", String.Empty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // todo future maybe use humanizer lib for this kind of work
 | 
			
		||||
        public static string TrimTo(this string str, int maxLength, bool hideDots = false)
 | 
			
		||||
        {
 | 
			
		||||
            if (maxLength < 0)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(maxLength), $"Argument {nameof(maxLength)} can't be negative.");
 | 
			
		||||
            if (maxLength == 0)
 | 
			
		||||
                return string.Empty;
 | 
			
		||||
            if (maxLength <= 3)
 | 
			
		||||
                return string.Concat(str.Select(c => '.'));
 | 
			
		||||
            if (str.Length < maxLength)
 | 
			
		||||
                return str;
 | 
			
		||||
 | 
			
		||||
            if (hideDots)
 | 
			
		||||
            {
 | 
			
		||||
                return string.Concat(str.Take(maxLength));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return string.Concat(str.Take(maxLength - 1)) + "…";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string ToTitleCase(this string str)
 | 
			
		||||
        {
 | 
			
		||||
            var tokens = str.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
            for (var i = 0; i < tokens.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var token = tokens[i];
 | 
			
		||||
                tokens[i] = token.Substring(0, 1).ToUpperInvariant() + token.Substring(1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return string.Join(" ", tokens)
 | 
			
		||||
                .Replace(" Of ", " of ")
 | 
			
		||||
                .Replace(" The ", " the ");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Removes trailing S or ES (if specified) on the given string if the num is 1
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="str"></param>
 | 
			
		||||
        /// <param name="num"></param>
 | 
			
		||||
        /// <param name="es"></param>
 | 
			
		||||
        /// <returns>String with the correct singular/plural form</returns>
 | 
			
		||||
        public static string SnPl(this string str, int? num, bool es = false)
 | 
			
		||||
        {
 | 
			
		||||
            if (str is null)
 | 
			
		||||
                throw new ArgumentNullException(nameof(str));
 | 
			
		||||
            if (num is null)
 | 
			
		||||
                throw new ArgumentNullException(nameof(num));
 | 
			
		||||
            return num == 1 ? str.Remove(str.Length - 1, es ? 2 : 1) : str;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //http://www.dotnetperls.com/levenshtein
 | 
			
		||||
        public static int LevenshteinDistance(this string s, string t)
 | 
			
		||||
        {
 | 
			
		||||
            var n = s.Length;
 | 
			
		||||
            var m = t.Length;
 | 
			
		||||
            var d = new int[n + 1, m + 1];
 | 
			
		||||
 | 
			
		||||
            // Step 1
 | 
			
		||||
            if (n == 0)
 | 
			
		||||
            {
 | 
			
		||||
                return m;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (m == 0)
 | 
			
		||||
            {
 | 
			
		||||
                return n;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Step 2
 | 
			
		||||
            for (var i = 0; i <= n; d[i, 0] = i++)
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var j = 0; j <= m; d[0, j] = j++)
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Step 3
 | 
			
		||||
            for (var i = 1; i <= n; i++)
 | 
			
		||||
            {
 | 
			
		||||
                //Step 4
 | 
			
		||||
                for (var j = 1; j <= m; j++)
 | 
			
		||||
                {
 | 
			
		||||
                    // Step 5
 | 
			
		||||
                    var cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
 | 
			
		||||
 | 
			
		||||
                    // Step 6
 | 
			
		||||
                    d[i, j] = Math.Min(
 | 
			
		||||
                        Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
 | 
			
		||||
                        d[i - 1, j - 1] + cost);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Step 7
 | 
			
		||||
            return d[n, m];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task<Stream> ToStream(this string str)
 | 
			
		||||
        {
 | 
			
		||||
            var ms = new MemoryStream();
 | 
			
		||||
            var sw = new StreamWriter(ms);
 | 
			
		||||
            await sw.WriteAsync(str).ConfigureAwait(false);
 | 
			
		||||
            await sw.FlushAsync().ConfigureAwait(false);
 | 
			
		||||
            ms.Position = 0;
 | 
			
		||||
            return ms;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static readonly Regex filterRegex = new Regex(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
 | 
			
		||||
        public static bool IsDiscordInvite(this string str)
 | 
			
		||||
            => filterRegex.IsMatch(str);
 | 
			
		||||
 | 
			
		||||
        public static string Unmention(this string str) => str.Replace("@", "ම", StringComparison.InvariantCulture);
 | 
			
		||||
 | 
			
		||||
        public static string SanitizeMentions(this string str, bool sanitizeRoleMentions = false)
 | 
			
		||||
        {
 | 
			
		||||
            str = str.Replace("@everyone", "@everyοne", StringComparison.InvariantCultureIgnoreCase)
 | 
			
		||||
                     .Replace("@here", "@һere", StringComparison.InvariantCultureIgnoreCase);
 | 
			
		||||
            if (sanitizeRoleMentions)
 | 
			
		||||
                str = str.SanitizeRoleMentions();
 | 
			
		||||
    public static string StripHTML(this string input)
 | 
			
		||||
    {
 | 
			
		||||
        return Regex.Replace(input, "<.*?>", String.Empty);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // todo future maybe use humanizer lib for this kind of work
 | 
			
		||||
    public static string TrimTo(this string str, int maxLength, bool hideDots = false)
 | 
			
		||||
    {
 | 
			
		||||
        if (maxLength < 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(maxLength), $"Argument {nameof(maxLength)} can't be negative.");
 | 
			
		||||
        if (maxLength == 0)
 | 
			
		||||
            return string.Empty;
 | 
			
		||||
        if (maxLength <= 3)
 | 
			
		||||
            return string.Concat(str.Select(c => '.'));
 | 
			
		||||
        if (str.Length < maxLength)
 | 
			
		||||
            return str;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string SanitizeRoleMentions(this string str) =>
 | 
			
		||||
            str.Replace("<@&", "<ම&", StringComparison.InvariantCultureIgnoreCase);
 | 
			
		||||
 | 
			
		||||
        public static string SanitizeAllMentions(this string str) =>
 | 
			
		||||
            str.SanitizeMentions().SanitizeRoleMentions();
 | 
			
		||||
 | 
			
		||||
        public static string ToBase64(this string plainText)
 | 
			
		||||
        if (hideDots)
 | 
			
		||||
        {
 | 
			
		||||
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
 | 
			
		||||
            return Convert.ToBase64String(plainTextBytes);
 | 
			
		||||
            return string.Concat(str.Take(maxLength));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetInitials(this string txt, string glue = "") =>
 | 
			
		||||
            string.Join(glue, txt.Split(' ').Select(x => x.FirstOrDefault()));
 | 
			
		||||
 | 
			
		||||
        public static bool IsAlphaNumeric(this string txt) =>
 | 
			
		||||
            txt.All(c => lettersAndDigits.Contains(c));
 | 
			
		||||
 | 
			
		||||
        private static readonly Regex CodePointRegex
 | 
			
		||||
            = new Regex(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
 | 
			
		||||
                RegexOptions.Compiled);
 | 
			
		||||
        
 | 
			
		||||
        public static string UnescapeUnicodeCodePoints(this string input)
 | 
			
		||||
        { 
 | 
			
		||||
            return CodePointRegex.Replace(input, me =>
 | 
			
		||||
            {
 | 
			
		||||
                var str = me.Groups["code"].Value;
 | 
			
		||||
                var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
 | 
			
		||||
                return newString;
 | 
			
		||||
            });
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return string.Concat(str.Take(maxLength - 1)) + "…";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public static string ToTitleCase(this string str)
 | 
			
		||||
    {
 | 
			
		||||
        var tokens = str.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
        for (var i = 0; i < tokens.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            var token = tokens[i];
 | 
			
		||||
            tokens[i] = token.Substring(0, 1).ToUpperInvariant() + token.Substring(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return string.Join(" ", tokens)
 | 
			
		||||
            .Replace(" Of ", " of ")
 | 
			
		||||
            .Replace(" The ", " the ");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Removes trailing S or ES (if specified) on the given string if the num is 1
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="str"></param>
 | 
			
		||||
    /// <param name="num"></param>
 | 
			
		||||
    /// <param name="es"></param>
 | 
			
		||||
    /// <returns>String with the correct singular/plural form</returns>
 | 
			
		||||
    public static string SnPl(this string str, int? num, bool es = false)
 | 
			
		||||
    {
 | 
			
		||||
        if (str is null)
 | 
			
		||||
            throw new ArgumentNullException(nameof(str));
 | 
			
		||||
        if (num is null)
 | 
			
		||||
            throw new ArgumentNullException(nameof(num));
 | 
			
		||||
        return num == 1 ? str.Remove(str.Length - 1, es ? 2 : 1) : str;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //http://www.dotnetperls.com/levenshtein
 | 
			
		||||
    public static int LevenshteinDistance(this string s, string t)
 | 
			
		||||
    {
 | 
			
		||||
        var n = s.Length;
 | 
			
		||||
        var m = t.Length;
 | 
			
		||||
        var d = new int[n + 1, m + 1];
 | 
			
		||||
 | 
			
		||||
        // Step 1
 | 
			
		||||
        if (n == 0)
 | 
			
		||||
        {
 | 
			
		||||
            return m;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (m == 0)
 | 
			
		||||
        {
 | 
			
		||||
            return n;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Step 2
 | 
			
		||||
        for (var i = 0; i <= n; d[i, 0] = i++)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (var j = 0; j <= m; d[0, j] = j++)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Step 3
 | 
			
		||||
        for (var i = 1; i <= n; i++)
 | 
			
		||||
        {
 | 
			
		||||
            //Step 4
 | 
			
		||||
            for (var j = 1; j <= m; j++)
 | 
			
		||||
            {
 | 
			
		||||
                // Step 5
 | 
			
		||||
                var cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
 | 
			
		||||
 | 
			
		||||
                // Step 6
 | 
			
		||||
                d[i, j] = Math.Min(
 | 
			
		||||
                    Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
 | 
			
		||||
                    d[i - 1, j - 1] + cost);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Step 7
 | 
			
		||||
        return d[n, m];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task<Stream> ToStream(this string str)
 | 
			
		||||
    {
 | 
			
		||||
        var ms = new MemoryStream();
 | 
			
		||||
        var sw = new StreamWriter(ms);
 | 
			
		||||
        await sw.WriteAsync(str).ConfigureAwait(false);
 | 
			
		||||
        await sw.FlushAsync().ConfigureAwait(false);
 | 
			
		||||
        ms.Position = 0;
 | 
			
		||||
        return ms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex filterRegex = new Regex(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
 | 
			
		||||
    public static bool IsDiscordInvite(this string str)
 | 
			
		||||
        => filterRegex.IsMatch(str);
 | 
			
		||||
 | 
			
		||||
    public static string Unmention(this string str) => str.Replace("@", "ම", StringComparison.InvariantCulture);
 | 
			
		||||
 | 
			
		||||
    public static string SanitizeMentions(this string str, bool sanitizeRoleMentions = false)
 | 
			
		||||
    {
 | 
			
		||||
        str = str.Replace("@everyone", "@everyοne", StringComparison.InvariantCultureIgnoreCase)
 | 
			
		||||
            .Replace("@here", "@һere", StringComparison.InvariantCultureIgnoreCase);
 | 
			
		||||
        if (sanitizeRoleMentions)
 | 
			
		||||
            str = str.SanitizeRoleMentions();
 | 
			
		||||
 | 
			
		||||
        return str;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static string SanitizeRoleMentions(this string str) =>
 | 
			
		||||
        str.Replace("<@&", "<ම&", StringComparison.InvariantCultureIgnoreCase);
 | 
			
		||||
 | 
			
		||||
    public static string SanitizeAllMentions(this string str) =>
 | 
			
		||||
        str.SanitizeMentions().SanitizeRoleMentions();
 | 
			
		||||
 | 
			
		||||
    public static string ToBase64(this string plainText)
 | 
			
		||||
    {
 | 
			
		||||
        var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
 | 
			
		||||
        return Convert.ToBase64String(plainTextBytes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static string GetInitials(this string txt, string glue = "") =>
 | 
			
		||||
        string.Join(glue, txt.Split(' ').Select(x => x.FirstOrDefault()));
 | 
			
		||||
 | 
			
		||||
    public static bool IsAlphaNumeric(this string txt) =>
 | 
			
		||||
        txt.All(c => lettersAndDigits.Contains(c));
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex CodePointRegex
 | 
			
		||||
        = new Regex(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
 | 
			
		||||
            RegexOptions.Compiled);
 | 
			
		||||
        
 | 
			
		||||
    public static string UnescapeUnicodeCodePoints(this string input)
 | 
			
		||||
    { 
 | 
			
		||||
        return CodePointRegex.Replace(input, me =>
 | 
			
		||||
        {
 | 
			
		||||
            var str = me.Groups["code"].Value;
 | 
			
		||||
            var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
 | 
			
		||||
            return newString;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user