diff --git a/DisCatSharp/Entities/Guild/DiscordMember.cs b/DisCatSharp/Entities/Guild/DiscordMember.cs index b4a598998..5b8f802d7 100644 --- a/DisCatSharp/Entities/Guild/DiscordMember.cs +++ b/DisCatSharp/Entities/Guild/DiscordMember.cs @@ -1,791 +1,790 @@ // 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.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using Newtonsoft.Json; namespace DisCatSharp.Entities; /// /// Represents a Discord guild member. /// public class DiscordMember : DiscordUser, IEquatable { /// /// Initializes a new instance of the class. /// internal DiscordMember() { this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Initializes a new instance of the class. /// /// The user. internal DiscordMember(DiscordUser user) { this.Discord = user.Discord; this.Id = user.Id; this.RoleIdsInternal = new List(); this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Initializes a new instance of the class. /// /// The mbr. internal DiscordMember(TransportMember mbr) { this.Id = mbr.User.Id; this.IsDeafened = mbr.IsDeafened; this.IsMuted = mbr.IsMuted; this.JoinedAt = mbr.JoinedAt; this.Nickname = mbr.Nickname; this.PremiumSince = mbr.PremiumSince; this.IsPending = mbr.IsPending; this.GuildAvatarHash = mbr.GuildAvatarHash; this.GuildBannerHash = mbr.GuildBannerHash; this.GuildBio = mbr.GuildBio; this.GuildPronouns = mbr.GuildPronouns; this.CommunicationDisabledUntil = mbr.CommunicationDisabledUntil; this.AvatarHashInternal = mbr.AvatarHash; this.RoleIdsInternal = mbr.Roles ?? new List(); this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); this.MemberFlags = mbr.MemberFlags; } /// /// Gets the members avatar hash. /// [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] public virtual string GuildAvatarHash { get; internal set; } /// /// Gets the members avatar URL. /// [JsonIgnore] public string GuildAvatarUrl => string.IsNullOrWhiteSpace(this.GuildAvatarHash) ? this.User.AvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.GuildAvatarHash}.{(this.GuildAvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// Gets the members banner hash. /// [JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)] public virtual string GuildBannerHash { get; internal set; } /// /// Gets the members banner URL. /// [JsonIgnore] public string GuildBannerUrl => string.IsNullOrWhiteSpace(this.GuildBannerHash) ? this.User.BannerUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.GuildBannerHash}.{(this.GuildBannerHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// The color of this member's banner. Mutually exclusive with . /// [JsonIgnore] public override DiscordColor? BannerColor => this.User.BannerColor; /// /// Gets this member's nickname. /// [JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)] public string Nickname { get; internal set; } /// /// Gets the members guild bio. /// This is not available to bots tho. /// [JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)] public string GuildBio { get; internal set; } /// /// Gets the members's pronouns. /// [JsonProperty("pronouns", NullValueHandling = NullValueHandling.Ignore)] public string GuildPronouns { get; internal set; } /// /// Gets the members flags. /// [JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)] public MemberFlags MemberFlags { get; internal set; } [JsonIgnore] internal string AvatarHashInternal; /// /// Gets this member's display name. /// [JsonIgnore] public string DisplayName => this.Nickname ?? this.Username; /// /// List of role ids /// [JsonIgnore] internal IReadOnlyList RoleIds => this._roleIdsLazy.Value; [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] internal List RoleIdsInternal; [JsonIgnore] private readonly Lazy> _roleIdsLazy; /// /// Gets the list of roles associated with this member. /// [JsonIgnore] public IEnumerable Roles => this.RoleIds.Select(id => this.Guild.GetRole(id)).Where(x => x != null); /// /// Gets the color associated with this user's top color-giving role, otherwise 0 (no color). /// [JsonIgnore] public DiscordColor Color { get { var role = this.Roles.OrderByDescending(xr => xr.Position).FirstOrDefault(xr => xr.Color.Value != 0); return role != null ? role.Color : new DiscordColor(); } } /// /// Date the user joined the guild /// [JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset JoinedAt { get; internal set; } /// /// Date the user started boosting this server /// [JsonProperty("premium_since", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset? PremiumSince { get; internal set; } /// /// Date until the can communicate again. /// [JsonProperty("communication_disabled_until", NullValueHandling = NullValueHandling.Include)] public DateTime? CommunicationDisabledUntil { get; internal set; } /// /// If the user is deafened /// [JsonProperty("is_deafened", NullValueHandling = NullValueHandling.Ignore)] public bool IsDeafened { get; internal set; } /// /// If the user is muted /// [JsonProperty("is_muted", NullValueHandling = NullValueHandling.Ignore)] public bool IsMuted { get; internal set; } /// /// Whether the user has not passed the guild's Membership Screening requirements yet. /// [JsonProperty("pending", NullValueHandling = NullValueHandling.Ignore)] public bool? IsPending { get; internal set; } /// /// Gets this member's voice state. /// [JsonIgnore] public DiscordVoiceState VoiceState => this.Discord.Guilds[this.GuildId].VoiceStates.TryGetValue(this.Id, out var voiceState) ? voiceState : null; [JsonIgnore] internal ulong GuildId = 0; /// /// Gets the guild of which this member is a part of. /// [JsonIgnore] public DiscordGuild Guild => this.Discord.Guilds[this.GuildId]; /// /// Gets whether this member is the Guild owner. /// [JsonIgnore] public bool IsOwner => this.Id == this.Guild.OwnerId; /// /// Gets the member's position in the role hierarchy, which is the member's highest role's position. Returns for the guild's owner. /// [JsonIgnore] public int Hierarchy => this.IsOwner ? int.MaxValue : this.RoleIds.Count == 0 ? 0 : this.Roles.Max(x => x.Position); /// /// Gets the permissions for the current member. /// [JsonIgnore] public Permissions Permissions => this.GetPermissions(); #region Overridden user properties /// /// Gets the user. /// [JsonIgnore] internal DiscordUser User => this.Discord.UserCache[this.Id]; /// /// Gets this member's username. /// [JsonIgnore] public override string Username { get => this.User.Username; internal set => this.User.Username = value; } /// /// Gets the member's 4-digit discriminator. /// [JsonIgnore] public override string Discriminator { get => this.User.Discriminator; internal set => this.User.Discriminator = value; } /// /// Gets the member's avatar hash. /// [JsonIgnore] public override string AvatarHash { get => this.User.AvatarHash; internal set => this.User.AvatarHash = value; } /// /// Gets the member's banner hash. /// [JsonIgnore] public override string BannerHash { get => this.User.BannerHash; internal set => this.User.BannerHash = value; } /// /// Gets whether the member is a bot. /// [JsonIgnore] public override bool IsBot { get => this.User.IsBot; internal set => this.User.IsBot = value; } /// /// Gets the member's email address. /// This is only present in OAuth. /// [JsonIgnore] public override string Email { get => this.User.Email; internal set => this.User.Email = value; } /// /// Gets whether the member has multi-factor authentication enabled. /// [JsonIgnore] public override bool? MfaEnabled { get => this.User.MfaEnabled; internal set => this.User.MfaEnabled = value; } /// /// Gets whether the member is verified. /// This is only present in OAuth. /// [JsonIgnore] public override bool? Verified { get => this.User.Verified; internal set => this.User.Verified = value; } /// /// Gets the member's chosen language /// [JsonIgnore] public override string Locale { get => this.User.Locale; internal set => this.User.Locale = value; } /// /// Gets the user's flags. /// [JsonIgnore] public override UserFlags? OAuthFlags { get => this.User.OAuthFlags; internal set => this.User.OAuthFlags = value; } /// /// Gets the member's flags for OAuth. /// [JsonIgnore] public override UserFlags? Flags { get => this.User.Flags; internal set => this.User.Flags = value; } #endregion /// /// Creates a direct message channel to this member. /// /// Direct message channel to this member. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateDmChannelAsync() => this.Discord.ApiClient.CreateDmAsync(this.Id); /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Content of the message to send. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(string content) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(content).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Embed to attach to the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(DiscordEmbed embed) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(embed).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Content of the message to send. /// Embed to attach to the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(string content, DiscordEmbed embed) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(content, embed).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Builder to with the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(DiscordMessageBuilder message) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(message).ConfigureAwait(false); } /// /// Sets this member's voice mute status. /// /// Whether the member is to be muted. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SetMuteAsync(bool mute, string reason = null) => this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, mute, default, default, reason); /// /// Sets this member's voice deaf status. /// /// Whether the member is to be deafened. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SetDeafAsync(bool deaf, string reason = null) => this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, default, deaf, default, reason); /// /// Modifies this member. /// /// Action to perform on this member. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyAsync(Action action) { var mdl = new MemberEditModel(); action(mdl); if (mdl.VoiceChannel.HasValue && mdl.VoiceChannel.Value != null && mdl.VoiceChannel.Value.Type != ChannelType.Voice && mdl.VoiceChannel.Value.Type != ChannelType.Stage) throw new ArgumentException("Given channel is not a voice or stage channel.", nameof(action)); if (mdl.Nickname.HasValue && this.Discord.CurrentUser.Id == this.Id) { await this.Discord.ApiClient.ModifyCurrentMemberNicknameAsync(this.Guild.Id, mdl.Nickname.Value, mdl.AuditLogReason).ConfigureAwait(false); await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, Optional.None, mdl.Roles.Map(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened, mdl.VoiceChannel.Map(e => e?.Id), mdl.AuditLogReason).ConfigureAwait(false); } else { await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, mdl.Nickname, mdl.Roles.Map(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened, mdl.VoiceChannel.Map(e => e?.Id), mdl.AuditLogReason).ConfigureAwait(false); } } /// /// Adds a timeout to a member. /// /// The datetime offset to time out the user. Up to 28 days. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task TimeoutAsync(DateTimeOffset until, string reason = null) => until.Subtract(DateTimeOffset.UtcNow).Days > 28 ? throw new ArgumentException("Timeout can not be longer than 28 days") : this.Discord.ApiClient.ModifyTimeoutAsync(this.Guild.Id, this.Id, until, reason); /// /// Adds a timeout to a member. /// /// The timespan to time out the user. Up to 28 days. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task TimeoutAsync(TimeSpan until, string reason = null) => this.TimeoutAsync(DateTimeOffset.UtcNow + until, reason); /// /// Adds a timeout to a member. /// /// The datetime to time out the user. Up to 28 days. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task TimeoutAsync(DateTime until, string reason = null) => this.TimeoutAsync(until.ToUniversalTime() - DateTime.UtcNow, reason); /// /// Removes the timeout from a member. /// /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task RemoveTimeoutAsync(string reason = null) => this.Discord.ApiClient.ModifyTimeoutAsync(this.Guild.Id, this.Id, null, reason); /// /// Grants a role to the member. /// /// Role to grant. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GrantRoleAsync(DiscordRole role, string reason = null) => this.Discord.ApiClient.AddGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason); /// /// Revokes a role from a member. /// /// Role to revoke. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task RevokeRoleAsync(DiscordRole role, string reason = null) => this.Discord.ApiClient.RemoveGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason); /// /// Sets the member's roles to ones specified. /// /// Roles to set. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ReplaceRolesAsync(IEnumerable roles, string reason = null) => this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, default, Optional.Some(roles.Select(xr => xr.Id)), default, default, default, reason); /// /// Bans this member from their guild. /// /// How many days to remove messages from. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task BanAsync(int deleteMessageDays = 0, string reason = null) => this.Guild.BanMemberAsync(this, deleteMessageDays, reason); /// /// Unbans this member from their guild. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task UnbanAsync(string reason = null) => this.Guild.UnbanMemberAsync(this, reason); /// /// Kicks this member from their guild. /// /// Reason for audit logs. /// - /// [alias="KickAsync"] /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task RemoveAsync(string reason = null) => this.Discord.ApiClient.RemoveGuildMemberAsync(this.GuildId, this.Id, reason); /// /// Moves this member to the specified voice channel /// /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task PlaceInAsync(DiscordChannel channel) => channel.PlaceMemberAsync(this); /// /// Updates the member's suppress state in a stage channel. /// /// The channel the member is currently in. /// Toggles the member's suppress state. /// Thrown when the channel in not a voice channel. public async Task UpdateVoiceStateAsync(DiscordChannel channel, bool? suppress) { if (channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated in a stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, channel.Id, suppress).ConfigureAwait(false); } /// /// Makes the user a speaker. /// /// Thrown when the user is not inside an stage channel. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task MakeSpeakerAsync() { var vs = this.VoiceState; if (vs == null || vs.Channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated when the user is inside an stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, vs.Channel.Id, false).ConfigureAwait(false); } /// /// Moves the user to audience. /// /// Thrown when the user is not inside an stage channel. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task MoveToAudienceAsync() { var vs = this.VoiceState; if (vs == null || vs.Channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated when the user is inside an stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, vs.Channel.Id, true).ConfigureAwait(false); } /// /// Calculates permissions in a given channel for this member. /// /// Channel to calculate permissions for. /// Calculated permissions for this member in the channel. public Permissions PermissionsIn(DiscordChannel channel) => channel.PermissionsFor(this); /// /// Get's the current member's roles based on the sum of the permissions of their given roles. /// private Permissions GetPermissions() { if (this.Guild.OwnerId == this.Id) return PermissionMethods.FullPerms; Permissions perms; // assign @everyone permissions var everyoneRole = this.Guild.EveryoneRole; perms = everyoneRole.Permissions; // assign permissions from member's roles (in order) perms |= this.Roles.Aggregate(Permissions.None, (c, role) => c | role.Permissions); // Administrator grants all permissions and cannot be overridden return (perms & Permissions.Administrator) == Permissions.Administrator ? PermissionMethods.FullPerms : perms; } /// /// Returns a string representation of this member. /// /// String representation of this member. public override string ToString() => $"Member {this.Id}; {this.Username}#{this.Discriminator} ({this.DisplayName})"; /// /// 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 DiscordMember); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordMember e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.GuildId == e.GuildId)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() { var hash = 13; hash = (hash * 7) + this.Id.GetHashCode(); hash = (hash * 7) + this.GuildId.GetHashCode(); return hash; } /// /// Gets whether the two objects are equal. /// /// First member to compare. /// Second member to compare. /// Whether the two members are equal. public static bool operator ==(DiscordMember e1, DiscordMember 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 && e1.GuildId == e2.GuildId)); } /// /// Gets whether the two objects are not equal. /// /// First member to compare. /// Second member to compare. /// Whether the two members are not equal. public static bool operator !=(DiscordMember e1, DiscordMember e2) => !(e1 == e2); }