mirror of
https://github.com/Ikatono/TwitchIrcClient.git
synced 2025-10-29 04:56:12 -05:00
Compare commits
2 Commits
81651a0e59
...
4806e50736
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4806e50736 | ||
|
|
1bf8afc68b |
@@ -139,19 +139,68 @@ 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())
|
||||||
|
{
|
||||||
|
//if ((command == IrcMessageType.NICK || command == IrcMessageType.PASS)
|
||||||
|
// && parameters.Count() == 1)
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
message += " " + parameters.Single();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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)
|
SendMessage(IrcMessageType.PASS, parameters: [pass]);
|
||||||
pass = "pass";
|
SendMessage(IrcMessageType.NICK, parameters: [user]);
|
||||||
SendLine($"NICK {user}");
|
|
||||||
SendLine($"PASS {pass}");
|
|
||||||
}
|
}
|
||||||
public void JoinChannel(string channel)
|
public void JoinChannel(string channel)
|
||||||
{
|
{
|
||||||
channel = channel.TrimStart('#');
|
channel = channel.TrimStart('#');
|
||||||
SendLine($"JOIN #{channel}");
|
SendMessage(IrcMessageType.JOIN, ["#" + channel]);
|
||||||
}
|
}
|
||||||
private async void ListenForInput()
|
private async void ListenForInput()
|
||||||
{
|
{
|
||||||
@@ -268,30 +317,36 @@ namespace TwitchIrcClient.IRC
|
|||||||
public void AddCallback(MessageCallbackItem callbackItem)
|
public void AddCallback(MessageCallbackItem callbackItem)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(disposedValue, this);
|
ObjectDisposedException.ThrowIf(disposedValue, this);
|
||||||
UserCallbacks.Add(callbackItem);
|
lock (UserCallbacks)
|
||||||
|
UserCallbacks.Add(callbackItem);
|
||||||
}
|
}
|
||||||
public bool RemoveCallback(MessageCallbackItem callbackItem)
|
public bool RemoveCallback(MessageCallbackItem callbackItem)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(disposedValue, this);
|
ObjectDisposedException.ThrowIf(disposedValue, this);
|
||||||
return UserCallbacks.Remove(callbackItem);
|
lock (UserCallbacks)
|
||||||
|
return UserCallbacks.Remove(callbackItem);
|
||||||
}
|
}
|
||||||
protected void AddSystemCallback(MessageCallbackItem callbackItem)
|
protected void AddSystemCallback(MessageCallbackItem callbackItem)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(disposedValue, this);
|
ObjectDisposedException.ThrowIf(disposedValue, this);
|
||||||
SystemCallbacks.Add(callbackItem);
|
lock (SystemCallbacks)
|
||||||
|
SystemCallbacks.Add(callbackItem);
|
||||||
}
|
}
|
||||||
protected bool RemoveSystemCallback(MessageCallbackItem callbackItem)
|
protected bool RemoveSystemCallback(MessageCallbackItem callbackItem)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(disposedValue, this);
|
ObjectDisposedException.ThrowIf(disposedValue, this);
|
||||||
return SystemCallbacks.Remove(callbackItem);
|
lock (SystemCallbacks)
|
||||||
|
return SystemCallbacks.Remove(callbackItem);
|
||||||
}
|
}
|
||||||
private void RunCallbacks(ReceivedMessage message)
|
private void RunCallbacks(ReceivedMessage message)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(message, nameof(message));
|
ArgumentNullException.ThrowIfNull(message, nameof(message));
|
||||||
if (disposedValue)
|
if (disposedValue)
|
||||||
return;
|
return;
|
||||||
SystemCallbacks.ForEach(c => c.TryCall(this, message));
|
lock (SystemCallbacks)
|
||||||
UserCallbacks.ForEach(c => c.TryCall(this, message));
|
SystemCallbacks.ForEach(c => c.TryCall(this, message));
|
||||||
|
lock (UserCallbacks)
|
||||||
|
UserCallbacks.ForEach(c => c.TryCall(this, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Dispose
|
#region Dispose
|
||||||
@@ -306,6 +361,7 @@ namespace TwitchIrcClient.IRC
|
|||||||
TokenSource.Dispose();
|
TokenSource.Dispose();
|
||||||
Client?.Dispose();
|
Client?.Dispose();
|
||||||
_HeartbeatTimer?.Dispose();
|
_HeartbeatTimer?.Dispose();
|
||||||
|
_Stream?.Dispose();
|
||||||
}
|
}
|
||||||
disposedValue = true;
|
disposedValue = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user