mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-12 02:08:27 -04:00
- More code cleanup and codestyle updates
- Fixed some possible nullref exceptions - Methods signatures now have up to 3 parameters before breakaing down each parameter in a separate line - Method invocations have the same rule, except the first parameter will be in the same line as the invocation to prevent some ugliness when passing lambas as arguments - Applied many more codestyles - Extensions folder fully reformatted
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
// and they get looped through constantly
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
private static int x = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new array from the old array + new element at the end
|
||||
/// </summary>
|
||||
@@ -14,11 +17,16 @@ public static class ArrayExtensions
|
||||
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);
|
||||
Array.Copy(input,
|
||||
0,
|
||||
newCrs,
|
||||
0,
|
||||
input.Length
|
||||
);
|
||||
newCrs[input.Length] = added;
|
||||
return newCrs;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new array by applying the specified function to every element in the input array
|
||||
/// </summary>
|
||||
|
85
src/NadekoBot/_Extensions/EnumerableExtensions.cs
Normal file
85
src/NadekoBot/_Extensions/EnumerableExtensions.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Security.Cryptography;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Concatenates the members of a collection, using the specified separator between each member.
|
||||
/// </summary>
|
||||
/// <param name="data">Collection to join</param>
|
||||
/// <param name="separator">The character to use as a separator. separator is included in the returned string only if values has more than one element.</param>
|
||||
/// <param name="func">Optional transformation to apply to each element before concatenation.</param>
|
||||
/// <typeparam name="T">The type of the members of values.</typeparam>
|
||||
/// <returns>A string that consists of the members of values delimited by the separator character. -or- Empty if values has no elements.</returns>
|
||||
public static string Join<T>(this IEnumerable<T> data, char separator, Func<T, string> func = null)
|
||||
=> string.Join(separator, data.Select(func ?? (x => x?.ToString() ?? string.Empty)));
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates the members of a collection, using the specified separator between each member.
|
||||
/// </summary>
|
||||
/// <param name="data">Collection to join</param>
|
||||
/// <param name="separator">The string to use as a separator.separator is included in the returned string only if values has more than one element.</param>
|
||||
/// <param name="func">Optional transformation to apply to each element before concatenation.</param>
|
||||
/// <typeparam name="T">The type of the members of values.</typeparam>
|
||||
/// <returns>A string that consists of the members of values delimited by the separator character. -or- Empty if values has no elements.</returns>
|
||||
public static string Join<T>(this IEnumerable<T> data, string separator, Func<T, string> func = null)
|
||||
=> string.Join(separator, data.Select(func ?? (x => x?.ToString() ?? string.Empty)));
|
||||
|
||||
/// <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--;
|
||||
(list[k], list[n]) = (list[n], list[k]);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/> class
|
||||
/// that contains elements copied from the specified <see cref="IEnumerable{T}" />
|
||||
/// has the default concurrency level, has the default initial capacity,
|
||||
/// and uses the default comparer for the key type.
|
||||
/// </summary>
|
||||
/// <param name="dict"> The <see cref="IEnumerable{T}" /> whose elements are copied to the new <see cref="ConcurrentDictionary{TKey,TValue}"/>.</param>
|
||||
/// <returns>A new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/> class</returns>
|
||||
public static ConcurrentDictionary<TKey, TValue> ToConcurrent<TKey, TValue>(
|
||||
this IEnumerable<KeyValuePair<TKey, TValue>> dict)
|
||||
=> new(dict);
|
||||
|
||||
public static IndexedCollection<T> ToIndexed<T>(this IEnumerable<T> enumerable) where T : class, IIndexed
|
||||
=> new(enumerable);
|
||||
|
||||
// todo use this extension instead of Task.WhenAll
|
||||
|
||||
/// <summary>
|
||||
/// Creates a task that will complete when all of the <see cref="Task{TResult}"/> objects in an enumerable
|
||||
/// collection have completed
|
||||
/// </summary>
|
||||
/// <param name="tasks">The tasks to wait on for completion.</param>
|
||||
/// <typeparam name="TResult">The type of the completed task.</typeparam>
|
||||
/// <returns>A task that represents the completion of all of the supplied tasks.</returns>
|
||||
public static Task<TResult[]> WhenAll<TResult>(this IEnumerable<Task<TResult>> tasks)
|
||||
=> Task.WhenAll(tasks);
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
using NadekoBot.Common.Collections;
|
||||
using Humanizer.Localisation;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -10,32 +10,34 @@ using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.Globalization;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using Color = Discord.Color;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
// todo imagesharp extensions
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static Regex UrlRegex = new(@"^(https?|ftp)://(?<path>[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled);
|
||||
private static readonly Regex _urlRegex =
|
||||
new(@"^(https?|ftp)://(?<path>[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled);
|
||||
|
||||
|
||||
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() ?? "";
|
||||
}),
|
||||
{
|
||||
x.Embed = set.GetEmbed().Build();
|
||||
x.Content = set.PlainText?.SanitizeMentions() ?? "";
|
||||
}
|
||||
),
|
||||
SmartPlainText spt => msg.ModifyAsync(x =>
|
||||
{
|
||||
x.Content = spt.Text.SanitizeMentions();
|
||||
x.Embed = null;
|
||||
}),
|
||||
{
|
||||
x.Content = spt.Text.SanitizeMentions();
|
||||
x.Embed = null;
|
||||
}
|
||||
),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
@@ -43,34 +45,29 @@ public static class Extensions
|
||||
=> 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 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 string ToPrettyStringHm(this TimeSpan span)
|
||||
=> span.Humanize(2, minUnit: TimeUnit.Second);
|
||||
|
||||
public static bool TryGetUrlPath(this string input, out string path)
|
||||
{
|
||||
var match = UrlRegex.Match(input);
|
||||
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)
|
||||
? maybeEmote
|
||||
: new Emoji(emojiStr);
|
||||
=> Emote.TryParse(emojiStr, out var maybeEmote) ? maybeEmote : new Emoji(emojiStr);
|
||||
|
||||
// https://github.com/SixLabors/Samples/blob/master/ImageSharp/AvatarWithRoundedCorner/Program.cs
|
||||
public static IImageProcessingContext ApplyRoundedCorners(this IImageProcessingContext ctx, float cornerRadius)
|
||||
@@ -79,22 +76,29 @@ public static class Extensions
|
||||
var 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
|
||||
});
|
||||
{
|
||||
Antialias = true,
|
||||
// enforces that any part of this shape that has color is punched out of the background
|
||||
AlphaCompositionMode = PixelAlphaCompositionMode.DestOut
|
||||
}
|
||||
);
|
||||
|
||||
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);
|
||||
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
|
||||
var cornerTopLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius));
|
||||
@@ -110,57 +114,43 @@ public static class Extensions
|
||||
var cornerBottomLeft = cornerTopLeft.RotateDegree(-90).Translate(0, bottomPos);
|
||||
var cornerBottomRight = cornerTopLeft.RotateDegree(180).Translate(rightPos, bottomPos);
|
||||
|
||||
return new PathCollection(cornerTopLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
|
||||
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));
|
||||
}
|
||||
=> bc.Token[..10];
|
||||
|
||||
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)
|
||||
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)
|
||||
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));
|
||||
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));
|
||||
private static string MethodName(this CommandInfo cmd)
|
||||
=> ((NadekoCommandAttribute)cmd.Attributes.FirstOrDefault(x => x is NadekoCommandAttribute))?.MethodName ??
|
||||
cmd.Name;
|
||||
|
||||
public static string GetFullUsage(string commandName, string args, string prefix)
|
||||
private 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)
|
||||
@@ -171,25 +161,36 @@ public static class Extensions
|
||||
return embed.WithFooter(curPage.ToString());
|
||||
}
|
||||
|
||||
public static Color ToDiscordColor(this Rgba32 color) => new(color.R, color.G, color.B);
|
||||
|
||||
public static IEmbedBuilder WithOkColor(this IEmbedBuilder eb) =>
|
||||
eb.WithColor(EmbedColor.Ok);
|
||||
public static Color ToDiscordColor(this Rgba32 color)
|
||||
=> new(color.R, color.G, color.B);
|
||||
|
||||
public static IEmbedBuilder WithPendingColor(this IEmbedBuilder eb) =>
|
||||
eb.WithColor(EmbedColor.Pending);
|
||||
|
||||
public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb) =>
|
||||
eb.WithColor(EmbedColor.Error);
|
||||
public static IEmbedBuilder WithOkColor(this IEmbedBuilder eb)
|
||||
=> eb.WithColor(EmbedColor.Ok);
|
||||
|
||||
public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Func<SocketReaction, Task> reactionAdded, Func<SocketReaction, Task> reactionRemoved = null)
|
||||
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)); };
|
||||
wrap.OnReactionAdded += r =>
|
||||
{
|
||||
var _ = Task.Run(() => reactionAdded(r));
|
||||
};
|
||||
wrap.OnReactionRemoved += r =>
|
||||
{
|
||||
var _ = Task.Run(() => reactionRemoved(r));
|
||||
};
|
||||
return wrap;
|
||||
}
|
||||
|
||||
@@ -203,24 +204,28 @@ public static class Extensions
|
||||
{
|
||||
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");
|
||||
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);
|
||||
await Task.Delay(seconds * 1000).ConfigureAwait(false);
|
||||
if (logService != null)
|
||||
{
|
||||
logService.AddDeleteIgnore(msg.Id);
|
||||
}
|
||||
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); }
|
||||
catch { }
|
||||
}
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); }
|
||||
catch { }
|
||||
});
|
||||
);
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -230,33 +235,18 @@ public static class Extensions
|
||||
{
|
||||
module = module.Parent;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this HashSet<T> target, IEnumerable<T> elements) where T : class
|
||||
public static async Task<IEnumerable<IGuildUser>> GetMembersAsync(this IRole role)
|
||||
{
|
||||
foreach (var item in elements)
|
||||
{
|
||||
target.Add(item);
|
||||
}
|
||||
var users = await role.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false);
|
||||
return users.Where(u => u.RoleIds.Contains(role.Id));
|
||||
}
|
||||
|
||||
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);
|
||||
public static string ToJson<T>(this T any, JsonSerializerOptions options = null)
|
||||
=> JsonSerializer.Serialize(any, options);
|
||||
|
||||
/// <summary>
|
||||
/// Adds fallback fonts to <see cref="TextOptions"/>
|
||||
@@ -270,6 +260,7 @@ public static class Extensions
|
||||
{
|
||||
opts.FallbackFonts.Add(ff);
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
@@ -294,12 +285,11 @@ public static class Extensions
|
||||
}
|
||||
else
|
||||
{
|
||||
img.SaveAsPng(imageStream, new()
|
||||
{
|
||||
ColorType = PngColorType.RgbWithAlpha,
|
||||
CompressionLevel = PngCompressionLevel.BestCompression
|
||||
});
|
||||
img.SaveAsPng(imageStream,
|
||||
new() { ColorType = PngColorType.RgbWithAlpha, CompressionLevel = PngCompressionLevel.BestCompression }
|
||||
);
|
||||
}
|
||||
|
||||
imageStream.Position = 0;
|
||||
return imageStream;
|
||||
}
|
||||
@@ -311,27 +301,20 @@ public static class Extensions
|
||||
return ms;
|
||||
}
|
||||
|
||||
public static IEnumerable<IRole> GetRoles(this IGuildUser user) =>
|
||||
user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r != null);
|
||||
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 owner = await guild.GetOwnerAsync();
|
||||
|
||||
return await owner.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
public static bool IsImage(this HttpResponseMessage msg) => IsImage(msg, out _);
|
||||
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")
|
||||
if (mimeType is "image/png" or "image/jpeg" or "image/gif")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -342,12 +325,12 @@ public static class Extensions
|
||||
return null;
|
||||
}
|
||||
|
||||
return msg.Content.Headers.ContentLength / 1.MB();
|
||||
return msg.Content.Headers.ContentLength.Value / 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,73 +0,0 @@
|
||||
using System.Security.Cryptography;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
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;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/// <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(dict);
|
||||
|
||||
public static IndexedCollection<T> ToIndexed<T>(this IEnumerable<T> enumerable) where T : class, IIndexed =>
|
||||
new(enumerable);
|
||||
|
||||
public static Task<TData[]> WhenAll<TData>(this IEnumerable<Task<TData>> items)
|
||||
=> Task.WhenAll(items);
|
||||
}
|
@@ -1,20 +1,21 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class IMessageChannelExtensions
|
||||
public static class MessageChannelExtensions
|
||||
{
|
||||
public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "")
|
||||
=> ch.SendMessageAsync(msg, embed: embed.Build(),
|
||||
options: new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
|
||||
public static Task<IUserMessage> SendAsync(this IMessageChannel channel, string plainText, Embed embed, bool sanitizeAll = false)
|
||||
=> ch.SendMessageAsync(msg, embed: embed.Build(), options: new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
|
||||
public static Task<IUserMessage> SendAsync(
|
||||
this IMessageChannel channel,
|
||||
string plainText,
|
||||
Embed embed,
|
||||
bool sanitizeAll = false)
|
||||
{
|
||||
plainText = sanitizeAll
|
||||
? plainText?.SanitizeAllMentions() ?? ""
|
||||
: plainText?.SanitizeMentions() ?? "";
|
||||
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
|
||||
{
|
||||
@@ -26,89 +27,115 @@ public static class IMessageChannelExtensions
|
||||
// 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)
|
||||
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);
|
||||
var embed = eb.Create().WithErrorColor().WithDescription(error).WithTitle(title);
|
||||
|
||||
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
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());
|
||||
=> 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> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string title, string text, string url = null, string footer = null)
|
||||
=> 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);
|
||||
|
||||
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
var embed = eb.Create().WithOkColor().WithDescription(text).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> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string text)
|
||||
=> ch.SendMessageAsync("", embed: eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(text)
|
||||
.Build());
|
||||
=> 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)
|
||||
=> 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(howToPrint))))}
|
||||
```"
|
||||
);
|
||||
|
||||
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("➡");
|
||||
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 SendPaginatedConfirmAsync(this ICommandContext ctx,
|
||||
int currentPage, Func<int, IEmbedBuilder> pageFunc, int totalElements,
|
||||
int itemsPerPage, bool addPaginatedFooter = true)
|
||||
private static readonly IEmote _arrowLeft = new Emoji("⬅");
|
||||
private static readonly IEmote _arrowRight = 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);
|
||||
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 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)
|
||||
if (ctx.Guild is SocketGuild sg &&
|
||||
!sg.CurrentUser.GetPermissions((IGuildChannel)ctx.Channel).AddReactions)
|
||||
canPaginate = false;
|
||||
|
||||
|
||||
if (!canPaginate)
|
||||
embed.WithFooter("⚠️ AddReaction permission required for pagination.");
|
||||
else if (addPaginatedFooter)
|
||||
@@ -116,17 +143,18 @@ public static class IMessageChannelExtensions
|
||||
|
||||
var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
|
||||
if (lastPage == 0 || !canPaginate)
|
||||
if (lastPage == 0 ||
|
||||
!canPaginate)
|
||||
return;
|
||||
|
||||
await msg.AddReactionAsync(arrow_left).ConfigureAwait(false);
|
||||
await msg.AddReactionAsync(arrow_right).ConfigureAwait(false);
|
||||
await msg.AddReactionAsync(_arrowLeft).ConfigureAwait(false);
|
||||
await msg.AddReactionAsync(_arrowRight).ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(2000).ConfigureAwait(false);
|
||||
|
||||
var lastPageChange = DateTime.MinValue;
|
||||
|
||||
async Task changePage(SocketReaction r)
|
||||
async Task ChangePage(SocketReaction r)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -134,7 +162,7 @@ public static class IMessageChannelExtensions
|
||||
return;
|
||||
if (DateTime.UtcNow - lastPageChange < TimeSpan.FromSeconds(1))
|
||||
return;
|
||||
if (r.Emote.Name == arrow_left.Name)
|
||||
if (r.Emote.Name == _arrowLeft.Name)
|
||||
{
|
||||
if (currentPage == 0)
|
||||
return;
|
||||
@@ -144,7 +172,7 @@ public static class IMessageChannelExtensions
|
||||
toSend.AddPaginatedFooter(currentPage, lastPage);
|
||||
await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false);
|
||||
}
|
||||
else if (r.Emote.Name == arrow_right.Name)
|
||||
else if (r.Emote.Name == _arrowRight.Name)
|
||||
{
|
||||
if (lastPage > currentPage)
|
||||
{
|
||||
@@ -162,21 +190,23 @@ public static class IMessageChannelExtensions
|
||||
}
|
||||
}
|
||||
|
||||
using (msg.OnReaction((DiscordSocketClient)ctx.Client, changePage, changePage))
|
||||
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)
|
||||
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)));
|
||||
.Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser))
|
||||
);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -187,10 +217,10 @@ public static class IMessageChannelExtensions
|
||||
|
||||
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,83 +0,0 @@
|
||||
using Discord;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class IUserExtensions
|
||||
{
|
||||
public static async Task<IUserMessage> EmbedAsync(this IUser user, IEmbedBuilder embed, string msg = "")
|
||||
{
|
||||
var ch = await user.CreateDMChannelAsync();
|
||||
return await ch.EmbedAsync(embed, msg);
|
||||
}
|
||||
|
||||
public static async Task<IUserMessage> SendAsync(this IUser user, string plainText, Embed embed, bool sanitizeAll = false)
|
||||
{
|
||||
var ch = await user.CreateDMChannelAsync();
|
||||
return await ch.SendAsync(plainText, embed, sanitizeAll);
|
||||
}
|
||||
|
||||
public static async Task<IUserMessage> SendAsync(this IUser user, SmartText text, bool sanitizeAll = false)
|
||||
{
|
||||
var ch = await user.CreateDMChannelAsync();
|
||||
return await ch.SendAsync(text, sanitizeAll);
|
||||
}
|
||||
|
||||
public static async Task<IUserMessage> SendConfirmAsync(this IUser user, IEmbedBuilderService eb, string text)
|
||||
=> await user.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);
|
||||
|
||||
return await user.SendMessageAsync("", embed: embed.Build());
|
||||
}
|
||||
|
||||
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 user.SendMessageAsync("", embed: embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<IUserMessage> SendErrorAsync(this IUser user, IEmbedBuilderService eb, string error)
|
||||
=> await user
|
||||
.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)
|
||||
{
|
||||
await using var file = File.Open(filePath, FileMode.Open);
|
||||
return await UserExtensions.SendFileAsync(user, 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 UserExtensions.SendFileAsync(user, 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(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,7 +0,0 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class MusicExtensions
|
||||
{
|
||||
public static EmbedAuthorBuilder WithMusicIcon(this EmbedAuthorBuilder eab) =>
|
||||
eab.WithIconUrl("http://i.imgur.com/nhKS3PT.png");
|
||||
}
|
@@ -2,26 +2,52 @@
|
||||
|
||||
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 MiB(this int value) => value.KiB() * 1024;
|
||||
public static int MB(this int value) => value.KB() * 1000;
|
||||
public static int Kb(this int value)
|
||||
=> value * 1000;
|
||||
|
||||
public static int GiB(this int value) => value.MiB() * 1024;
|
||||
public static int GB(this int value) => value.MB() * 1000;
|
||||
public static int MiB(this int value)
|
||||
=> value.KiB() * 1024;
|
||||
|
||||
public static ulong KiB(this ulong value) => value * 1024;
|
||||
public static ulong KB(this ulong value) => value * 1000;
|
||||
public static int Mb(this int 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 int GiB(this int value)
|
||||
=> value.MiB() * 1024;
|
||||
|
||||
public static ulong GiB(this ulong value) => value.MiB() * 1024;
|
||||
public static ulong GB(this ulong value) => value.MB() * 1000;
|
||||
public static int Gb(this int value)
|
||||
=> value.Mb() * 1000;
|
||||
|
||||
public static bool IsInteger(this decimal number) => number == Math.Truncate(number);
|
||||
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 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 DateTimeOffset ToUnixTimestamp(this double number)
|
||||
=> new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(number);
|
||||
=> new DateTimeOffset(1970,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
TimeSpan.Zero
|
||||
).AddSeconds(number);
|
||||
}
|
@@ -13,19 +13,17 @@ public static class ProcessExtensions
|
||||
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public static void KillTree(this Process process)
|
||||
{
|
||||
process.KillTree(_defaultTimeout);
|
||||
}
|
||||
=> process.KillTree(_defaultTimeout);
|
||||
|
||||
public static void KillTree(this Process process, TimeSpan timeout)
|
||||
{
|
||||
if (_isWindows)
|
||||
{
|
||||
RunProcessAndWaitForExit(
|
||||
"taskkill",
|
||||
RunProcessAndWaitForExit("taskkill",
|
||||
$"/T /F /PID {process.Id}",
|
||||
timeout,
|
||||
out var stdout);
|
||||
out _
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -35,19 +33,21 @@ public static class ProcessExtensions
|
||||
{
|
||||
KillProcessUnix(childId, timeout);
|
||||
}
|
||||
|
||||
KillProcessUnix(process.Id, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
|
||||
{
|
||||
var exitCode = RunProcessAndWaitForExit(
|
||||
"pgrep",
|
||||
var exitCode = RunProcessAndWaitForExit("pgrep",
|
||||
$"-P {parentId}",
|
||||
timeout,
|
||||
out var stdout);
|
||||
out var stdout
|
||||
);
|
||||
|
||||
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
|
||||
if (exitCode == 0 &&
|
||||
!string.IsNullOrEmpty(stdout))
|
||||
{
|
||||
using var reader = new StringReader(stdout);
|
||||
while (true)
|
||||
@@ -69,27 +69,30 @@ public static class ProcessExtensions
|
||||
}
|
||||
|
||||
private static void KillProcessUnix(int processId, TimeSpan timeout)
|
||||
{
|
||||
RunProcessAndWaitForExit(
|
||||
"kill",
|
||||
=> RunProcessAndWaitForExit("kill",
|
||||
$"-TERM {processId}",
|
||||
timeout,
|
||||
out var stdout);
|
||||
}
|
||||
out _
|
||||
);
|
||||
|
||||
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
|
||||
private static int RunProcessAndWaitForExit(
|
||||
string fileName,
|
||||
string arguments,
|
||||
TimeSpan timeout,
|
||||
out string stdout)
|
||||
{
|
||||
stdout = null;
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = arguments,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false
|
||||
FileName = fileName, Arguments = arguments, RedirectStandardOutput = true, UseShellExecute = false
|
||||
};
|
||||
|
||||
var process = Process.Start(startInfo);
|
||||
|
||||
stdout = null;
|
||||
if (process is null)
|
||||
return -1;
|
||||
|
||||
if (process.WaitForExit((int)timeout.TotalMilliseconds))
|
||||
{
|
||||
stdout = process.StandardOutput.ReadToEnd();
|
||||
|
@@ -10,27 +10,26 @@ namespace NadekoBot.Extensions;
|
||||
public static class Rgba32Extensions
|
||||
{
|
||||
public static Image<Rgba32> Merge(this IEnumerable<Image<Rgba32>> images)
|
||||
{
|
||||
return images.Merge(out _);
|
||||
}
|
||||
=> 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)
|
||||
void DrawFrame(IList<Image<Rgba32>> imgArray, Image<Rgba32> imgFrame, int frameNumber)
|
||||
{
|
||||
var xOffset = 0;
|
||||
for (var i = 0; i < imgArray.Length; i++)
|
||||
for (var i = 0; i < imgArray.Count; i++)
|
||||
{
|
||||
var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
|
||||
imgFrame.Mutate(x => x.DrawImage(frame, new(xOffset, 0), new GraphicsOptions()));
|
||||
var offset = xOffset;
|
||||
imgFrame.Mutate(x => x.DrawImage(frame, new(offset, 0), new GraphicsOptions()));
|
||||
xOffset += imgArray[i].Bounds().Width;
|
||||
}
|
||||
}
|
||||
|
||||
var imgs = images.ToArray();
|
||||
var frames = images.Max(x => x.Frames.Count);
|
||||
var imgs = images.ToList();
|
||||
var frames = imgs.Max(x => x.Frames.Count);
|
||||
|
||||
var width = imgs.Sum(img => img.Width);
|
||||
var height = imgs.Max(img => img.Height);
|
||||
|
@@ -13,24 +13,21 @@ public static class StringExtensions
|
||||
var padLeft = (spaces / 2) + str.Length;
|
||||
return str.PadLeft(padLeft).PadRight(length);
|
||||
}
|
||||
|
||||
|
||||
public static T MapJson<T>(this string str)
|
||||
=> JsonConvert.DeserializeObject<T>(str);
|
||||
|
||||
private static readonly HashSet<char> lettersAndDigits = new(Enumerable.Range(48, 10)
|
||||
private static readonly HashSet<char> _lettersAndDigits = new(Enumerable.Range(48, 10)
|
||||
.Concat(Enumerable.Range(65, 26))
|
||||
.Concat(Enumerable.Range(97, 26))
|
||||
.Select(x => (char)x));
|
||||
.Select(x => (char)x)
|
||||
);
|
||||
|
||||
public static string StripHTML(this string input)
|
||||
{
|
||||
return Regex.Replace(input, "<.*?>", String.Empty);
|
||||
}
|
||||
public static string StripHtml(this string input)
|
||||
=> Regex.Replace(input, "<.*?>", String.Empty);
|
||||
|
||||
public static string TrimTo(this string str, int maxLength, bool hideDots = false)
|
||||
=> hideDots
|
||||
? str?.Truncate(maxLength, string.Empty)
|
||||
: str?.Truncate(maxLength);
|
||||
=> hideDots ? str?.Truncate(maxLength, string.Empty) : str?.Truncate(maxLength);
|
||||
|
||||
public static string ToTitleCase(this string str)
|
||||
{
|
||||
@@ -38,12 +35,10 @@ public static class StringExtensions
|
||||
for (var i = 0; i < tokens.Length; i++)
|
||||
{
|
||||
var token = tokens[i];
|
||||
tokens[i] = token.Substring(0, 1).ToUpperInvariant() + token.Substring(1);
|
||||
tokens[i] = token[..1].ToUpperInvariant() + token[1..];
|
||||
}
|
||||
|
||||
return string.Join(" ", tokens)
|
||||
.Replace(" Of ", " of ")
|
||||
.Replace(" The ", " the ");
|
||||
return string.Join(" ", tokens).Replace(" Of ", " of ").Replace(" The ", " the ");
|
||||
}
|
||||
|
||||
//http://www.dotnetperls.com/levenshtein
|
||||
@@ -83,11 +78,10 @@ public static class StringExtensions
|
||||
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);
|
||||
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];
|
||||
}
|
||||
@@ -102,11 +96,15 @@ public static class StringExtensions
|
||||
return ms;
|
||||
}
|
||||
|
||||
private static readonly Regex filterRegex = new(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public static bool IsDiscordInvite(this string str)
|
||||
=> filterRegex.IsMatch(str);
|
||||
private static readonly Regex _filterRegex = new(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase
|
||||
);
|
||||
|
||||
public static string Unmention(this string str) => str.Replace("@", "ම", StringComparison.InvariantCulture);
|
||||
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)
|
||||
{
|
||||
@@ -118,11 +116,11 @@ public static class StringExtensions
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string SanitizeRoleMentions(this string str) =>
|
||||
str.Replace("<@&", "<ම&", StringComparison.InvariantCultureIgnoreCase);
|
||||
public static string SanitizeRoleMentions(this string str)
|
||||
=> str.Replace("<@&", "<ම&", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
public static string SanitizeAllMentions(this string str) =>
|
||||
str.SanitizeMentions().SanitizeRoleMentions();
|
||||
public static string SanitizeAllMentions(this string str)
|
||||
=> str.SanitizeMentions().SanitizeRoleMentions();
|
||||
|
||||
public static string ToBase64(this string plainText)
|
||||
{
|
||||
@@ -130,23 +128,24 @@ public static class StringExtensions
|
||||
return Convert.ToBase64String(plainTextBytes);
|
||||
}
|
||||
|
||||
public static string GetInitials(this string txt, string glue = "") =>
|
||||
string.Join(glue, txt.Split(' ').Select(x => x.FirstOrDefault()));
|
||||
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));
|
||||
public static bool IsAlphaNumeric(this string txt)
|
||||
=> txt.All(c => _lettersAndDigits.Contains(c));
|
||||
|
||||
private static readonly Regex _codePointRegex =
|
||||
new(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
|
||||
private static readonly Regex CodePointRegex
|
||||
= new(@"(\\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;
|
||||
});
|
||||
}
|
||||
=> _codePointRegex.Replace(input,
|
||||
me =>
|
||||
{
|
||||
var str = me.Groups["code"].Value;
|
||||
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
|
||||
return newString;
|
||||
}
|
||||
);
|
||||
}
|
40
src/NadekoBot/_Extensions/UserExtensions.cs
Normal file
40
src/NadekoBot/_Extensions/UserExtensions.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class UserExtensions
|
||||
{
|
||||
public static async Task<IUserMessage> EmbedAsync(this IUser user, IEmbedBuilder embed, string msg = "")
|
||||
{
|
||||
var ch = await user.CreateDMChannelAsync();
|
||||
return await ch.EmbedAsync(embed, msg);
|
||||
}
|
||||
|
||||
public static async Task<IUserMessage> SendAsync(this IUser user, SmartText text, bool sanitizeAll = false)
|
||||
{
|
||||
var ch = await user.CreateDMChannelAsync();
|
||||
return await ch.SendAsync(text, sanitizeAll);
|
||||
}
|
||||
|
||||
public static async Task<IUserMessage> SendConfirmAsync(this IUser user, IEmbedBuilderService eb, string text)
|
||||
=> await user.SendMessageAsync("", embed: eb.Create().WithOkColor().WithDescription(text).Build());
|
||||
|
||||
public static async Task<IUserMessage> SendErrorAsync(this IUser user, IEmbedBuilderService eb, string error)
|
||||
=> await user.SendMessageAsync("", embed: eb.Create().WithErrorColor().WithDescription(error).Build());
|
||||
|
||||
public static async Task<IUserMessage> SendPendingAsync(this IUser user, IEmbedBuilderService eb, string message)
|
||||
=> await user.SendMessageAsync("", embed: eb.Create().WithPendingColor().WithDescription(message).Build());
|
||||
|
||||
// This method is used by everything that fetches the avatar from a user
|
||||
public static Uri RealAvatarUrl(this IUser usr, ushort size = 256)
|
||||
=> usr.AvatarId is null ? new(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)
|
||||
=> 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"
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user