NadekoBot/NadekoBot.Core/_Extensions/Extensions.cs

260 lines
10 KiB
C#

using Discord;
using Discord.Commands;
using Discord.WebSocket;
using ImageSharp;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NadekoBot.Common.Collections;
using SixLabors.Primitives;
using NadekoBot.Common;
using NadekoBot.Core.Services;
using SixLabors.Shapes;
using System.Numerics;
namespace NadekoBot.Extensions
{
public static class Extensions
{
// https://github.com/SixLabors/ImageSharp/tree/master/samples/AvatarWithRoundedCorner
public static void ApplyRoundedCorners(this Image<Rgba32> img, float cornerRadius)
{
var corners = BuildCorners(img.Width, img.Height, cornerRadius);
// now we have our corners time to draw them
img.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true)
{
BlenderMode = ImageSharp.PixelFormats.PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background
});
}
public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius)
{
// first create a square
var rect = new RectangularePolygon(-0.5f, -0.5f, cornerRadius, cornerRadius);
// then cut out of the square a circle so we are left with a corner
var cornerToptLeft = 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 orgional around the center of the image
var center = new Vector2(imageWidth / 2, imageHeight / 2);
float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1;
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1;
// move it across the width of the image - the width of the shape
var cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0);
var cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos);
var cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos);
return new PathCollection(cornerToptLeft, 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 obj, string name) where T : class
{
if (obj == null)
throw new ArgumentNullException(nameof(name));
}
public static ConcurrentDictionary<TKey, TValue> ToConcurrent<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> dict)
=> new ConcurrentDictionary<TKey, TValue>(dict);
public static bool IsAuthor(this IMessage msg, IDiscordClient client) =>
msg.Author?.Id == client.CurrentUser.Id;
public static string RealSummary(this CommandInfo cmd, string prefix) => string.Format(cmd.Summary, prefix);
public static string RealRemarks(this CommandInfo cmd, string prefix) => string.Format(cmd.Remarks, 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(NadekoBot.OkColor);
public static EmbedBuilder WithErrorColor(this EmbedBuilder eb) =>
eb.WithColor(NadekoBot.ErrorColor);
public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null)
{
if (reactionRemoved == null)
reactionRemoved = delegate { };
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 void AddFakeHeaders(this HttpClient http)
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1");
http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
}
public static IMessage DeleteAfter(this IUserMessage msg, int seconds)
{
Task.Run(async () =>
{
await Task.Delay(seconds * 1000);
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)).Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty<IGuildUser>();
public static string ToJson<T>(this T any, Formatting formatting = Formatting.Indented) =>
JsonConvert.SerializeObject(any, formatting);
public static MemoryStream ToStream(this ImageSharp.Image<Rgba32> img)
{
var imageStream = new MemoryStream();
img.SaveAsPng(imageStream, new ImageSharp.Formats.PngEncoder() { CompressionLevel = 9});
imageStream.Position = 0;
return imageStream;
}
/// <summary>
/// returns an IEnumerable with randomized element order
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
{
// Thanks to @Joe4Evr for finding a bug in the old version of the shuffle
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)
{
foreach (var elem in elems)
{
exec(elem);
}
return elems;
}
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 Image<Rgba32> Merge(this IEnumerable<Image<Rgba32>> images)
{
var imgs = images.ToArray();
var canvas = new Image<Rgba32>(imgs.Sum(img => img.Width), imgs.Max(img => img.Height));
var xOffset = 0;
for (int i = 0; i < imgs.Length; i++)
{
canvas.DrawImage(imgs[i], 100, default, new Point(xOffset, 0));
xOffset += imgs[i].Bounds.Width;
}
return canvas;
}
}
}