namespace RegexBot.Common; /// /// Helper class that holds an entity's name, ID, or both. /// Meant to be used during configuration processing in cases where the configuration expects /// an entity name to be defined in a certain way which may or may not include its snowflake ID. /// public class EntityName { /// /// The entity's type, as specified in configuration. /// public EntityType Type { get; private set; } private ulong? _id; /// /// Entity's unique ID value (snowflake). May be null if the value is not known. /// /// /// This property may be updated during runtime if instructed to update the ID for persistence. /// public ulong? Id { get => _id; internal set => _id ??= value; } /// /// Entity's name as specified in configuration. May be null if it was not specified. /// /// This value is not updated during runtime. public string? Name { get; private set; } /// /// Creates a new object instance from the given input string. /// Documentation for the EntityName format can be found elsewhere in this project's documentation. /// /// Input string in EntityName format. /// Input string is null or blank. /// Input string cannot be resolved to an entity type. public EntityName(string input) { if (string.IsNullOrWhiteSpace(input)) throw new ArgumentNullException(nameof(input), "Specified name is blank."); if (input.Length < 2) throw new ArgumentException("Input is not in a valid entity name format."); if (input[0] == '&') Type = EntityType.Role; else if (input[0] == '#') Type = EntityType.Channel; else if (input[0] == '@') Type = EntityType.User; else throw new ArgumentException("Entity type unable to be inferred by given input."); input = input[1..]; // Remove prefix // Input contains ID/Label separator? var separator = input.IndexOf("::"); if (separator != -1) { Name = input[(separator + 2)..]; if (ulong.TryParse(input.AsSpan(0, separator), out var parseOut)) { // Got an ID. Id = parseOut; } else { // It's not actually an ID. Assuming the entire string is a name. Name = input; Id = null; } } else { // No separator. Input is either entirely an ID or entirely a Name. if (ulong.TryParse(input, out var parseOut)) { // ID without name. Id = parseOut; Name = null; } else { // Name without ID. Name = input; Id = null; } } } /// /// Creates a new object instance from the given input string. /// Documentation for the EntityName format can be found elsewhere in this project's documentation. /// /// Input string in EntityName format. /// The expected for this instance. /// Input string is null or blank. /// Input string cannot be resolved to an entity type. /// Input string was resolved to a type other than specified. public EntityName(string input, EntityType expectedType) : this(input) { if (Type != expectedType) throw new FormatException("Resolved EntityType does not match expected type."); } /// /// Returns the appropriate prefix corresponding to an EntityType. /// public static char GetPrefix(EntityType t) => t switch { EntityType.Role => '&', EntityType.Channel => '#', EntityType.User => '@', _ => '\0', }; /// /// Returns a string representation of this item in proper EntityName format. /// public override string ToString() { var pf = GetPrefix(Type); if (Id.HasValue && Name != null) return $"{pf}{Id.Value}::{Name}"; else if (Id.HasValue) return $"{pf}{Id}"; else return $"{pf}{Name}"; } #region Helper methods // TODO convert all to extension methods /// /// Attempts to find the corresponding role within the given guild. /// /// The guild in which to search for the role. /// /// Specifies if this EntityName instance should cache the snowflake ID of the /// corresponding role found in this guild if it is not already known by this instance. /// public SocketRole? FindRoleIn(SocketGuild guild, bool updateMissingID = false) { if (Type != EntityType.Role) throw new ArgumentException("This EntityName instance must correspond to a Role."); var dirty = false; // flag for updating ID if possible regardless of updateMissingId setting if (Id.HasValue) { var role = guild.GetRole(Id.Value); if (role != null) return role; else dirty = true; // only set if ID already existed but is now invalid } var r = guild.Roles.FirstOrDefault(rq => string.Equals(rq.Name, Name, StringComparison.OrdinalIgnoreCase)); if (r != null && (updateMissingID || dirty)) Id = r.Id; return r; } /// /// Attempts to find the corresponding user within the given guild. /// /// The guild in which to search for the user. /// /// Specifies if this EntityName instance should cache the snowflake ID of the /// corresponding user found in this guild if it is not already known by this instance. /// public SocketGuildUser? FindUserIn(SocketGuild guild, bool updateMissingID = false) { if (Type != EntityType.User) throw new ArgumentException("This EntityName instance must correspond to a User."); var dirty = false; // flag for updating ID if possible regardless of updateMissingId setting if (Id.HasValue) { var user = guild.GetUser(Id.Value); if (user != null) return user; else dirty = true; // only set if ID already existed but is now invalid } var u = guild.Users.FirstOrDefault(rq => string.Equals(rq.Username, Name, StringComparison.OrdinalIgnoreCase)); if (u != null && (updateMissingID || dirty)) Id = u.Id; return u; } /// /// Attempts to find the corresponding channel within the given guild. /// /// The guild in which to search for the channel. /// /// Specifies if this EntityName instance should cache the snowflake ID of the /// corresponding channel found in this guild if it is not already known by this instance. /// public SocketTextChannel? FindChannelIn(SocketGuild guild, bool updateMissingID = false) { if (Type != EntityType.Channel) throw new ArgumentException("This EntityName instance must correspond to a Channel."); var dirty = false; // flag for updating ID if possible regardless of updateMissingId setting if (Id.HasValue) { var channel = guild.GetTextChannel(Id.Value); if (channel != null) return channel; else dirty = true; // only set if ID already existed but is now invalid } var c = guild.TextChannels.FirstOrDefault(rq => string.Equals(rq.Name, Name, StringComparison.OrdinalIgnoreCase)); if (c != null && (updateMissingID || dirty)) Id = c.Id; return c; } #endregion }