Killed history

This commit is contained in:
Kwoth
2021-09-06 21:29:22 +02:00
commit 7aca29ae8a
950 changed files with 366651 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
using System;
namespace NadekoBot.Extensions
{
// 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)
{
var newCrs = new T[input.Length + 1];
Array.Copy(input, 0, newCrs, 0, input.Length);
newCrs[input.Length] = added;
return newCrs;
}
}
}

View File

@@ -0,0 +1,423 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common;
using NadekoBot.Common.Collections;
using NadekoBot.Core.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)://(?<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<IUserMessage> 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<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 == 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(NadekoBot.OkColor);
public static EmbedBuilder WithPendingColor(this EmbedBuilder eb) =>
eb.WithColor(NadekoBot.PendingColor);
public static EmbedBuilder WithErrorColor(this EmbedBuilder eb) =>
eb.WithColor(NadekoBot.ErrorColor);
public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Func<SocketReaction, Task> reactionAdded, Func<SocketReaction, Task> reactionRemoved = null)
{
if (reactionRemoved == 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<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, Formatting formatting = Formatting.Indented) =>
JsonConvert.SerializeObject(any, formatting);
/// <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 == null)
{
return null;
}
return msg.Content.Headers.ContentLength / 1.MB();
}
public static IEnumerable<Type> LoadFrom(this IServiceCollection collection, Assembly assembly)
{
// list of all the types which are added with this method
List<Type> addedTypes = new List<Type>();
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<Type>();
}
// 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<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract
#if GLOBAL_NADEKO
&& x.GetTypeInfo().GetCustomAttribute<NoPublicBotAttribute>() == 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<Type>(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;
}
}
}

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using NadekoBot.Common.Collections;
using NadekoBot.Core.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));
}
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 == 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);
}
}

View File

@@ -0,0 +1,167 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Extensions
{
public static class IMessageChannelExtensions
{
public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, EmbedBuilder embed, string msg = "")
=> ch.SendMessageAsync(msg, embed: embed.Build(),
options: new RequestOptions() { RetryMode = RetryMode.AlwaysRetry });
public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, string title, string error, string url = null, string footer = null)
{
var eb = new EmbedBuilder().WithErrorColor().WithDescription(error)
.WithTitle(title);
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
eb.WithUrl(url);
if (!string.IsNullOrWhiteSpace(footer))
eb.WithFooter(efb => efb.WithText(footer));
return ch.SendMessageAsync("", embed: eb.Build());
}
public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, string error)
=> ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error).Build());
public static Task<IUserMessage> SendPendingAsync(this IMessageChannel ch, string message)
=> ch.SendMessageAsync("", embed: new EmbedBuilder().WithPendingColor().WithDescription(message).Build());
public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, string title, string text, string url = null, string footer = null)
{
var eb = new EmbedBuilder().WithOkColor().WithDescription(text)
.WithTitle(title);
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
eb.WithUrl(url);
if (!string.IsNullOrWhiteSpace(footer))
eb.WithFooter(efb => efb.WithText(footer));
return ch.SendMessageAsync("", embed: eb.Build());
}
public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, string text)
=> ch.SendMessageAsync("", embed: new EmbedBuilder().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
{string.Join("\n", items.Chunk(columns)
.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);
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, EmbedBuilder> 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<EmbedBuilder>> 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 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
{
if (r.UserId != ctx.User.Id)
return;
if (DateTime.UtcNow - lastPageChange < TimeSpan.FromSeconds(1))
return;
if (r.Emote.Name == arrow_left.Name)
{
if (currentPage == 0)
return;
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)
{
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))
{
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("⚠️"));
}
}

View File

@@ -0,0 +1,63 @@
using Discord;
using NadekoBot.Core.Services.Database.Models;
using System;
using System.IO;
using System.Threading.Tasks;
namespace NadekoBot.Extensions
{
public static class IUserExtensions
{
public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string text)
=> await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text).Build()).ConfigureAwait(false);
public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string title, string text, string url = null)
{
var eb = new EmbedBuilder().WithOkColor().WithDescription(text).WithTitle(title);
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
eb.WithUrl(url);
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync("", embed: eb.Build()).ConfigureAwait(false);
}
public static async Task<IUserMessage> SendErrorAsync(this IUser user, string title, string error, string url = null)
{
var eb = new EmbedBuilder().WithErrorColor().WithDescription(error).WithTitle(title);
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
eb.WithUrl(url);
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync("", embed: eb.Build()).ConfigureAwait(false);
}
public static async Task<IUserMessage> SendErrorAsync(this IUser user, string error)
=> await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error).Build()).ConfigureAwait(false);
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 == 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 == 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");
}
}
}

View File

@@ -0,0 +1,23 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace NadekoBot.Extensions
{
public static class LinkedListExtensions
{
public static LinkedListNode<T>? FindNode<T>(this LinkedList<T> list, Func<T, bool> predicate)
{
var node = list.First;
while (!(node is null))
{
if (predicate(node.Value))
return node;
node = node.Next;
}
return null;
}
}
}

View File

@@ -0,0 +1,10 @@
using Discord;
namespace NadekoBot.Extensions
{
public static class MusicExtensions
{
public static EmbedAuthorBuilder WithMusicIcon(this EmbedAuthorBuilder eab) =>
eab.WithIconUrl("http://i.imgur.com/nhKS3PT.png");
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace NadekoBot.Extensions
{
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 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 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);
}
}

View File

@@ -0,0 +1,110 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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
{
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)
{
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}",
timeout,
out var stdout);
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
{
using (var reader = new StringReader(stdout))
{
while (true)
{
var text = reader.ReadLine();
if (text == 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;
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Linq;
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
{
public static class Rgba32Extensions
{
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)
{
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);
return canvas;
}
}
}

View File

@@ -0,0 +1,183 @@
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
{
public static class StringExtensions
{
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));
public static string StripHTML(this string input)
{
return Regex.Replace(input, "<.*?>", String.Empty);
}
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 - 3)) + "...";
}
}
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 == null)
throw new ArgumentNullException(nameof(str));
if (num == 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;
});
}
}
}