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