Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,7 @@ FodyWeavers.xsd

# Settings file
appsettings.json
!Documentation/**/appsettings.json
!Documentation/**/appsettings.json

# Intellij configuration files (eg. Jetbrains Rider)
.idea/
2 changes: 1 addition & 1 deletion NetCord.Services/UserId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null) => Mention.TryFormatUser(destination, out charsWritten, Id);
}
2 changes: 1 addition & 1 deletion NetCord/Channels/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null) => Mention.TryFormatChannel(destination, out charsWritten, Id);

Expand Down
110 changes: 95 additions & 15 deletions NetCord/Mention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ namespace NetCord;

public static class Mention
{
public static string User(ulong userId) => $"<@{userId}>";

public static bool TryFormatUser(Span<char> destination, out int charsWritten, ulong id)
{
return TryFormat(destination, out charsWritten, id, "<@", ">");
}

public static bool TryParseUser(ReadOnlySpan<char> mention, out ulong id)
{
if (mention is ['<', '@', _, .., '>'])
Expand All @@ -27,6 +34,13 @@ public static ulong ParseUser(ReadOnlySpan<char> mention)
throw new FormatException("Cannot parse the mention.");
}

public static string Channel(ulong channelId) => $"<#{channelId}>";

public static bool TryFormatChannel(Span<char> destination, out int charsWritten, ulong id)
{
return TryFormat(destination, out charsWritten, id, "<#", ">");
}

public static bool TryParseChannel(ReadOnlySpan<char> mention, out ulong id)
{
if (mention is ['<', '#', .., '>'])
Expand All @@ -48,6 +62,13 @@ public static ulong ParseChannel(ReadOnlySpan<char> mention)
throw new FormatException("Cannot parse the mention.");
}

public static string Role(ulong roleId) => $"<@&{roleId}>";

public static bool TryFormatRole(Span<char> destination, out int charsWritten, ulong id)
{
return TryFormat(destination, out charsWritten, id, "<@&", ">");
}

public static bool TryParseRole(ReadOnlySpan<char> mention, out ulong id)
{
if (mention is ['<', '@', '&', .., '>'])
Expand All @@ -69,6 +90,79 @@ public static ulong ParseRole(ReadOnlySpan<char> mention)
throw new FormatException("Cannot parse the mention.");
}

public static string ApplicationCommand(string name, ulong id) => ApplicationCommandCore(name, id);

internal static string ApplicationCommandCore(string fullName, ulong id) => $"</{fullName}:{id}>";

public static string ApplicationCommand(string name, string subCommandName, ulong id) => $"</{name} {subCommandName}:{id}>";

public static string ApplicationCommand(string name, string subCommandGroupName, string subCommandName, ulong id) => $"</{name} {subCommandGroupName} {subCommandName}:{id}>";

public static bool TryFormatApplicationCommand(Span<char> destination, out int charsWritten, ulong id, ReadOnlySpan<char> name)
{
var pathLength = name.Length;
var idOffset = 3 + pathLength;
if (destination.Length < 5 + pathLength || !id.TryFormat(destination[idOffset..^1], out int length))
{
charsWritten = 0;
return false;
}

"</".CopyTo(destination);
name.CopyTo(destination[2..]);
destination[2 + pathLength] = ':';
destination[idOffset + length] = '>';
charsWritten = idOffset + length + 1;
return true;
}

public static bool TryFormatApplicationCommand(Span<char> destination, out int charsWritten, ulong id, ReadOnlySpan<char> name, ReadOnlySpan<char> subCommandName)
{
var nameLength = name.Length;
var subCommandNameLength = subCommandName.Length;
var pathLength = nameLength + subCommandNameLength + 1;
var idOffset = 3 + pathLength;
if (destination.Length < 5 + pathLength || !id.TryFormat(destination[idOffset..^1], out int length))
{
charsWritten = 0;
return false;
}

"</".CopyTo(destination);
name.CopyTo(destination[2..]);
destination[2 + nameLength] = ' ';
subCommandName.CopyTo(destination[(3 + nameLength)..]);
destination[3 + nameLength + subCommandNameLength] = ':';
destination[idOffset + length] = '>';
charsWritten = idOffset + length + 1;
return true;
}

public static bool TryFormatApplicationCommand(Span<char> destination, out int charsWritten, ulong id, ReadOnlySpan<char> name, ReadOnlySpan<char> subCommandGroupName, ReadOnlySpan<char> 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 || !id.TryFormat(destination[idOffset..^1], out int length))
{
charsWritten = 0;
return false;
}

"</".CopyTo(destination);
name.CopyTo(destination[2..]);
destination[2 + nameLength] = ' ';
subCommandGroupName.CopyTo(destination[(3 + nameLength)..]);
destination[3 + nameLength + subCommandGroupNameLength] = ' ';
subCommandName.CopyTo(destination[(4 + nameLength + subCommandGroupNameLength)..]);
destination[4 + nameLength + subCommandGroupNameLength + subCommandNameLength] = ':';
destination[idOffset + length] = '>';
charsWritten = idOffset + length + 1;
return true;
}

public static bool TryParseSlashCommand(ReadOnlySpan<char> mention, [MaybeNullWhen(false)] out SlashCommandMention result)
{
if (mention is ['<', '/', .., '>'])
Expand All @@ -94,6 +188,7 @@ public static bool TryParseSlashCommand(ReadOnlySpan<char> mention, [MaybeNullWh
s[i] = names[..x].ToString();
names = names[(x + 1)..];
}

i++;
}

Expand Down Expand Up @@ -152,21 +247,6 @@ public static GuildNavigation ParseGuildNavigation(ReadOnlySpan<char> mention)
throw new FormatException("Cannot parse the mention.");
}

public static bool TryFormatUser(Span<char> destination, out int charsWritten, ulong id)
{
return TryFormat(destination, out charsWritten, id, "<@", ">");
}

public static bool TryFormatChannel(Span<char> destination, out int charsWritten, ulong id)
{
return TryFormat(destination, out charsWritten, id, "<#", ">");
}

public static bool TryFormatRole(Span<char> destination, out int charsWritten, ulong id)
{
return TryFormat(destination, out charsWritten, id, "<@&", ">");
}

private static bool TryFormat(Span<char> destination, out int charsWritten, ulong id, ReadOnlySpan<char> prefix, ReadOnlySpan<char> suffix)
{
if (destination.Length <= prefix.Length + suffix.Length || !id.TryFormat(destination[prefix.Length..^suffix.Length], out int length))
Expand Down
23 changes: 4 additions & 19 deletions NetCord/Rest/ApplicationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,8 @@ public partial class ApplicationCommand(JsonModels.JsonApplicationCommand jsonMo
/// </summary>
public ulong Version => _jsonModel.Version;

public override string ToString() => $"</{Name}:{Id}>";

public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> 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;
}

"</".CopyTo(destination);
Name.CopyTo(destination[2..]);
destination[2 + Name.Length] = ':';
destination[3 + Name.Length + length] = '>';

charsWritten = 4 + Name.Length + length;
return true;
}
public override string ToString() => Mention.ApplicationCommand(Name, Id);

public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null) =>
Mention.TryFormatApplicationCommand(destination, out charsWritten, Id, Name);
}
21 changes: 3 additions & 18 deletions NetCord/Rest/ApplicationCommandOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,10 @@ public ApplicationCommandOption(JsonModels.JsonApplicationCommandOption jsonMode
Options = options.Select(o => new ApplicationCommandOption(o, _fullName, _parentId)).ToArray();
}

public override string ToString() => $"</{_fullName}:{_parentId}>";
public override string ToString() => Mention.ApplicationCommandCore(_fullName, _parentId);

public string ToString(string? format, IFormatProvider? formatProvider) => ToString();

public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> 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;
}

"</".CopyTo(destination);
_fullName.CopyTo(destination[2..]);
destination[2 + _fullName.Length] = ':';
destination[3 + _fullName.Length + length] = '>';

charsWritten = 4 + _fullName.Length + length;
return true;
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null) =>
Mention.TryFormatApplicationCommand(destination, out charsWritten, _parentId, _fullName);
}
2 changes: 1 addition & 1 deletion NetCord/Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public Role(JsonRole jsonModel, ulong guildId, RestClient client) : base(client)
/// <returns>An <see cref="ImageUrl"/> pointing to the role's icon. If the role does not have one set, returns <see langword="null"/>.</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<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null) => Mention.TryFormatRole(destination, out charsWritten, Id);
}
Expand Down
61 changes: 6 additions & 55 deletions NetCord/SlashCommandMention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,77 +27,28 @@ public override string ToString()
if (subCommandGroupName is null)
{
if (subCommandName is null)
return $"</{Name}:{Id}>";
return Mention.ApplicationCommand(Name, Id);
else
return $"</{Name} {subCommandName}:{Id}>";
return Mention.ApplicationCommand(Name, subCommandName, Id);
}
else
return $"</{Name} {subCommandGroupName} {subCommandName}:{Id}>";
return Mention.ApplicationCommand(Name, subCommandGroupName, subCommandName!, Id);
}

public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> 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;
}

"</".CopyTo(destination);
name.CopyTo(destination[2..]);
destination[2 + name.Length] = ':';
destination[3 + name.Length + length] = '>';

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

"</".CopyTo(destination);
name.CopyTo(destination[2..]);
destination[2 + name.Length] = ' ';
subCommandName.CopyTo(destination[(3 + name.Length)..]);
destination[3 + name.Length + subCommandName.Length] = ':';
destination[4 + name.Length + subCommandName.Length + length] = '>';

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

"</".CopyTo(destination);
name.CopyTo(destination[2..]);
destination[2 + name.Length] = ' ';
subCommandGroupName.CopyTo(destination[(3 + name.Length)..]);
destination[3 + name.Length + subCommandGroupName.Length] = ' ';
subCommandName.CopyTo(destination[(4 + name.Length + subCommandGroupName.Length)..]);
destination[4 + name.Length + subCommandGroupName.Length + subCommandName.Length] = ':';
destination[5 + name.Length + subCommandGroupName.Length + subCommandName.Length + length] = '>';

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);
Expand Down
2 changes: 1 addition & 1 deletion NetCord/ThreadUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null) => Mention.TryFormatUser(destination, out charsWritten, Id);
}
2 changes: 1 addition & 1 deletion NetCord/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public User(JsonModels.JsonUser jsonModel, RestClient client) : base(client)
/// <summary>
/// Converts the ID of this user into its string representation, using Discord's mention syntax (<c>&lt;@803169206115237908&gt;</c>).
/// </summary>
public override string ToString() => $"<@{Id}>";
public override string ToString() => Mention.User(Id);

public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null) => Mention.TryFormatUser(destination, out charsWritten, Id);
}
17 changes: 17 additions & 0 deletions Tests/MentionTest/TryFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ public void SlashCommand()
static bool TryFormat(Span<char> 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<char> 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()
{
Expand Down