Added ability to construct IRC messages from parts when sending them.

This commit is contained in:
Cameron
2024-03-25 05:06:51 -05:00
parent 81651a0e59
commit 1bf8afc68b
4 changed files with 77 additions and 22 deletions

View File

@@ -139,12 +139,52 @@ namespace TwitchIrcClient.IRC
var bytes = Encoding.UTF8.GetBytes(line + ENDL); var bytes = Encoding.UTF8.GetBytes(line + ENDL);
_Stream.Write(bytes, 0, bytes.Length); _Stream.Write(bytes, 0, bytes.Length);
} }
//TODO make this unit testable?
/// <summary>
/// Construct an IRC message from parts and sends it. Does little to no validation on inputs.
/// </summary>
/// <param name="command"></param>
/// <param name="parameters"></param>
/// <param name="tags"></param>
/// <param name="prefix"></param>
public void SendMessage(IrcMessageType command, IEnumerable<string>? parameters = null,
Dictionary<string, string?>? 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) public void Authenticate(string? user, string? pass)
{ {
if (user == null) user ??= $"justinfan{Random.Shared.NextInt64(10000):D4}";
user = $"justinfan{Random.Shared.NextInt64(10000):D4}"; pass ??= "pass";
if (pass == null)
pass = "pass";
SendLine($"NICK {user}"); SendLine($"NICK {user}");
SendLine($"PASS {pass}"); SendLine($"PASS {pass}");
} }

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace TwitchIrcClient.IRC namespace TwitchIrcClient.IRC
{ {
/// <summary>
/// Represents the "command" of an IRC message.
/// </summary>
public enum IrcMessageType public enum IrcMessageType
{ {
//twitch standard messages //twitch standard messages
@@ -174,12 +177,26 @@ namespace TwitchIrcClient.IRC
} }
public static class IrcMessageTypeHelper public static class IrcMessageTypeHelper
{ {
//parses a string that is either a numeric code or the command name /// <summary>
/// Parses a string that is either a numeric code or the command name.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
/// <remarks>
/// The value range 000-999 is reserved for numeric commands, and will
/// be converted to a numeric string when forming a message.
/// </remarks>
public static IrcMessageType Parse(string s) public static IrcMessageType Parse(string s)
{ {
if (int.TryParse(s, out int result)) if (int.TryParse(s, out int result))
return (IrcMessageType)result; return (IrcMessageType)result;
return Enum.Parse<IrcMessageType>(s); return Enum.Parse<IrcMessageType>(s);
} }
public static string ToCommand(this IrcMessageType type)
{
if ((int)type >= 0 && (int)type < 1000)
return $"{(int)type,3}";
return type.ToString();
}
} }
} }

View File

@@ -96,19 +96,19 @@ namespace TwitchIrcClient.IRC.Messages
} }
return message.MessageType switch return message.MessageType switch
{ {
IrcMessageType.CLEARCHAT => new ClearChat(message), IrcMessageType.CLEARCHAT => new ClearChat(message),
IrcMessageType.CLEARMSG => new ClearMsg(message), IrcMessageType.CLEARMSG => new ClearMsg(message),
IrcMessageType.JOIN => new Join(message), IrcMessageType.JOIN => new Join(message),
IrcMessageType.GLOBALUSERSTATE => new GlobalUserState(message), IrcMessageType.GLOBALUSERSTATE => new GlobalUserState(message),
IrcMessageType.HOSTTARGET => new HostTarget(message), IrcMessageType.HOSTTARGET => new HostTarget(message),
IrcMessageType.NOTICE => new Notice(message), IrcMessageType.NOTICE => new Notice(message),
IrcMessageType.PART => new Part(message), IrcMessageType.PART => new Part(message),
IrcMessageType.PRIVMSG => new Privmsg(message), IrcMessageType.PRIVMSG => new Privmsg(message),
IrcMessageType.ROOMSTATE => new Roomstate(message), IrcMessageType.ROOMSTATE => new Roomstate(message),
IrcMessageType.RPL_NAMREPLY => new NamReply(message), IrcMessageType.RPL_NAMREPLY => new NamReply(message),
IrcMessageType.USERNOTICE => new UserNotice(message), IrcMessageType.USERNOTICE => new UserNotice(message),
IrcMessageType.USERSTATE => new UserState(message), IrcMessageType.USERSTATE => new UserState(message),
IrcMessageType.WHISPER => new Whisper(message), IrcMessageType.WHISPER => new Whisper(message),
_ => message, _ => message,
}; };
} }

View File

@@ -9,11 +9,9 @@ RateLimiter limiter = new(20, 30);
bool ssl = true; bool ssl = true;
async Task<IrcConnection> CreateConnection(string channel) async Task<IrcConnection> CreateConnection(string channel)
{ {
IrcConnection connection; IrcConnection connection = ssl
if (ssl) ? connection = new IrcConnection("irc.chat.twitch.tv", 6697, limiter, true, true)
connection = new IrcConnection("irc.chat.twitch.tv", 6697, limiter, true, true); : connection = new IrcConnection("irc.chat.twitch.tv", 6667, limiter, true, false);
else
connection = new IrcConnection("irc.chat.twitch.tv", 6667, limiter, true, false);
connection.AddCallback(new MessageCallbackItem( connection.AddCallback(new MessageCallbackItem(
(o, m) => (o, m) =>
{ {
@@ -51,7 +49,7 @@ async Task<IrcConnection> CreateConnection(string channel)
} }
Console.Write("Channel: "); Console.Write("Channel: ");
var channelName = Console.ReadLine(); var channelName = Console.ReadLine();
ArgumentNullException.ThrowIfNull(channelName, nameof(Channel)); ArgumentNullException.ThrowIfNullOrWhiteSpace(channelName, nameof(channelName));
var connection = await CreateConnection(channelName); var connection = await CreateConnection(channelName);
while (true) while (true)
{ {