diff --git a/DisCatSharp/Utilities.cs b/DisCatSharp/Utilities.cs
index 6f0ea4337..afb5c8158 100644
--- a/DisCatSharp/Utilities.cs
+++ b/DisCatSharp/Utilities.cs
@@ -1,464 +1,464 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Net;
using Microsoft.Extensions.Logging;
namespace DisCatSharp
{
///
/// Various Discord-related utilities.
///
public static class Utilities
{
///
/// Gets the version of the library
///
internal static string VersionHeader { get; set; }
///
/// Gets or sets the permission strings.
///
internal static Dictionary PermissionStrings { get; set; }
///
/// Gets the utf8 encoding
///
// ReSharper disable once InconsistentNaming
internal static UTF8Encoding UTF8 { get; } = new(false);
///
/// Initializes a new instance of the class.
///
static Utilities()
{
PermissionStrings = new Dictionary();
var t = typeof(Permissions);
var ti = t.GetTypeInfo();
var vals = Enum.GetValues(t).Cast();
foreach (var xv in vals)
{
var xsv = xv.ToString();
var xmv = ti.DeclaredMembers.FirstOrDefault(xm => xm.Name == xsv);
var xav = xmv.GetCustomAttribute();
PermissionStrings[xv] = xav.String;
}
var a = typeof(DiscordClient).GetTypeInfo().Assembly;
var vs = "";
var iv = a.GetCustomAttribute();
if (iv != null)
vs = iv.InformationalVersion;
else
{
var v = a.GetName().Version;
vs = v.ToString(3);
}
VersionHeader = $"DiscordBot (https://github.com/Aiko-IT-Systems/DisCatSharp, v{vs})";
}
///
/// Gets the api base uri.
///
/// The config
/// A string.
internal static string GetApiBaseUri(DiscordConfiguration config = null)
=> config == null ? Endpoints.BASE_URI + "9" : config.UseCanary ? Endpoints.CANARY_URI + config.ApiVersion : Endpoints.BASE_URI + config.ApiVersion;
///
/// Gets the api uri for.
///
/// The path.
/// The config
/// An Uri.
internal static Uri GetApiUriFor(string path, DiscordConfiguration config)
=> new($"{GetApiBaseUri(config)}{path}");
///
/// Gets the api uri for.
///
/// The path.
/// The query string.
/// The config
/// An Uri.
internal static Uri GetApiUriFor(string path, string queryString, DiscordConfiguration config)
=> new($"{GetApiBaseUri(config)}{path}{queryString}");
///
/// Gets the api uri builder for.
///
/// The path.
/// The config
/// A QueryUriBuilder.
internal static QueryUriBuilder GetApiUriBuilderFor(string path, DiscordConfiguration config)
=> new($"{GetApiBaseUri(config)}{path}");
///
/// Gets the formatted token.
///
/// The client.
/// A string.
internal static string GetFormattedToken(BaseDiscordClient client) => GetFormattedToken(client.Configuration);
///
/// Gets the formatted token.
///
/// The config.
/// A string.
internal static string GetFormattedToken(DiscordConfiguration config) =>
config.TokenType switch
{
TokenType.Bearer => $"Bearer {config.Token}",
TokenType.Bot => $"Bot {config.Token}",
_ => throw new ArgumentException("Invalid token type specified.", nameof(config.Token)),
};
///
/// Gets the base headers.
///
/// A Dictionary.
internal static Dictionary GetBaseHeaders()
=> new();
///
/// Gets the user agent.
///
/// A string.
internal static string GetUserAgent()
=> VersionHeader;
///
/// Contains the user mentions.
///
/// The message.
/// A bool.
internal static bool ContainsUserMentions(string message)
{
var pattern = @"<@(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
///
/// Contains the nickname mentions.
///
/// The message.
/// A bool.
internal static bool ContainsNicknameMentions(string message)
{
var pattern = @"<@!(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
///
/// Contains the channel mentions.
///
/// The message.
/// A bool.
internal static bool ContainsChannelMentions(string message)
{
var pattern = @"<#(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
///
/// Contains the role mentions.
///
/// The message.
/// A bool.
internal static bool ContainsRoleMentions(string message)
{
var pattern = @"<@&(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
///
/// Contains the emojis.
///
/// The message.
/// A bool.
internal static bool ContainsEmojis(string message)
{
var pattern = @"";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
///
/// Gets the user mentions.
///
/// The message.
/// A list of ulong.
internal static IEnumerable GetUserMentions(DiscordMessage message)
{
var regex = new Regex(@"<@!?(\d+)>", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
foreach (Match match in matches)
yield return ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
}
///
/// Gets the role mentions.
///
/// The message.
/// A list of ulong.
internal static IEnumerable GetRoleMentions(DiscordMessage message)
{
var regex = new Regex(@"<@&(\d+)>", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
foreach (Match match in matches)
yield return ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
}
///
/// Gets the channel mentions.
///
/// The message.
/// A list of ulong.
internal static IEnumerable GetChannelMentions(DiscordMessage message)
{
var regex = new Regex(@"<#(\d+)>", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
foreach (Match match in matches)
yield return ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
}
///
/// Gets the emojis.
///
/// The message.
/// A list of ulong.
internal static IEnumerable GetEmojis(DiscordMessage message)
{
var regex = new Regex(@"", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
foreach (Match match in matches)
yield return ulong.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
}
///
/// Are the valid slash command name.
///
/// The name.
/// A bool.
internal static bool IsValidSlashCommandName(string name)
{
- var regex = new Regex(@"^[\w-]{1,32}$", RegexOptions.ECMAScript);
+ var regex = new Regex(@"^[\w-]{1,32}$");
return regex.IsMatch(name);
}
///
/// Checks the thread auto archive duration feature.
///
/// The guild.
/// The taad.
/// A bool.
internal static bool CheckThreadAutoArchiveDurationFeature(DiscordGuild guild, ThreadAutoArchiveDuration taad) =>
taad == ThreadAutoArchiveDuration.ThreeDays
? guild.PremiumTier.HasFlag(PremiumTier.TierOne) || guild.Features.CanSetThreadArchiveDurationThreeDays
: taad != ThreadAutoArchiveDuration.OneWeek || guild.PremiumTier.HasFlag(PremiumTier.TierTwo) || guild.Features.CanSetThreadArchiveDurationSevenDays;
///
/// Checks the thread private feature.
///
/// The guild.
/// A bool.
internal static bool CheckThreadPrivateFeature(DiscordGuild guild) => guild.PremiumTier.HasFlag(PremiumTier.TierTwo) || guild.Features.CanCreatePrivateThreads;
///
/// Have the message intents.
///
/// The intents.
/// A bool.
internal static bool HasMessageIntents(DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMessages) || intents.HasIntent(DiscordIntents.DirectMessages);
///
/// Have the reaction intents.
///
/// The intents.
/// A bool.
internal static bool HasReactionIntents(DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMessageReactions) || intents.HasIntent(DiscordIntents.DirectMessageReactions);
///
/// Have the typing intents.
///
/// The intents.
/// A bool.
internal static bool HasTypingIntents(DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMessageTyping) || intents.HasIntent(DiscordIntents.DirectMessageTyping);
// https://discord.com/developers/docs/topics/gateway#sharding-sharding-formula
///
/// Gets a shard id from a guild id and total shard count.
///
/// The guild id the shard is on.
/// The total amount of shards.
/// The shard id.
public static int GetShardId(ulong guildId, int shardCount)
=> (int)(guildId >> 22) % shardCount;
///
/// Helper method to create a from Unix time seconds for targets that do not support this natively.
///
/// Unix time seconds to convert.
/// Whether the method should throw on failure. Defaults to true.
/// Calculated .
public static DateTimeOffset GetDateTimeOffset(long unixTime, bool shouldThrow = true)
{
try
{
return DateTimeOffset.FromUnixTimeSeconds(unixTime);
}
catch (Exception)
{
if (shouldThrow)
throw;
return DateTimeOffset.MinValue;
}
}
///
/// Helper method to create a from Unix time milliseconds for targets that do not support this natively.
///
/// Unix time milliseconds to convert.
/// Whether the method should throw on failure. Defaults to true.
/// Calculated .
public static DateTimeOffset GetDateTimeOffsetFromMilliseconds(long unixTime, bool shouldThrow = true)
{
try
{
return DateTimeOffset.FromUnixTimeMilliseconds(unixTime);
}
catch (Exception)
{
if (shouldThrow)
throw;
return DateTimeOffset.MinValue;
}
}
///
/// Helper method to calculate Unix time seconds from a for targets that do not support this natively.
///
/// to calculate Unix time for.
/// Calculated Unix time.
public static long GetUnixTime(DateTimeOffset dto)
=> dto.ToUnixTimeMilliseconds();
///
/// Computes a timestamp from a given snowflake.
///
/// Snowflake to compute a timestamp from.
/// Computed timestamp.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTimeOffset GetSnowflakeTime(this ulong snowflake)
=> DiscordClient.DiscordEpoch.AddMilliseconds(snowflake >> 22);
///
/// Converts this into human-readable format.
///
/// Permissions enumeration to convert.
/// Human-readable permissions.
public static string ToPermissionString(this Permissions perm)
{
if (perm == Permissions.None)
return PermissionStrings[perm];
perm &= PermissionMethods.FullPerms;
var strs = PermissionStrings
.Where(xkvp => xkvp.Key != Permissions.None && (perm & xkvp.Key) == xkvp.Key)
.Select(xkvp => xkvp.Value);
return string.Join(", ", strs.OrderBy(xs => xs));
}
///
/// Checks whether this string contains given characters.
///
/// String to check.
/// Characters to check for.
/// Whether the string contained these characters.
public static bool Contains(this string str, params char[] characters)
{
foreach (var xc in str)
if (characters.Contains(xc))
return true;
return false;
}
///
/// Logs the task fault.
///
/// The task.
/// The logger.
/// The level.
/// The event id.
/// The message.
internal static void LogTaskFault(this Task task, ILogger logger, LogLevel level, EventId eventId, string message)
{
if (task == null)
throw new ArgumentNullException(nameof(task));
if (logger == null)
return;
task.ContinueWith(t => logger.Log(level, eventId, t.Exception, message), TaskContinuationOptions.OnlyOnFaulted);
}
///
/// Deconstructs the.
///
/// The kvp.
/// The key.
/// The value.
internal static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value)
{
key = kvp.Key;
value = kvp.Value;
}
}
}