diff --git a/README.md b/README.md
index c3b75df..8d92434 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Version 4.0.1 contains breaking changes.
- Removed obsolete methods.
- Methods are now asynchronous. (The return value changed from `void` to `Task` and gains `Async` suffix)
- Events are now asynchronous (return value changed from `void` to `Task`)
-- `Add/RemoveChatCommandIdentifier` methods were removed, use `ChatCommandIdentifiers` property instead (same applies to whisper);
+- `Add/RemoveChatCommandIdentifier` methods were removed, use `ChatCommandIdentifiers` property instead (same applies to whisper) and uses `string` instead of `char`
- `OnLog` event was removed (you can still use `ILoggerFactory` to get logs)
- removed builders classes (removed `TwitchLib.Client.Models.Builders namespace`)
- changed public fields to properties
diff --git a/TwitchLib.Client.Example/Program.cs b/TwitchLib.Client.Example/Program.cs
index 435aa81..3ad0153 100644
--- a/TwitchLib.Client.Example/Program.cs
+++ b/TwitchLib.Client.Example/Program.cs
@@ -11,7 +11,7 @@
var credentials = new ConnectionCredentials(); // anonymous user, add Username and OAuth token to get the ability to send messages
var client = new TwitchClient(loggerFactory: loggerFactory)
{
- ChatCommandIdentifiers = { '!', '?' }, // you can customize the command identifiers, if not set, defaults to '!'
+ ChatCommandIdentifiers = { "!", "?" }, // you can customize the command identifiers, if not set, defaults to '!'
};
client.Initialize(credentials);
diff --git a/TwitchLib.Client.Models/CommandInfo.cs b/TwitchLib.Client.Models/CommandInfo.cs
index d3f55a5..889664f 100644
--- a/TwitchLib.Client.Models/CommandInfo.cs
+++ b/TwitchLib.Client.Models/CommandInfo.cs
@@ -6,7 +6,7 @@ namespace TwitchLib.Client.Models;
public class CommandInfo
{
/// Property representing the command identifier (ie command prefix).
- public char Identifier { get; }
+ public string Identifier { get; }
/// Property representing the actual command (without the command prefix).
public string Name { get; }
@@ -20,13 +20,13 @@ public class CommandInfo
///
/// Initializes a new instance of the class.
///
- public CommandInfo(char identifier, string name) : this(identifier, name, string.Empty, new())
+ public CommandInfo(string identifier, string name) : this(identifier, name, string.Empty, new())
{ }
///
/// Initializes a new instance of the class.
///
- public CommandInfo(char identifier, string name, string argumentsAsString, List argumentsAsList)
+ public CommandInfo(string identifier, string name, string argumentsAsString, List argumentsAsList)
{
Identifier = identifier;
Name = name;
@@ -35,37 +35,34 @@ public CommandInfo(char identifier, string name, string argumentsAsString, List<
}
///
- /// Tries to parse a span of characters into a value.
+ /// Tries to parse a message with specified command identifier into a value.
///
- /// The span of characters to parse.
- /// When this method returns, contains the result of successfully parsing s, or an undefined value on failure.
/// true if s was successfully parsed; otherwise, false.
#if NETSTANDARD2_0
- public static bool TryParse(ReadOnlySpan s, out CommandInfo result)
+ internal static bool TryParse(string commandIdentifier, ReadOnlySpan message, out CommandInfo result)
#else
- public static bool TryParse(ReadOnlySpan s, [MaybeNullWhen(false)] out CommandInfo result)
+ internal static bool TryParse(string commandIdentifier, ReadOnlySpan message, [MaybeNullWhen(false)] out CommandInfo result)
#endif
{
result = default!;
- s = s.Trim();
- if (s.IsEmpty)
+ if(!message.StartsWith(commandIdentifier.AsSpan()))
return false;
- var commandIdentifier = s[0];
- s = s.Slice(1);
- if (s.IsEmpty || s[0] == ' ') // if string contains only the identifier or the first char after identifier is space, then it is invalid input
+
+ message = message.Slice(commandIdentifier.Length);
+ if (message.IsEmpty || message[0] == ' ') // if string contains only the identifier or the first char after identifier is space, then it is invalid input
return false;
- var indexOfSpace = s.IndexOf(' ');
+ var indexOfSpace = message.IndexOf(' ');
if (indexOfSpace == -1)
{
- var name = s.ToString();
+ var name = message.ToString();
result = new(commandIdentifier, name);
}
else
{
- var name = s.Slice(0, indexOfSpace).ToString();
- s = s.Slice(indexOfSpace + 1).TrimStart();
- var argumentsAsString = s.ToString();
- result = new(commandIdentifier, name, argumentsAsString, ParseArgumentsToList(s));
+ var name = message.Slice(0, indexOfSpace).ToString();
+ message = message.Slice(indexOfSpace + 1).TrimStart();
+ var argumentsAsString = message.ToString();
+ result = new(commandIdentifier, name, argumentsAsString, ParseArgumentsToList(message));
}
return true;
diff --git a/TwitchLib.Client.Test/CommandInfoTest.cs b/TwitchLib.Client.Test/CommandInfoTest.cs
index c8fc735..d7571f6 100644
--- a/TwitchLib.Client.Test/CommandInfoTest.cs
+++ b/TwitchLib.Client.Test/CommandInfoTest.cs
@@ -6,12 +6,30 @@ namespace TwitchLib.Client.Test;
public class CommandInfoTest
{
[Theory]
- [InlineData("")]
- [InlineData("!")]
- [InlineData("! command")]
- public void ParsingFailAndReturnNull(string s)
+ [InlineData("", "")]
+ [InlineData("!", "!")]
+ [InlineData("!", "! command")]
+ [InlineData("?", "!command")]
+ public void ParsingFailAndReturnNull(string commandIdentifier, string message)
{
- Assert.False(CommandInfo.TryParse(s, out var commandInfo));
+ Assert.False(CommandInfo.TryParse(commandIdentifier, message, out var commandInfo));
Assert.Null(commandInfo);
}
+
+ [Theory]
+ [InlineData("!", "!command", 0)]
+ [InlineData("!", "!command arg1", 1)]
+ [InlineData("!", "!command arg1 arg2", 2)]
+ [InlineData("!", "!command arg1 arg2 arg3 arg4", 4)]
+ [InlineData("!", "!command \"arg1 with space\"", 1)]
+ [InlineData("!", "!command \"arg1 with space\" \"arg2 with space\"", 2)]
+ [InlineData("cmd!", "cmd!command", 0)]
+ [InlineData("cmd!", "cmd!command arg1", 1)]
+ [InlineData("cmd!", "cmd!command \"arg1 with space\" \"arg2 with space\"", 2)]
+ public void Parsing(string commandIdentifier, string message, int argCount)
+ {
+ Assert.True(CommandInfo.TryParse(commandIdentifier, message, out var commandInfo));
+ Assert.NotNull(commandInfo);
+ Assert.Equal(argCount, commandInfo.ArgumentsAsList.Count);
+ }
}
diff --git a/TwitchLib.Client/Interfaces/ITwitchClient.cs b/TwitchLib.Client/Interfaces/ITwitchClient.cs
index 67b049c..b9ee319 100644
--- a/TwitchLib.Client/Interfaces/ITwitchClient.cs
+++ b/TwitchLib.Client/Interfaces/ITwitchClient.cs
@@ -57,11 +57,11 @@ public interface ITwitchClient
///
/// The chat command identifiers
///
- ICollection ChatCommandIdentifiers { get; }
+ ICollection ChatCommandIdentifiers { get; }
///
/// The whisper command identifiers
///
- ICollection WhisperCommandIdentifiers { get; }
+ ICollection WhisperCommandIdentifiers { get; }
///
/// Fires when an Announcement is received
diff --git a/TwitchLib.Client/TwitchClient.cs b/TwitchLib.Client/TwitchClient.cs
index 993fcac..b8f3cc2 100644
--- a/TwitchLib.Client/TwitchClient.cs
+++ b/TwitchLib.Client/TwitchClient.cs
@@ -89,10 +89,10 @@ public class TwitchClient : ITwitchClient
#region Public Variables
///
- public ICollection ChatCommandIdentifiers { get; } = new HashSet();
+ public ICollection ChatCommandIdentifiers { get; } = new HashSet();
///
- public ICollection WhisperCommandIdentifiers { get; } = new HashSet();
+ public ICollection WhisperCommandIdentifiers { get; } = new HashSet();
///
#if NET
@@ -370,9 +370,9 @@ private void InitializationHelper(
ConnectionCredentials = credentials;
if (ChatCommandIdentifiers.Count == 0)
- ChatCommandIdentifiers.Add('!');
+ ChatCommandIdentifiers.Add("!");
if (WhisperCommandIdentifiers.Count == 0)
- WhisperCommandIdentifiers.Add('!');
+ WhisperCommandIdentifiers.Add("!");
for (var i = 0; i < channels.Count; i++)
{
@@ -898,13 +898,10 @@ private async Task HandlePrivMsg(IrcMessage ircMessage)
await OnUserIntro.Invoke(this, new(chatMessage));
}
- if (OnChatCommandReceived is not null
- && !string.IsNullOrEmpty(chatMessage.Message)
- && ChatCommandIdentifiers.Contains(chatMessage.Message[0])
- && CommandInfo.TryParse(chatMessage.Message.AsSpan(), out var commandInfo)
- )
+ if (OnChatCommandReceived is not null
+ && CanInvokeCommand(ChatCommandIdentifiers, chatMessage.Message.AsSpan(), out var commandInfo))
{
- await OnChatCommandReceived.Invoke(this, new(chatMessage, commandInfo));
+ await OnChatCommandReceived.Invoke(this, new(chatMessage, commandInfo!));
}
}
@@ -1101,16 +1098,29 @@ private async Task HandleWhisper(IrcMessage ircMessage)
await OnWhisperReceived.TryInvoke(this, new(whisperMessage));
- if (OnWhisperCommandReceived is not null
- && !string.IsNullOrEmpty(whisperMessage.Message)
- && WhisperCommandIdentifiers.Contains(whisperMessage.Message[0])
- && CommandInfo.TryParse(whisperMessage.Message.AsSpan(), out var commandInfo)
- )
+ if (OnWhisperCommandReceived is not null
+ && CanInvokeCommand(WhisperCommandIdentifiers, whisperMessage.Message.AsSpan(), out var commandInfo))
{
- await OnWhisperCommandReceived.Invoke(this, new(whisperMessage, commandInfo));
+ await OnWhisperCommandReceived.Invoke(this, new(whisperMessage, commandInfo!));
}
}
+ static bool CanInvokeCommand(ICollection commandIdentifiers, ReadOnlySpan message, out CommandInfo? commandInfo)
+ {
+ commandInfo = null;
+ if (message.IsEmpty)
+ return false;
+
+ foreach (var commandIdentifier in commandIdentifiers)
+ {
+ if (message.StartsWith(commandIdentifier.AsSpan()))
+ {
+ return CommandInfo.TryParse(commandIdentifier, message, out commandInfo);
+ }
+ }
+ return false;
+ }
+
///
/// Handles the state of the room.
///
@@ -1169,9 +1179,9 @@ private static Task HandleCap(IrcMessage _)
return Task.CompletedTask;
}
- #endregion
+#endregion
- #endregion
+#endregion
private Task UnaccountedFor(string ircString)
{