using Discord; using Discord.Commands; using Discord.WebSocket; using Microsoft.Extensions.DependencyInjection; 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; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using AngleSharp.Attributes; using NadekoBot.Common.Attributes; using Serilog; namespace NadekoBot.Extensions { public static class Extensions { public static Regex UrlRegex = new Regex(@"^(https?|ftp)://(?[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled); public static TOut[] Map(this TIn[] arr, Func f) => Array.ConvertAll(arr, x => f(x)); public static Task EmbedAsync(this IMessageChannel channel, CREmbed crEmbed, bool sanitizeAll = false) { var plainText = sanitizeAll ? crEmbed.PlainText?.SanitizeAllMentions() ?? "" : crEmbed.PlainText?.SanitizeMentions() ?? ""; return channel.SendMessageAsync(plainText, embed: crEmbed.IsEmbedValid ? crEmbed.ToEmbed().Build() : null); } public static List GetGuildIds(this DiscordSocketClient client) => client.Guilds.Select(x => x.Id).ToList(); /// /// 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. /// /// Timespan to convert to string /// Formatted duration string 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); } /// /// First 10 characters of teh bot token. /// public static string RedisKey(this IBotCredentials bc) { return bc.Token.Substring(0, 10); } public static async Task ReplaceAsync(this Regex regex, string input, Func> 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(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 EmbedBuilder AddPaginatedFooter(this EmbedBuilder embed, int curPage, int? lastPage) { if (lastPage != null) return embed.WithFooter(efb => efb.WithText($"{curPage + 1} / {lastPage + 1}")); else return embed.WithFooter(efb => efb.WithText(curPage.ToString())); } public static EmbedBuilder WithOkColor(this EmbedBuilder eb) => eb.WithColor(Bot.OkColor); public static EmbedBuilder WithPendingColor(this EmbedBuilder eb) => eb.WithColor(Bot.PendingColor); public static EmbedBuilder WithErrorColor(this EmbedBuilder eb) => eb.WithColor(Bot.ErrorColor); public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Func reactionAdded, Func 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, LogCommandService 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(this HashSet target, IEnumerable elements) where T : class { foreach (var item in elements) { target.Add(item); } } public static void AddRange(this ConcurrentHashSet target, IEnumerable 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> GetMembersAsync(this IRole role) => (await role.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false)).Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty(); public static string ToJson(this T any, Formatting formatting = Formatting.Indented) => JsonConvert.SerializeObject(any, formatting); /// /// Adds fallback fonts to /// /// to which fallback fonts will be added to /// List of fallback Font Families to add /// The same to allow chaining public static TextOptions WithFallbackFonts(this TextOptions opts, List fallback) { foreach (var ff in fallback) { opts.FallbackFonts.Add(ff); } return opts; } /// /// Adds fallback fonts to /// /// to which fallback fonts will be added to /// List of fallback Font Families to add /// The same to allow chaining public static TextGraphicsOptions WithFallbackFonts(this TextGraphicsOptions opts, List fallback) { opts.TextOptions.WithFallbackFonts(fallback); return opts; } public static MemoryStream ToStream(this Image 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 bytes, bool canWrite = false) { var ms = new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite); ms.Seek(0, SeekOrigin.Begin); return ms; } public static IEnumerable GetRoles(this IGuildUser user) => user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r != null); public static async Task 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 IEnumerable LoadFrom(this IServiceCollection collection, Assembly assembly) { // list of all the types which are added with this method List addedTypes = new List(); Type[] allTypes; try { // first, get all types in te assembly allTypes = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { Log.Error(ex, "Error loading assembly types"); return Enumerable.Empty(); } // all types which have INService implementation are services // which are supposed to be loaded with this method // ignore all interfaces and abstract classes var services = new Queue(allTypes .Where(x => x.GetInterfaces().Contains(typeof(INService)) && !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract #if GLOBAL_NADEKO && x.GetTypeInfo().GetCustomAttribute() is null #endif ) .ToArray()); // we will just return those types when we're done instantiating them addedTypes.AddRange(services); // get all interfaces which inherit from INService // as we need to also add a service for each one of interfaces // so that DI works for them too var interfaces = new HashSet(allTypes .Where(x => x.GetInterfaces().Contains(typeof(INService)) && x.GetTypeInfo().IsInterface)); // keep instantiating until we've instantiated them all while (services.Count > 0) { var serviceType = services.Dequeue(); //get a type i need to add if (collection.FirstOrDefault(x => x.ServiceType == serviceType) != null) // if that type is already added, skip continue; //also add the same type var interfaceType = interfaces.FirstOrDefault(x => serviceType.GetInterfaces().Contains(x)); if (interfaceType != null) { addedTypes.Add(interfaceType); collection.AddSingleton(interfaceType, serviceType); } else { collection.AddSingleton(serviceType, serviceType); } } return addedTypes; } } }