diff --git a/DisCatSharp/Entities/Emoji/DiscordEmoji.cs b/DisCatSharp/Entities/Emoji/DiscordEmoji.cs
index 37ab73e53..e0263773c 100644
--- a/DisCatSharp/Entities/Emoji/DiscordEmoji.cs
+++ b/DisCatSharp/Entities/Emoji/DiscordEmoji.cs
@@ -1,375 +1,375 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using DisCatSharp.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
///
/// Represents a Discord emoji.
///
public partial class DiscordEmoji : SnowflakeObject, IEquatable
{
///
/// Gets the name of this emoji.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
///
/// Gets IDs the roles this emoji is enabled for.
///
[JsonIgnore]
public IReadOnlyList Roles => this._rolesLazy.Value;
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public List RolesInternal;
private readonly Lazy> _rolesLazy;
///
/// Gets whether this emoji requires colons to use.
///
[JsonProperty("require_colons")]
public bool RequiresColons { get; internal set; }
///
/// Gets whether this emoji is managed by an integration.
///
[JsonProperty("managed")]
public bool IsManaged { get; internal set; }
///
/// Gets whether this emoji is animated.
///
[JsonProperty("animated")]
public bool IsAnimated { get; internal set; }
///
/// Gets whether the emoji is available for use.
/// An emoji may not be available due to loss of server boost.
///
[JsonProperty("available", NullValueHandling = NullValueHandling.Ignore)]
public bool IsAvailable { get; internal set; }
///
/// Gets the image URL of this emoji.
///
[JsonIgnore]
public string Url =>
this.Id == 0
? throw new InvalidOperationException("Cannot get URL of unicode emojis.")
: this.IsAnimated
? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMOJIS}/{this.Id.ToString(CultureInfo.InvariantCulture)}.gif"
: $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMOJIS}/{this.Id.ToString(CultureInfo.InvariantCulture)}.png";
///
/// Initializes a new instance of the class.
///
internal DiscordEmoji()
{
this._rolesLazy = new Lazy>(() => new ReadOnlyCollection(this.RolesInternal));
}
///
/// Gets emoji's name in non-Unicode format (eg. :thinking: instead of the Unicode representation of the emoji).
///
public string GetDiscordName()
{
s_discordNameLookup.TryGetValue(this.Name, out var name);
return name ?? $":{this.Name}:";
}
///
/// Returns a string representation of this emoji.
///
/// String representation of this emoji.
public override string ToString()
=> this.Id != 0
? this.IsAnimated
? $""
: $"<:{this.Name}:{this.Id.ToString(CultureInfo.InvariantCulture)}>"
: this.Name;
///
/// 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 DiscordEmoji);
///
/// Checks whether this is equal to another .
///
/// to compare to.
/// Whether the is equal to this .
public bool Equals(DiscordEmoji e)
=> e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.Name == e.Name));
///
/// 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.Name.GetHashCode();
return hash;
}
///
/// Gets the reactions string.
///
internal string ToReactionString()
=> this.Id != 0 ? $"{this.Name}:{this.Id.ToString(CultureInfo.InvariantCulture)}" : this.Name;
///
/// Gets whether the two objects are equal.
///
/// First emoji to compare.
/// Second emoji to compare.
/// Whether the two emoji are equal.
public static bool operator ==(DiscordEmoji e1, DiscordEmoji 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);
+ return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1.Name == e2.Name));
}
///
/// Gets whether the two objects are not equal.
///
/// First emoji to compare.
/// Second emoji to compare.
/// Whether the two emoji are not equal.
public static bool operator !=(DiscordEmoji e1, DiscordEmoji e2)
=> !(e1 == e2);
///
/// Implicitly converts this emoji to its string representation.
///
/// Emoji to convert.
public static implicit operator string(DiscordEmoji e1)
=> e1.ToString();
///
/// Checks whether specified unicode entity is a valid unicode emoji.
///
/// Entity to check.
/// Whether it's a valid emoji.
public static bool IsValidUnicode(string unicodeEntity)
=> s_discordNameLookup.ContainsKey(unicodeEntity);
///
/// Creates an emoji object from a unicode entity.
///
/// to attach to the object.
/// Unicode entity to create the object from.
/// Create object.
public static DiscordEmoji FromUnicode(BaseDiscordClient client, string unicodeEntity)
=> !IsValidUnicode(unicodeEntity)
? throw new ArgumentException("Specified unicode entity is not a valid unicode emoji.", nameof(unicodeEntity))
: new DiscordEmoji { Name = unicodeEntity, Discord = client };
///
/// Creates an emoji object from a unicode entity.
///
/// Unicode entity to create the object from.
/// Create object.
public static DiscordEmoji FromUnicode(string unicodeEntity)
=> FromUnicode(null, unicodeEntity);
///
/// Attempts to create an emoji object from a unicode entity.
///
/// to attach to the object.
/// Unicode entity to create the object from.
/// Resulting object.
/// Whether the operation was successful.
public static bool TryFromUnicode(BaseDiscordClient client, string unicodeEntity, out DiscordEmoji emoji)
{
// this is a round-trip operation because of FE0F inconsistencies.
// through this, the inconsistency is normalized.
emoji = null;
if (!s_discordNameLookup.TryGetValue(unicodeEntity, out var discordName))
return false;
if (!s_unicodeEmojis.TryGetValue(discordName, out unicodeEntity))
return false;
emoji = new DiscordEmoji { Name = unicodeEntity, Discord = client };
return true;
}
///
/// Attempts to create an emoji object from a unicode entity.
///
/// Unicode entity to create the object from.
/// Resulting object.
/// Whether the operation was successful.
public static bool TryFromUnicode(string unicodeEntity, out DiscordEmoji emoji)
=> TryFromUnicode(null, unicodeEntity, out emoji);
///
/// Creates an emoji object from a guild emote.
///
/// to attach to the object.
/// Id of the emote.
/// Create object.
public static DiscordEmoji FromGuildEmote(BaseDiscordClient client, ulong id)
{
if (client == null)
throw new ArgumentNullException(nameof(client), "Client cannot be null.");
foreach (var guild in client.Guilds.Values)
{
if (guild.Emojis.TryGetValue(id, out var found))
return found;
}
throw new KeyNotFoundException("Given emote was not found.");
}
///
/// Attempts to create an emoji object from a guild emote.
///
/// to attach to the object.
/// Id of the emote.
/// Resulting object.
/// Whether the operation was successful.
public static bool TryFromGuildEmote(BaseDiscordClient client, ulong id, out DiscordEmoji emoji)
{
if (client == null)
throw new ArgumentNullException(nameof(client), "Client cannot be null.");
foreach (var guild in client.Guilds.Values)
{
if (guild.Emojis.TryGetValue(id, out emoji))
return true;
}
emoji = null;
return false;
}
///
/// Creates an emoji object from emote name that includes colons (eg. :thinking:). This method also supports
/// skin tone variations (eg. :ok_hand::skin-tone-2:), standard emoticons (eg. :D), as well as guild emoji
/// (still specified by :name:).
///
/// to attach to the object.
/// Name of the emote to find, including colons (eg. :thinking:).
/// Should guild emojis be included in the search.
/// Create object.
public static DiscordEmoji FromName(BaseDiscordClient client, string name, bool includeGuilds = true)
{
if (client == null)
throw new ArgumentNullException(nameof(client), "Client cannot be null.");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name), "Name cannot be empty or null.");
if (s_unicodeEmojis.TryGetValue(name, out var unicodeEntity))
return new DiscordEmoji { Discord = client, Name = unicodeEntity };
if (includeGuilds)
{
var allEmojis = client.Guilds.Values
.SelectMany(xg => xg.Emojis.Values); // save cycles - don't order
var ek = name.AsSpan().Slice(1, name.Length - 2);
foreach (var emoji in allEmojis)
if (emoji.Name.AsSpan().SequenceEqual(ek))
return emoji;
}
throw new ArgumentException("Invalid emoji name specified.", nameof(name));
}
///
/// Attempts to create an emoji object from emote name that includes colons (eg. :thinking:). This method also
/// supports skin tone variations (eg. :ok_hand::skin-tone-2:), standard emoticons (eg. :D), as well as guild
/// emoji (still specified by :name:).
///
/// to attach to the object.
/// Name of the emote to find, including colons (eg. :thinking:).
/// Resulting object.
/// Whether the operation was successful.
public static bool TryFromName(BaseDiscordClient client, string name, out DiscordEmoji emoji)
=> TryFromName(client, name, true, out emoji);
///
/// Attempts to create an emoji object from emote name that includes colons (eg. :thinking:). This method also
/// supports skin tone variations (eg. :ok_hand::skin-tone-2:), standard emoticons (eg. :D), as well as guild
/// emoji (still specified by :name:).
///
/// to attach to the object.
/// Name of the emote to find, including colons (eg. :thinking:).
/// Should guild emojis be included in the search.
/// Resulting object.
/// Whether the operation was successful.
public static bool TryFromName(BaseDiscordClient client, string name, bool includeGuilds, out DiscordEmoji emoji)
{
if (client == null)
throw new ArgumentNullException(nameof(client), "Client cannot be null.");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name), "Name cannot be empty or null.");
if (s_unicodeEmojis.TryGetValue(name, out var unicodeEntity))
{
emoji = new DiscordEmoji { Discord = client, Name = unicodeEntity };
return true;
}
if (includeGuilds)
{
var allEmojis = client.Guilds.Values
.SelectMany(xg => xg.Emojis.Values); // save cycles - don't order
var ek = name.AsSpan().Slice(1, name.Length - 2);
foreach (var xemoji in allEmojis)
if (xemoji.Name.AsSpan().SequenceEqual(ek))
{
emoji = xemoji;
return true;
}
}
emoji = null;
return false;
}
}