IRC parsing, tokenization, and state handling in C#
1using System;
2using System.Net.Sockets;
3using System.Threading;
4using IRCStates;
5using IRCTokens;
6// ReSharper disable StringLiteralTypo
7
8namespace States;
9
10internal class Client(string host, int port, string nick)
11{
12 private readonly byte[] _bytes = new byte[1024];
13 private readonly StatefulEncoder _encoder = new();
14 private readonly Server _server = new("test");
15 private readonly Socket _socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
16
17 private void Send(string raw)
18 {
19 _encoder.Push(new(raw));
20 }
21
22 public void Start()
23 {
24 _socket.Connect(host, port);
25 while (!_socket.Connected) Thread.Sleep(1000);
26
27 Send("USER test 0 * test");
28 Send($"NICK {nick}");
29
30 while (true)
31 {
32 while (_encoder.PendingBytes.Length != 0)
33 {
34 foreach (var line in _encoder.Pop(_socket.Send(_encoder.PendingBytes)))
35 Console.WriteLine($"> {line.Format()}");
36 }
37
38 var bytesReceived = _socket.Receive(_bytes);
39 if (bytesReceived == 0)
40 {
41 Console.WriteLine("! disconnected");
42 _socket.Shutdown(SocketShutdown.Both);
43 _socket.Close();
44 break;
45 }
46
47 var receivedLines = _server.Receive(_bytes, bytesReceived);
48 foreach (var (line, _) in receivedLines)
49 {
50 Console.WriteLine($"< {line.Format()}");
51
52 switch (line.Command)
53 {
54 case Commands.Privmsg:
55 if (line.Params[1].Contains(_server.NickName))
56 Send($"PRIVMSG {line.Params[0]} :hi {line.Hostmask.NickName}!");
57 break;
58 case "PING":
59 Send($"PONG :{line.Params[0]}");
60 break;
61 case Numeric.RPL_WELCOME:
62 if (!_server.HasChannel("#irctokens")) Send("JOIN #irctokens");
63 break;
64 case "INVITE":
65 var c = line.Params[1];
66 if (!_server.HasChannel(c)) Send($"JOIN {c}");
67 break;
68 }
69 }
70 }
71 }
72}