diff --git a/.gitignore b/.gitignore index 44c3b430e..006a4a894 100644 --- a/.gitignore +++ b/.gitignore @@ -364,4 +364,7 @@ FodyWeavers.xsd # Settings file appsettings.json -!Documentation/**/appsettings.json \ No newline at end of file +!Documentation/**/appsettings.json + +# Intellij configuration files (eg. Jetbrains Rider) +.idea/ diff --git a/NetCord.Services/UserId.cs b/NetCord.Services/UserId.cs index 9f342d7ad..e855413e6 100644 --- a/NetCord.Services/UserId.cs +++ b/NetCord.Services/UserId.cs @@ -26,7 +26,7 @@ public UserId(ulong id, User? user) : this(id) User = user; } - public override string ToString() => $"<@{Id}>"; + public override string ToString() => Mention.User(Id); public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => Mention.TryFormatUser(destination, out charsWritten, Id); } diff --git a/NetCord/Channels/Channel.cs b/NetCord/Channels/Channel.cs index a2f0e00cf..8a83172c4 100644 --- a/NetCord/Channels/Channel.cs +++ b/NetCord/Channels/Channel.cs @@ -13,7 +13,7 @@ public abstract partial class Channel(JsonChannel jsonModel, RestClient client) Permissions IInteractionChannel.Permissions => _jsonModel.Permissions.GetValueOrDefault(); - public override string ToString() => $"<#{Id}>"; + public override string ToString() => Mention.Channel(Id); public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => Mention.TryFormatChannel(destination, out charsWritten, Id); diff --git a/NetCord/Mention.cs b/NetCord/Mention.cs index 17e0e2a1e..ff8e4ab57 100644 --- a/NetCord/Mention.cs +++ b/NetCord/Mention.cs @@ -4,71 +4,165 @@ namespace NetCord; public static class Mention { - public static bool TryParseUser(ReadOnlySpan mention, out ulong id) + public static string User(ulong userId) => $"<@{userId}>"; + + public static bool TryFormatUser(Span destination, out int charsWritten, ulong userId) + { + return TryFormat(destination, out charsWritten, userId, "<@", ">"); + } + + public static bool TryParseUser(ReadOnlySpan mention, out ulong userId) { if (mention is ['<', '@', _, .., '>']) { mention = mention[2] is '!' ? mention[3..^1] : mention[2..^1]; - if (Snowflake.TryParse(mention, out id)) + if (Snowflake.TryParse(mention, out userId)) return true; } else - id = default; + userId = default; return false; } - public static ulong ParseUser(ReadOnlySpan mention) + public static ulong ParseUser(ReadOnlySpan userMention) { - if (TryParseUser(mention, out ulong id)) + if (TryParseUser(userMention, out ulong id)) return id; else throw new FormatException("Cannot parse the mention."); } - public static bool TryParseChannel(ReadOnlySpan mention, out ulong id) + public static string Channel(ulong channelId) => $"<#{channelId}>"; + + public static bool TryFormatChannel(Span destination, out int charsWritten, ulong channelId) + { + return TryFormat(destination, out charsWritten, channelId, "<#", ">"); + } + + public static bool TryParseChannel(ReadOnlySpan mention, out ulong channelId) { if (mention is ['<', '#', .., '>']) { - if (Snowflake.TryParse(mention[2..^1], out id)) + if (Snowflake.TryParse(mention[2..^1], out channelId)) return true; } else - id = default; + channelId = default; return false; } - public static ulong ParseChannel(ReadOnlySpan mention) + public static ulong ParseChannel(ReadOnlySpan channelMention) { - if (TryParseChannel(mention, out ulong id)) + if (TryParseChannel(channelMention, out ulong id)) return id; else throw new FormatException("Cannot parse the mention."); } - public static bool TryParseRole(ReadOnlySpan mention, out ulong id) + public static string Role(ulong roleId) => $"<@&{roleId}>"; + + public static bool TryFormatRole(Span destination, out int charsWritten, ulong roleId) + { + return TryFormat(destination, out charsWritten, roleId, "<@&", ">"); + } + + public static bool TryParseRole(ReadOnlySpan mention, out ulong roleId) { if (mention is ['<', '@', '&', .., '>']) { - if (Snowflake.TryParse(mention[3..^1], out id)) + if (Snowflake.TryParse(mention[3..^1], out roleId)) return true; } else - id = default; + roleId = default; return false; } - public static ulong ParseRole(ReadOnlySpan mention) + public static ulong ParseRole(ReadOnlySpan roleMention) { - if (TryParseRole(mention, out ulong id)) + if (TryParseRole(roleMention, out ulong id)) return id; else throw new FormatException("Cannot parse the mention."); } + public static string ApplicationCommand(string name, ulong commandId) => ApplicationCommandCore(name, commandId); + + internal static string ApplicationCommandCore(string fullName, ulong commandId) => $""; + + public static string ApplicationCommand(string name, string subCommandName, ulong commandId) => $""; + + public static string ApplicationCommand(string name, string subCommandGroupName, string subCommandName, ulong commandId) => $""; + + public static bool TryFormatApplicationCommand(Span destination, out int charsWritten, ulong commandId, ReadOnlySpan name) + { + var pathLength = name.Length; + var idOffset = 3 + pathLength; + if (destination.Length < 5 + pathLength || !commandId.TryFormat(destination[idOffset..^1], out int length)) + { + charsWritten = 0; + return false; + } + + "'; + charsWritten = idOffset + length + 1; + return true; + } + + public static bool TryFormatApplicationCommand(Span destination, out int charsWritten, ulong commandId, ReadOnlySpan name, ReadOnlySpan subCommandName) + { + var nameLength = name.Length; + var subCommandNameLength = subCommandName.Length; + var pathLength = nameLength + subCommandNameLength + 1; + var idOffset = 3 + pathLength; + if (destination.Length < 5 + pathLength || !commandId.TryFormat(destination[idOffset..^1], out int length)) + { + charsWritten = 0; + return false; + } + + "'; + charsWritten = idOffset + length + 1; + return true; + } + + public static bool TryFormatApplicationCommand(Span destination, out int charsWritten, ulong commandId, ReadOnlySpan name, ReadOnlySpan subCommandGroupName, ReadOnlySpan subCommandName) + { + var nameLength = name.Length; + var subCommandGroupNameLength = subCommandGroupName.Length; + var subCommandNameLength = subCommandName.Length; + var pathLength = nameLength + subCommandGroupNameLength + subCommandNameLength + 2; + var idOffset = 3 + pathLength; + if (destination.Length < 5 + pathLength || !commandId.TryFormat(destination[idOffset..^1], out int length)) + { + charsWritten = 0; + return false; + } + + "'; + charsWritten = idOffset + length + 1; + return true; + } + public static bool TryParseSlashCommand(ReadOnlySpan mention, [MaybeNullWhen(false)] out SlashCommandMention result) { if (mention is ['<', '/', .., '>']) @@ -94,6 +188,7 @@ public static bool TryParseSlashCommand(ReadOnlySpan mention, [MaybeNullWh s[i] = names[..x].ToString(); names = names[(x + 1)..]; } + i++; } @@ -118,9 +213,9 @@ public static bool TryParseSlashCommand(ReadOnlySpan mention, [MaybeNullWh return false; } - public static SlashCommandMention ParseSlashCommand(ReadOnlySpan mention) + public static SlashCommandMention ParseSlashCommand(ReadOnlySpan slashCommandMention) { - if (TryParseSlashCommand(mention, out var result)) + if (TryParseSlashCommand(slashCommandMention, out var result)) return result; else throw new FormatException("Cannot parse the mention."); @@ -152,21 +247,6 @@ public static GuildNavigation ParseGuildNavigation(ReadOnlySpan mention) throw new FormatException("Cannot parse the mention."); } - public static bool TryFormatUser(Span destination, out int charsWritten, ulong id) - { - return TryFormat(destination, out charsWritten, id, "<@", ">"); - } - - public static bool TryFormatChannel(Span destination, out int charsWritten, ulong id) - { - return TryFormat(destination, out charsWritten, id, "<#", ">"); - } - - public static bool TryFormatRole(Span destination, out int charsWritten, ulong id) - { - return TryFormat(destination, out charsWritten, id, "<@&", ">"); - } - private static bool TryFormat(Span destination, out int charsWritten, ulong id, ReadOnlySpan prefix, ReadOnlySpan suffix) { if (destination.Length <= prefix.Length + suffix.Length || !id.TryFormat(destination[prefix.Length..^suffix.Length], out int length)) diff --git a/NetCord/Rest/ApplicationCommand.cs b/NetCord/Rest/ApplicationCommand.cs index 26940e66a..56e02fd90 100644 --- a/NetCord/Rest/ApplicationCommand.cs +++ b/NetCord/Rest/ApplicationCommand.cs @@ -67,23 +67,8 @@ public partial class ApplicationCommand(JsonModels.JsonApplicationCommand jsonMo /// public ulong Version => _jsonModel.Version; - public override string ToString() => $""; - - public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) - { - var requiredLength = 5 + Name.Length; - if (destination.Length < requiredLength || !Id.TryFormat(destination[(3 + Name.Length)..^1], out int length)) - { - charsWritten = 0; - return false; - } - - "'; - - charsWritten = 4 + Name.Length + length; - return true; - } + public override string ToString() => Mention.ApplicationCommand(Name, Id); + + public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => + Mention.TryFormatApplicationCommand(destination, out charsWritten, Id, Name); } diff --git a/NetCord/Rest/ApplicationCommandOption.cs b/NetCord/Rest/ApplicationCommandOption.cs index 21a7515a3..139b07a08 100644 --- a/NetCord/Rest/ApplicationCommandOption.cs +++ b/NetCord/Rest/ApplicationCommandOption.cs @@ -94,25 +94,10 @@ public ApplicationCommandOption(JsonModels.JsonApplicationCommandOption jsonMode Options = options.Select(o => new ApplicationCommandOption(o, _fullName, _parentId)).ToArray(); } - public override string ToString() => $""; + public override string ToString() => Mention.ApplicationCommandCore(_fullName, _parentId); public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); - public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) - { - var requiredLength = 5 + _fullName.Length; - if (destination.Length < requiredLength || !_parentId.TryFormat(destination[(3 + _fullName.Length)..^1], out int length)) - { - charsWritten = 0; - return false; - } - - "'; - - charsWritten = 4 + _fullName.Length + length; - return true; - } + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => + Mention.TryFormatApplicationCommand(destination, out charsWritten, _parentId, _fullName); } diff --git a/NetCord/Role.cs b/NetCord/Role.cs index 0107ac91b..7a23687f9 100644 --- a/NetCord/Role.cs +++ b/NetCord/Role.cs @@ -103,7 +103,7 @@ public Role(JsonRole jsonModel, ulong guildId, RestClient client) : base(client) /// An pointing to the role's icon. If the role does not have one set, returns . public ImageUrl? GetIconUrl(ImageFormat format) => IconHash is string hash ? ImageUrl.RoleIcon(Id, hash, format) : null; - public override string ToString() => $"<@&{Id}>"; + public override string ToString() => Mention.Role(Id); public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => Mention.TryFormatRole(destination, out charsWritten, Id); } diff --git a/NetCord/SlashCommandMention.cs b/NetCord/SlashCommandMention.cs index 1185d7bb1..fb167cbbe 100644 --- a/NetCord/SlashCommandMention.cs +++ b/NetCord/SlashCommandMention.cs @@ -27,77 +27,28 @@ public override string ToString() if (subCommandGroupName is null) { if (subCommandName is null) - return $""; + return Mention.ApplicationCommand(Name, Id); else - return $""; + return Mention.ApplicationCommand(Name, subCommandName, Id); } else - return $""; + return Mention.ApplicationCommand(Name, subCommandGroupName, subCommandName!, Id); } public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) { var subCommandGroupName = SubCommandGroupName; var subCommandName = SubCommandName; - var name = Name; if (subCommandGroupName is null) { if (subCommandName is null) - { - if (destination.Length < 5 + name.Length || !Id.TryFormat(destination[(3 + name.Length)..^1], out var length)) - { - charsWritten = 0; - return false; - } - - "'; - - charsWritten = 4 + name.Length + length; - return true; - } + return Mention.TryFormatApplicationCommand(destination, out charsWritten, Id, Name); else - { - if (destination.Length < 6 + name.Length + subCommandName.Length || !Id.TryFormat(destination[(4 + name.Length + subCommandName.Length)..^1], out var length)) - { - charsWritten = 0; - return false; - } - - "'; - - charsWritten = 5 + name.Length + subCommandName.Length + length; - return true; - } + return Mention.TryFormatApplicationCommand(destination, out charsWritten, Id, Name, subCommandName); } else - { - if (destination.Length < 7 + name.Length + subCommandGroupName.Length + subCommandName!.Length || !Id.TryFormat(destination[(5 + name.Length + subCommandGroupName.Length + subCommandName.Length)..^1], out var length)) - { - charsWritten = 0; - return false; - } - - "'; - - charsWritten = 6 + name.Length + subCommandGroupName.Length + subCommandName.Length + length; - return true; - } + return Mention.TryFormatApplicationCommand(destination, out charsWritten, Id, Name, subCommandGroupName, subCommandName!); } public static bool operator ==(SlashCommandMention? left, SlashCommandMention? right) => left is null ? right is null : Equals(left, right); diff --git a/NetCord/ThreadUser.cs b/NetCord/ThreadUser.cs index bc9867e1a..a7d86bbac 100644 --- a/NetCord/ThreadUser.cs +++ b/NetCord/ThreadUser.cs @@ -11,7 +11,7 @@ public class ThreadUser(JsonModels.JsonThreadUser jsonModel, RestClient client) public DateTimeOffset JoinTimestamp => jsonModel.JoinTimestamp; public int Flags => jsonModel.Flags; - public override string ToString() => $"<@{Id}>"; + public override string ToString() => Mention.User(Id); public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => Mention.TryFormatUser(destination, out charsWritten, Id); } diff --git a/NetCord/User.cs b/NetCord/User.cs index d9223c426..fd0342855 100644 --- a/NetCord/User.cs +++ b/NetCord/User.cs @@ -256,7 +256,7 @@ public User(JsonModels.JsonUser jsonModel, RestClient client) : base(client) /// /// Converts the ID of this user into its string representation, using Discord's mention syntax (<@803169206115237908>). /// - public override string ToString() => $"<@{Id}>"; + public override string ToString() => Mention.User(Id); public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => Mention.TryFormatUser(destination, out charsWritten, Id); } diff --git a/Tests/MentionTest/TryFormat.cs b/Tests/MentionTest/TryFormat.cs index 8bec51b45..cd379f59b 100644 --- a/Tests/MentionTest/TryFormat.cs +++ b/Tests/MentionTest/TryFormat.cs @@ -99,6 +99,23 @@ public void SlashCommand() static bool TryFormat(Span destination, out int charsWritten, SlashCommandMention value) => value.TryFormat(destination, out charsWritten); } + [TestMethod] + public void ApplicationCommandMentionSegments() + { + TestTryFormat(TryFormat, _slashCommandMentions, m => m.ToString()); + + static bool TryFormat(Span destination, out int charsWritten, SlashCommandMention value) + { + if (value.SubCommandGroupName is { } subCommandGroupName) + return Mention.TryFormatApplicationCommand(destination, out charsWritten, value.Id, value.Name, subCommandGroupName, value.SubCommandName!); + + if (value.SubCommandName is { } subCommandName) + return Mention.TryFormatApplicationCommand(destination, out charsWritten, value.Id, value.Name, subCommandName); + + return Mention.TryFormatApplicationCommand(destination, out charsWritten, value.Id, value.Name); + } + } + [TestMethod] public void Timestamp() {