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(); }