diff --git a/DisCatSharp/Entities/User/DiscordUser.cs b/DisCatSharp/Entities/User/DiscordUser.cs
index 60772683b..ac95b680f 100644
--- a/DisCatSharp/Entities/User/DiscordUser.cs
+++ b/DisCatSharp/Entities/User/DiscordUser.cs
@@ -1,505 +1,513 @@
// 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.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
///
/// Represents a Discord user.
///
public class DiscordUser : SnowflakeObject, IEquatable
{
///
/// Initializes a new instance of the class.
///
internal DiscordUser()
{ }
///
/// Initializes a new instance of the class.
///
/// The transport user.
internal DiscordUser(TransportUser transport)
{
this.Id = transport.Id;
this.Username = transport.Username;
this.Discriminator = transport.Discriminator;
this.AvatarHash = transport.AvatarHash;
this.AvatarDecorationHash = transport.AvatarDecorationHash;
this.BannerHash = transport.BannerHash;
this.BannerColorInternal = transport.BannerColor;
this.ThemeColorsInternal = (transport.ThemeColors ?? Array.Empty()).ToList();
this.IsBot = transport.IsBot;
this.MfaEnabled = transport.MfaEnabled;
this.Verified = transport.Verified;
this.Email = transport.Email;
this.PremiumType = transport.PremiumType;
this.Locale = transport.Locale;
this.Flags = transport.Flags;
this.OAuthFlags = transport.OAuthFlags;
this.Bio = transport.Bio;
this.Pronouns = transport.Pronouns;
}
///
/// Gets this user's username.
///
[JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Username { get; internal set; }
///
/// Gets this user's username with the discriminator.
/// Example: Discord#0000
///
[JsonIgnore]
public virtual string UsernameWithDiscriminator
=> $"{this.Username}#{this.Discriminator}";
///
/// Gets the user's 4-digit discriminator.
///
[JsonProperty("discriminator", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Discriminator { get; internal set; }
///
/// Gets the discriminator integer.
///
[JsonIgnore]
internal int DiscriminatorInt
=> int.Parse(this.Discriminator, NumberStyles.Integer, CultureInfo.InvariantCulture);
///
/// Gets the user's banner color, if set. Mutually exclusive with .
///
[JsonIgnore]
public virtual DiscordColor? BannerColor
=> !this.BannerColorInternal.HasValue ? null : new DiscordColor(this.BannerColorInternal.Value);
///
/// Gets the user's theme colors, if set.
///
[JsonIgnore]
public virtual IReadOnlyList? ThemeColors
=> !(this.ThemeColorsInternal is not null && this.ThemeColorsInternal.Count != 0) ? null : this.ThemeColorsInternal.Select(x => new DiscordColor(x)).ToList();
///
/// Gets the user's banner color integer.
///
[JsonProperty("accent_color")]
internal int? BannerColorInternal;
///
/// Gets the user's theme color integers.
///
[JsonProperty("theme_colors", NullValueHandling = NullValueHandling.Ignore)]
internal List? ThemeColorsInternal;
///
/// Gets the user's banner url
///
[JsonIgnore]
public string BannerUrl
=> string.IsNullOrWhiteSpace(this.BannerHash) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}?size=4096";
///
/// Gets the user's profile banner hash. Mutually exclusive with .
///
[JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)]
public virtual string BannerHash { get; internal set; }
///
/// Gets the users bio.
/// This is not available to bots tho.
///
[JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Bio { get; internal set; }
///
/// Gets the user's avatar hash.
///
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)]
public virtual string AvatarHash { get; internal set; }
///
/// Gets the user's avatar decoration hash.
///
[JsonProperty("avatar_decoration", NullValueHandling = NullValueHandling.Ignore)]
public virtual string AvatarDecorationHash { get; internal set; }
///
/// Returns a uri to this users profile.
///
[JsonIgnore]
public Uri ProfileUri => new($"{DiscordDomain.GetDomain(CoreDomain.Discord).Url}{Endpoints.USERS}/{this.Id}");
///
/// Returns a string representing the direct URL to this users profile.
///
/// The URL of this users profile.
[JsonIgnore]
public string ProfileUrl => this.ProfileUri.AbsoluteUri;
///
/// Gets the user's avatar url.
///
[JsonIgnore]
public string AvatarUrl
=> string.IsNullOrWhiteSpace(this.AvatarHash) ? this.DefaultAvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.AvatarHash}.{(this.AvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024";
///
/// Gets the user's avatar decoration url.
///
[JsonIgnore]
public string? AvatarDecorationUrl
=> string.IsNullOrWhiteSpace(this.AvatarDecorationHash) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS_DECORATIONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.AvatarDecorationHash}.{(this.AvatarDecorationHash.StartsWith("a_") ? "gif" : "png")}?size=1024";
///
/// Gets the URL of default avatar for this user.
///
[JsonIgnore]
public string DefaultAvatarUrl
=> $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMBED}{Endpoints.AVATARS}/{(this.DiscriminatorInt % 5).ToString(CultureInfo.InvariantCulture)}.png?size=1024";
///
/// Gets whether the user is a bot.
///
[JsonProperty("bot", NullValueHandling = NullValueHandling.Ignore)]
public virtual bool IsBot { get; internal set; }
///
/// Gets whether the user has multi-factor authentication enabled.
///
[JsonProperty("mfa_enabled", NullValueHandling = NullValueHandling.Ignore)]
public virtual bool? MfaEnabled { get; internal set; }
///
/// Gets whether the user is an official Discord system user.
///
[JsonProperty("system", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsSystem { get; internal set; }
///
/// Gets whether the user is verified.
/// This is only present in OAuth.
///
[JsonProperty("verified", NullValueHandling = NullValueHandling.Ignore)]
public virtual bool? Verified { get; internal set; }
///
/// Gets the user's email address.
/// This is only present in OAuth.
///
[JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Email { get; internal set; }
///
/// Gets the user's premium type.
///
[JsonProperty("premium_type", NullValueHandling = NullValueHandling.Ignore)]
public virtual PremiumType? PremiumType { get; internal set; }
///
/// Gets the user's chosen language
///
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Locale { get; internal set; }
///
/// Gets the user's flags for OAuth.
///
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public virtual UserFlags? OAuthFlags { get; internal set; }
///
/// Gets the user's flags.
///
[JsonProperty("public_flags", NullValueHandling = NullValueHandling.Ignore)]
public virtual UserFlags? Flags { get; internal set; }
///
/// Gets the user's pronouns.
///
[JsonProperty("pronouns", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Pronouns { get; internal set; }
///
/// Gets the user's mention string.
///
[JsonIgnore]
public string Mention
=> Formatter.Mention(this, this is DiscordMember);
///
/// Gets whether this user is the Client which created this object.
///
[JsonIgnore]
public bool IsCurrent
=> this.Id == this.Discord.CurrentUser.Id;
#region Extension of DiscordUser
///
/// Whether this member is a
///
///
[JsonIgnore]
public bool IsMod
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.CertifiedModerator);
///
/// Whether this member is a
///
///
[JsonIgnore]
public bool IsPartner
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.Partner);
///
/// Whether this member is a
///
///
[JsonIgnore]
public bool IsVerifiedBot
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.VerifiedBot);
///
/// Whether this member is a
///
///
[JsonIgnore]
public bool IsBotDev
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.VerifiedDeveloper);
+
+ ///
+ /// Whether this member is a
+ ///
+ ///
+ [JsonIgnore]
+ public bool IsActiveDeveloper
+ => this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.ActiveDeveloper);
///
/// Whether this member is a
///
///
[JsonIgnore]
public bool IsStaff
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.Staff);
#endregion
///
/// Fetches the user from the API.
///
/// The user with fresh data from the API.
public async Task GetFromApiAsync()
=> await this.Discord.ApiClient.GetUserAsync(this.Id);
///
/// Gets additional information about an application if the user is an bot.
///
/// The rpc info or
/// Thrown when the application does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetRpcInfoAsync()
=> this.IsBot ? await this.Discord.ApiClient.GetApplicationInfoAsync(this.Id) : await Task.FromResult(null);
///
/// Whether this user is in a
///
///
///
/// DiscordGuild guild = await Client.GetGuildAsync(806675511555915806);
/// DiscordUser user = await Client.GetUserAsync(469957180968271873);
/// Console.WriteLine($"{user.Username} {(user.IsInGuild(guild) ? "is a" : "is not a")} member of {guild.Name}");
///
/// results to J_M_Lutra is a member of Project Nyaw~.
///
///
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")]
public async Task IsInGuild(DiscordGuild guild)
{
try
{
var member = await guild.GetMemberAsync(this.Id);
return member is not null;
}
catch (NotFoundException)
{
return false;
}
}
///
/// Whether this user is not in a
///
///
///
public async Task IsNotInGuild(DiscordGuild guild)
=> !await this.IsInGuild(guild);
///
/// Returns the DiscordMember in the specified
///
/// The to get this user on.
/// The .
/// Thrown when the user is not part of the guild.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ConvertToMember(DiscordGuild guild)
=> await guild.GetMemberAsync(this.Id);
///
/// Unbans this user from a guild.
///
/// Guild to unban this user from.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the user does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task UnbanAsync(DiscordGuild guild, string reason = null)
=> guild.UnbanMemberAsync(this, reason);
///
/// Gets this user's presence.
///
[JsonIgnore]
public DiscordPresence Presence
=> this.Discord is DiscordClient dc && dc.Presences.TryGetValue(this.Id, out var presence) ? presence : null;
///
/// Gets the user's avatar URL, in requested format and size.
///
/// Format of the avatar to get.
/// Maximum size of the avatar. Must be a power of two, minimum 16, maximum 2048.
/// URL of the user's avatar.
public string GetAvatarUrl(ImageFormat fmt, ushort size = 1024)
{
if (fmt == ImageFormat.Unknown)
throw new ArgumentException("You must specify valid image format.", nameof(fmt));
if (size < 16 || size > 2048)
throw new ArgumentOutOfRangeException(nameof(size));
var log = Math.Log(size, 2);
if (log < 4 || log > 11 || log % 1 != 0)
throw new ArgumentOutOfRangeException(nameof(size));
var sfmt = "";
sfmt = fmt switch
{
ImageFormat.Gif => "gif",
ImageFormat.Jpeg => "jpg",
ImageFormat.Png => "png",
ImageFormat.WebP => "webp",
ImageFormat.Auto => !string.IsNullOrWhiteSpace(this.AvatarHash) ? this.AvatarHash.StartsWith("a_") ? "gif" : "png" : "png",
_ => throw new ArgumentOutOfRangeException(nameof(fmt)),
};
var ssize = size.ToString(CultureInfo.InvariantCulture);
if (!string.IsNullOrWhiteSpace(this.AvatarHash))
{
var id = this.Id.ToString(CultureInfo.InvariantCulture);
return $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{id}/{this.AvatarHash}.{sfmt}?size={ssize}";
}
else
{
var type = (this.DiscriminatorInt % 5).ToString(CultureInfo.InvariantCulture);
return $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMBED}{Endpoints.AVATARS}/{type}.{sfmt}?size={ssize}";
}
}
///
/// Returns a string representation of this user.
///
/// String representation of this user.
public override string ToString() => $"User {this.Id}; {this.Username}#{this.Discriminator}";
///
/// Checks whether this is equal to another object.
///
/// Object to compare to.
/// Whether the object is equal to this .
public override bool Equals(object obj) => this.Equals(obj as DiscordUser);
///
/// Checks whether this is equal to another .
///
/// to compare to.
/// Whether the is equal to this .
public bool Equals(DiscordUser e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
///
/// Gets the hash code for this .
///
/// The hash code for this .
public override int GetHashCode() => this.Id.GetHashCode();
///
/// Gets whether the two objects are equal.
///
/// First user to compare.
/// Second user to compare.
/// Whether the two users are equal.
public static bool operator ==(DiscordUser e1, DiscordUser e2)
{
var o1 = e1 as object;
var o2 = e2 as object;
return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || e1.Id == e2.Id);
}
///
/// Gets whether the two objects are not equal.
///
/// First user to compare.
/// Second user to compare.
/// Whether the two users are not equal.
public static bool operator !=(DiscordUser e1, DiscordUser e2)
=> !(e1 == e2);
}
///
/// Represents a user comparer.
///
internal class DiscordUserComparer : IEqualityComparer
{
///
/// Whether the users are equal.
///
/// The first user
/// The second user.
public bool Equals(DiscordUser x, DiscordUser y) => x.Equals(y);
///
/// Gets the hash code.
///
/// The user.
public int GetHashCode(DiscordUser obj) => obj.Id.GetHashCode();
}