2016-08-15 14:57:40 +00:00
|
|
|
|
using Discord;
|
2016-12-21 08:33:47 +00:00
|
|
|
|
using Discord.Commands;
|
2017-01-06 22:58:06 +00:00
|
|
|
|
using Discord.WebSocket;
|
2016-11-25 12:14:44 +00:00
|
|
|
|
using ImageSharp;
|
2016-11-12 19:58:33 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2016-08-15 14:57:40 +00:00
|
|
|
|
using System;
|
2016-10-12 15:35:27 +00:00
|
|
|
|
using System.Collections.Concurrent;
|
2016-08-15 14:57:40 +00:00
|
|
|
|
using System.Collections.Generic;
|
2016-08-15 23:38:28 +00:00
|
|
|
|
using System.IO;
|
2016-08-15 14:57:40 +00:00
|
|
|
|
using System.Linq;
|
2016-08-21 16:00:03 +00:00
|
|
|
|
using System.Net.Http;
|
2016-08-16 12:11:45 +00:00
|
|
|
|
using System.Security.Cryptography;
|
2017-06-17 13:23:13 +00:00
|
|
|
|
using System.Text;
|
2016-08-15 14:57:40 +00:00
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Threading.Tasks;
|
2017-07-17 19:42:36 +00:00
|
|
|
|
using NadekoBot.Common.Collections;
|
2017-07-18 16:26:55 +00:00
|
|
|
|
using SixLabors.Primitives;
|
2017-09-29 22:46:33 +00:00
|
|
|
|
using NadekoBot.Common;
|
2017-10-13 04:14:54 +00:00
|
|
|
|
using NadekoBot.Core.Services;
|
2017-10-15 09:48:29 +00:00
|
|
|
|
using SixLabors.Shapes;
|
|
|
|
|
using System.Numerics;
|
2016-08-15 14:57:40 +00:00
|
|
|
|
|
|
|
|
|
namespace NadekoBot.Extensions
|
|
|
|
|
{
|
|
|
|
|
public static class Extensions
|
|
|
|
|
{
|
2017-10-15 09:48:29 +00:00
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-23 17:46:59 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// First 10 characters of teh bot token.
|
|
|
|
|
/// </summary>
|
2017-10-10 16:24:36 +00:00
|
|
|
|
public static string RedisKey(this IBotCredentials bc)
|
2017-10-09 22:04:02 +00:00
|
|
|
|
{
|
2017-10-10 16:24:36 +00:00
|
|
|
|
return bc.Token.Substring(0, 10);
|
2017-10-09 22:04:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-17 13:23:13 +00:00
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-02 01:44:59 +00:00
|
|
|
|
public static void ThrowIfNull<T>(this T obj, string name) where T : class
|
|
|
|
|
{
|
|
|
|
|
if (obj == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(name));
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-30 04:54:59 +00:00
|
|
|
|
public static ConcurrentDictionary<TKey, TValue> ToConcurrent<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> dict)
|
|
|
|
|
=> new ConcurrentDictionary<TKey, TValue>(dict);
|
|
|
|
|
|
2017-05-27 17:42:23 +00:00
|
|
|
|
public static bool IsAuthor(this IMessage msg, IDiscordClient client) =>
|
|
|
|
|
msg.Author?.Id == client.CurrentUser.Id;
|
|
|
|
|
|
2017-06-01 03:12:00 +00:00
|
|
|
|
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);
|
2017-05-08 11:43:38 +00:00
|
|
|
|
|
2017-07-15 13:08:34 +00:00
|
|
|
|
public static EmbedBuilder AddPaginatedFooter(this EmbedBuilder embed, int curPage, int? lastPage)
|
2017-01-11 16:15:59 +00:00
|
|
|
|
{
|
|
|
|
|
if (lastPage != null)
|
2017-06-14 15:19:27 +00:00
|
|
|
|
return embed.WithFooter(efb => efb.WithText($"{curPage + 1} / {lastPage + 1}"));
|
2017-01-11 16:15:59 +00:00
|
|
|
|
else
|
2017-03-09 01:17:18 +00:00
|
|
|
|
return embed.WithFooter(efb => efb.WithText(curPage.ToString()));
|
2017-01-11 16:15:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-15 13:08:34 +00:00
|
|
|
|
public static EmbedBuilder WithOkColor(this EmbedBuilder eb) =>
|
|
|
|
|
eb.WithColor(NadekoBot.OkColor);
|
|
|
|
|
|
|
|
|
|
public static EmbedBuilder WithErrorColor(this EmbedBuilder eb) =>
|
|
|
|
|
eb.WithColor(NadekoBot.ErrorColor);
|
|
|
|
|
|
2017-06-19 13:42:10 +00:00
|
|
|
|
public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null)
|
2017-01-11 16:15:59 +00:00
|
|
|
|
{
|
|
|
|
|
if (reactionRemoved == null)
|
|
|
|
|
reactionRemoved = delegate { };
|
|
|
|
|
|
2017-05-22 23:59:31 +00:00
|
|
|
|
var wrap = new ReactionEventWrapper(client, msg);
|
2017-06-06 03:02:06 +00:00
|
|
|
|
wrap.OnReactionAdded += (r) => { var _ = Task.Run(() => reactionAdded(r)); };
|
|
|
|
|
wrap.OnReactionRemoved += (r) => { var _ = Task.Run(() => reactionRemoved(r)); };
|
2017-01-06 22:58:06 +00:00
|
|
|
|
return wrap;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-21 16:00:03 +00:00
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-22 03:49:33 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-11 16:15:59 +00:00
|
|
|
|
public static ModuleInfo GetTopLevelModule(this ModuleInfo module)
|
|
|
|
|
{
|
2016-12-21 08:33:47 +00:00
|
|
|
|
while (module.Parent != null)
|
|
|
|
|
{
|
|
|
|
|
module = module.Parent;
|
|
|
|
|
}
|
|
|
|
|
return module;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-10 04:38:20 +00:00
|
|
|
|
public static void AddRange<T>(this HashSet<T> target, IEnumerable<T> elements) where T : class
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in elements)
|
|
|
|
|
{
|
|
|
|
|
target.Add(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-12 15:35:27 +00:00
|
|
|
|
public static void AddRange<T>(this ConcurrentHashSet<T> target, IEnumerable<T> elements) where T : class
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in elements)
|
|
|
|
|
{
|
|
|
|
|
target.Add(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-03 21:18:18 +00:00
|
|
|
|
public static double UnixTimestamp(this DateTime dt) => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
|
2016-08-21 22:37:39 +00:00
|
|
|
|
|
2017-07-21 04:56:21 +00:00
|
|
|
|
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>();
|
2017-07-15 13:08:34 +00:00
|
|
|
|
|
|
|
|
|
public static string ToJson<T>(this T any, Formatting formatting = Formatting.Indented) =>
|
|
|
|
|
JsonConvert.SerializeObject(any, formatting);
|
2017-01-11 16:15:59 +00:00
|
|
|
|
|
2017-09-10 01:52:34 +00:00
|
|
|
|
public static MemoryStream ToStream(this ImageSharp.Image<Rgba32> img)
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2017-07-15 13:08:34 +00:00
|
|
|
|
var imageStream = new MemoryStream();
|
2017-09-11 21:39:28 +00:00
|
|
|
|
img.SaveAsPng(imageStream, new ImageSharp.Formats.PngEncoder() { CompressionLevel = 9});
|
2017-07-15 13:08:34 +00:00
|
|
|
|
imageStream.Position = 0;
|
|
|
|
|
return imageStream;
|
2016-08-16 12:11:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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
|
2016-08-16 13:04:49 +00:00
|
|
|
|
using (var provider = RandomNumberGenerator.Create())
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2016-08-16 13:04:49 +00:00
|
|
|
|
var list = items.ToList();
|
|
|
|
|
var n = list.Count;
|
|
|
|
|
while (n > 1)
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2016-08-16 13:04:49 +00:00
|
|
|
|
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;
|
2016-08-16 12:11:45 +00:00
|
|
|
|
}
|
2016-08-16 13:04:49 +00:00
|
|
|
|
return list;
|
2016-08-16 12:11:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-28 02:59:32 +00:00
|
|
|
|
|
2017-07-15 13:08:34 +00:00
|
|
|
|
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> elems, Action<T> exec)
|
2016-11-29 20:24:49 +00:00
|
|
|
|
{
|
2017-07-15 13:08:34 +00:00
|
|
|
|
foreach (var elem in elems)
|
2016-11-29 20:24:49 +00:00
|
|
|
|
{
|
2017-07-15 13:08:34 +00:00
|
|
|
|
exec(elem);
|
2016-11-29 20:24:49 +00:00
|
|
|
|
}
|
2017-07-15 13:08:34 +00:00
|
|
|
|
return elems;
|
2016-11-29 20:24:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-15 13:08:34 +00:00
|
|
|
|
public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
|
2016-08-16 13:29:48 +00:00
|
|
|
|
{
|
2017-07-15 13:08:34 +00:00
|
|
|
|
var ms = new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite);
|
|
|
|
|
ms.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
return ms;
|
2016-08-16 13:29:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-15 13:08:34 +00:00
|
|
|
|
public static IEnumerable<IRole> GetRoles(this IGuildUser user) =>
|
|
|
|
|
user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r != null);
|
2016-08-16 13:29:48 +00:00
|
|
|
|
|
2017-07-15 13:08:34 +00:00
|
|
|
|
public static async Task<IMessage> SendMessageToOwnerAsync(this IGuild guild, string message)
|
2016-08-18 15:33:45 +00:00
|
|
|
|
{
|
2017-07-15 13:08:34 +00:00
|
|
|
|
var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).GetOrCreateDMChannelAsync()
|
|
|
|
|
.ConfigureAwait(false);
|
2016-08-18 15:33:45 +00:00
|
|
|
|
|
2017-07-15 13:08:34 +00:00
|
|
|
|
return await ownerPrivate.SendMessageAsync(message).ConfigureAwait(false);
|
2016-08-18 15:33:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-22 05:57:17 +00:00
|
|
|
|
public static Image<Rgba32> Merge(this IEnumerable<Image<Rgba32>> images)
|
2016-09-13 16:12:27 +00:00
|
|
|
|
{
|
2017-02-20 22:46:34 +00:00
|
|
|
|
var imgs = images.ToArray();
|
2016-09-13 16:12:27 +00:00
|
|
|
|
|
2017-07-18 16:26:55 +00:00
|
|
|
|
var canvas = new Image<Rgba32>(imgs.Sum(img => img.Width), imgs.Max(img => img.Height));
|
2016-09-13 16:12:27 +00:00
|
|
|
|
|
2017-02-20 22:46:34 +00:00
|
|
|
|
var xOffset = 0;
|
|
|
|
|
for (int i = 0; i < imgs.Length; i++)
|
2016-09-13 16:12:27 +00:00
|
|
|
|
{
|
2017-10-10 16:24:36 +00:00
|
|
|
|
canvas.DrawImage(imgs[i], 100, default, new Point(xOffset, 0));
|
2017-02-20 22:46:34 +00:00
|
|
|
|
xOffset += imgs[i].Bounds.Width;
|
2016-09-13 16:12:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return canvas;
|
|
|
|
|
}
|
2016-08-15 14:57:40 +00:00
|
|
|
|
}
|
2017-06-28 02:59:32 +00:00
|
|
|
|
}
|