mirror of
https://github.com/Ikatono/TwitchIrcClient.git
synced 2025-10-29 04:56:12 -05:00
Compare commits
4 Commits
9dc86478a8
...
cceae30d5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cceae30d5e | ||
|
|
917e90558d | ||
|
|
29b5b111b2 | ||
|
|
c2ad6b9a8e |
1
README.md
Normal file
1
README.md
Normal file
@@ -0,0 +1 @@
|
||||
Provides a light-weight client for Twitch chatrooms over IRC. Primarily focused on receiving messages rather than sending them, TwitchIrcClient automatically requests all messages and tags from Twitch and parses these into easy to use classes. Additionally, it provides an event-like interface to receive messages of a specific type, and quality of life features like user tracking and an event for batches of user updates. Future plans include better handling of outgoing messages, providing interfaces to more tags for features like Hype Chats, and a better way to read chat messages with emotes substituted.
|
||||
@@ -15,16 +15,6 @@ namespace TwitchIrcClient.IRC.Messages
|
||||
/// </summary>
|
||||
public NoticeId? MessageId => Enum.TryParse(TryGetTag("msg-id"), out NoticeId value)
|
||||
? value : null;
|
||||
//{ get
|
||||
// {
|
||||
// string spaced = TryGetTag("msg-id").Replace('_', ' ');
|
||||
// string title = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(spaced);
|
||||
// string pascal = title.Replace(" ", "");
|
||||
// if (!Enum.TryParse(pascal, out NoticeId value))
|
||||
// return null;
|
||||
// return value;
|
||||
// }
|
||||
//}
|
||||
public string TargetUserId => TryGetTag("target-user-id");
|
||||
|
||||
public Notice(ReceivedMessage message) : base(message)
|
||||
|
||||
@@ -82,12 +82,13 @@ namespace TwitchIrcClient.IRC.Messages
|
||||
//is stripped
|
||||
if (s.StartsWith(':'))
|
||||
{
|
||||
message.Parameters.Add(s.Substring(1));
|
||||
message.Parameters.Add(s[1..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var spl_final = s.Split(" :", 2);
|
||||
var spl_initial = spl_final[0].Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var spl_initial = spl_final[0].Split(' ', StringSplitOptions.RemoveEmptyEntries
|
||||
| StringSplitOptions.TrimEntries);
|
||||
message.Parameters.AddRange(spl_initial);
|
||||
if (spl_final.Length >= 2)
|
||||
message.Parameters.Add(spl_final[1]);
|
||||
|
||||
@@ -29,28 +29,35 @@ namespace TwitchIrcClient.IRC
|
||||
Timer.Start();
|
||||
}
|
||||
|
||||
public void WaitForAvailable(CancellationToken? token = null)
|
||||
public bool WaitForAvailable(CancellationToken? token = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (token is CancellationToken actualToken)
|
||||
Semaphore.Wait(actualToken);
|
||||
else
|
||||
Semaphore.Wait();
|
||||
lock (Semaphore)
|
||||
{
|
||||
if (token is CancellationToken actualToken)
|
||||
Semaphore.Wait(actualToken);
|
||||
else
|
||||
Semaphore.Wait();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
//caller is responsible for checking whether connection is cancelled before trying to send
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool WaitForAvailable(TimeSpan timeout, CancellationToken? token = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (token is CancellationToken actualToken)
|
||||
return Semaphore.Wait(timeout, actualToken);
|
||||
else
|
||||
return Semaphore.Wait(timeout);
|
||||
lock (Semaphore)
|
||||
{
|
||||
if (token is CancellationToken actualToken)
|
||||
return Semaphore.Wait(timeout, actualToken);
|
||||
else
|
||||
return Semaphore.Wait(timeout);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -61,10 +68,13 @@ namespace TwitchIrcClient.IRC
|
||||
{
|
||||
try
|
||||
{
|
||||
if (token is CancellationToken actualToken)
|
||||
return Semaphore.Wait(millis, actualToken);
|
||||
else
|
||||
return Semaphore.Wait(millis);
|
||||
lock (Semaphore)
|
||||
{
|
||||
if (token is CancellationToken actualToken)
|
||||
return Semaphore.Wait(millis, actualToken);
|
||||
else
|
||||
return Semaphore.Wait(millis);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -76,16 +86,13 @@ namespace TwitchIrcClient.IRC
|
||||
{
|
||||
try
|
||||
{
|
||||
Semaphore.Release(MessageLimit);
|
||||
}
|
||||
catch (SemaphoreFullException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
|
||||
lock (Semaphore)
|
||||
{
|
||||
Semaphore.Release(MessageLimit - Semaphore.CurrentCount);
|
||||
}
|
||||
}
|
||||
catch (SemaphoreFullException) { }
|
||||
catch (ObjectDisposedException) { }
|
||||
}
|
||||
|
||||
#region RateLimiter Dispose
|
||||
|
||||
@@ -1 +1,62 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Security.AccessControl;
|
||||
using System.Threading.Channels;
|
||||
using TwitchIrcClient.IRC;
|
||||
using TwitchIrcClient.IRC.Messages;
|
||||
|
||||
RateLimiter limiter = new(20, 30);
|
||||
bool ssl = true;
|
||||
async Task<IrcConnection> 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);
|
||||
connection.AddCallback(new MessageCallbackItem(
|
||||
(o, m) =>
|
||||
{
|
||||
if (m is Privmsg priv)
|
||||
{
|
||||
if (priv.Bits > 0)
|
||||
lock (Console.Out)
|
||||
Console.WriteLine($"{priv.DisplayName}: {priv.Bits}{Environment.NewLine}");
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("Received an unrequested message type", nameof(m));
|
||||
}, [IrcMessageType.PRIVMSG]));
|
||||
connection.onUserChange += (object? o, UserChangeEventArgs args) =>
|
||||
{
|
||||
lock (Console.Out)
|
||||
{
|
||||
var resetColor = Console.BackgroundColor;
|
||||
Console.BackgroundColor = ConsoleColor.DarkGreen;
|
||||
Console.WriteLine(string.Join(", ", args.Joined.Order()));
|
||||
Console.BackgroundColor = ConsoleColor.DarkRed;
|
||||
Console.WriteLine(string.Join(", ", args.Left.Order()));
|
||||
Console.BackgroundColor = resetColor;
|
||||
Console.WriteLine();
|
||||
}
|
||||
};
|
||||
if (!await connection.ConnectAsync())
|
||||
{
|
||||
Console.WriteLine("failed to connect");
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
connection.Authenticate(null, null);
|
||||
connection.SendLine("CAP REQ :twitch.tv/commands twitch.tv/membership twitch.tv/tags");
|
||||
connection.JoinChannel(channel);
|
||||
return connection;
|
||||
}
|
||||
Console.Write("Channel: ");
|
||||
var channelName = Console.ReadLine();
|
||||
ArgumentNullException.ThrowIfNull(channelName, nameof(Channel));
|
||||
var connection = CreateConnection(channelName);
|
||||
while (true)
|
||||
{
|
||||
//all the work happens in other threads
|
||||
//specifically the threadpool used by Task.Run for
|
||||
//the tasks owned by the IrcConnection
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user