diff --git a/TwitchIrcClient/IRC/IrcConnection.cs b/TwitchIrcClient/IRC/IrcConnection.cs
index 2f66ce8..ea8252a 100644
--- a/TwitchIrcClient/IRC/IrcConnection.cs
+++ b/TwitchIrcClient/IRC/IrcConnection.cs
@@ -139,12 +139,52 @@ namespace TwitchIrcClient.IRC
var bytes = Encoding.UTF8.GetBytes(line + ENDL);
_Stream.Write(bytes, 0, bytes.Length);
}
+ //TODO make this unit testable?
+ ///
+ /// Construct an IRC message from parts and sends it. Does little to no validation on inputs.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void SendMessage(IrcMessageType command, IEnumerable? parameters = null,
+ Dictionary? tags = null, string? prefix = null)
+ {
+ var message = "";
+ if (tags is not null && tags.Count != 0)
+ {
+ message = "@" + string.Join(';',
+ tags.OrderBy(p => p.Key).Select(p => $"{p.Key}={EscapeTagValue(p.Value)}"))
+ + " ";
+ }
+ if (prefix is not null && !string.IsNullOrWhiteSpace(prefix))
+ message += ":" + prefix + " ";
+ message += command.ToCommand() + " ";
+ if (parameters is not null && parameters.Any())
+ {
+ message += string.Join(' ', parameters.SkipLast(1));
+ message += " :" + parameters.Last();
+ }
+ SendLine(message);
+ }
+ private static string EscapeTagValue(string? s)
+ {
+ if (s is null)
+ return "";
+ return string.Join("", s.Select(c => c switch
+ {
+ ';' => @"\:",
+ ' ' => @"\s",
+ '\\' => @"\\",
+ '\r' => @"\r",
+ '\n' => @"\n",
+ char ch => ch.ToString(),
+ }));
+ }
public void Authenticate(string? user, string? pass)
{
- if (user == null)
- user = $"justinfan{Random.Shared.NextInt64(10000):D4}";
- if (pass == null)
- pass = "pass";
+ user ??= $"justinfan{Random.Shared.NextInt64(10000):D4}";
+ pass ??= "pass";
SendLine($"NICK {user}");
SendLine($"PASS {pass}");
}
diff --git a/TwitchIrcClient/IRC/IrcMessageType.cs b/TwitchIrcClient/IRC/IrcMessageType.cs
index 9eefa07..33743d6 100644
--- a/TwitchIrcClient/IRC/IrcMessageType.cs
+++ b/TwitchIrcClient/IRC/IrcMessageType.cs
@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace TwitchIrcClient.IRC
{
+ ///
+ /// Represents the "command" of an IRC message.
+ ///
public enum IrcMessageType
{
//twitch standard messages
@@ -174,12 +177,26 @@ namespace TwitchIrcClient.IRC
}
public static class IrcMessageTypeHelper
{
- //parses a string that is either a numeric code or the command name
+ ///
+ /// Parses a string that is either a numeric code or the command name.
+ ///
+ ///
+ ///
+ ///
+ /// The value range 000-999 is reserved for numeric commands, and will
+ /// be converted to a numeric string when forming a message.
+ ///
public static IrcMessageType Parse(string s)
{
if (int.TryParse(s, out int result))
return (IrcMessageType)result;
return Enum.Parse(s);
}
+ public static string ToCommand(this IrcMessageType type)
+ {
+ if ((int)type >= 0 && (int)type < 1000)
+ return $"{(int)type,3}";
+ return type.ToString();
+ }
}
}
diff --git a/TwitchIrcClient/IRC/Messages/ReceivedMessage.cs b/TwitchIrcClient/IRC/Messages/ReceivedMessage.cs
index 958ff35..24ded53 100644
--- a/TwitchIrcClient/IRC/Messages/ReceivedMessage.cs
+++ b/TwitchIrcClient/IRC/Messages/ReceivedMessage.cs
@@ -96,19 +96,19 @@ namespace TwitchIrcClient.IRC.Messages
}
return message.MessageType switch
{
- IrcMessageType.CLEARCHAT => new ClearChat(message),
- IrcMessageType.CLEARMSG => new ClearMsg(message),
- IrcMessageType.JOIN => new Join(message),
+ IrcMessageType.CLEARCHAT => new ClearChat(message),
+ IrcMessageType.CLEARMSG => new ClearMsg(message),
+ IrcMessageType.JOIN => new Join(message),
IrcMessageType.GLOBALUSERSTATE => new GlobalUserState(message),
- IrcMessageType.HOSTTARGET => new HostTarget(message),
- IrcMessageType.NOTICE => new Notice(message),
- IrcMessageType.PART => new Part(message),
- IrcMessageType.PRIVMSG => new Privmsg(message),
- IrcMessageType.ROOMSTATE => new Roomstate(message),
+ IrcMessageType.HOSTTARGET => new HostTarget(message),
+ IrcMessageType.NOTICE => new Notice(message),
+ IrcMessageType.PART => new Part(message),
+ IrcMessageType.PRIVMSG => new Privmsg(message),
+ IrcMessageType.ROOMSTATE => new Roomstate(message),
IrcMessageType.RPL_NAMREPLY => new NamReply(message),
- IrcMessageType.USERNOTICE => new UserNotice(message),
- IrcMessageType.USERSTATE => new UserState(message),
- IrcMessageType.WHISPER => new Whisper(message),
+ IrcMessageType.USERNOTICE => new UserNotice(message),
+ IrcMessageType.USERSTATE => new UserState(message),
+ IrcMessageType.WHISPER => new Whisper(message),
_ => message,
};
}
diff --git a/TwitchIrcClient/Program.cs b/TwitchIrcClient/Program.cs
index e4ffc2f..d0c341a 100644
--- a/TwitchIrcClient/Program.cs
+++ b/TwitchIrcClient/Program.cs
@@ -9,11 +9,9 @@ RateLimiter limiter = new(20, 30);
bool ssl = true;
async Task CreateConnection(string channel)
{
- IrcConnection connection;
- if (ssl)
- connection = new IrcConnection("irc.chat.twitch.tv", 6697, limiter, true, true);
- else
- connection = new IrcConnection("irc.chat.twitch.tv", 6667, limiter, true, false);
+ IrcConnection connection = ssl
+ ? connection = new IrcConnection("irc.chat.twitch.tv", 6697, limiter, true, true)
+ : connection = new IrcConnection("irc.chat.twitch.tv", 6667, limiter, true, false);
connection.AddCallback(new MessageCallbackItem(
(o, m) =>
{
@@ -51,7 +49,7 @@ async Task CreateConnection(string channel)
}
Console.Write("Channel: ");
var channelName = Console.ReadLine();
-ArgumentNullException.ThrowIfNull(channelName, nameof(Channel));
+ArgumentNullException.ThrowIfNullOrWhiteSpace(channelName, nameof(channelName));
var connection = await CreateConnection(channelName);
while (true)
{