IRC parsing, tokenization, and state handling in C#

working ircstates example

+168 -8
+84
Examples/States/Client.cs
··· 1 + using System; 2 + using System.Linq; 3 + using System.Net.Sockets; 4 + using System.Threading; 5 + using IrcStates; 6 + using IrcTokens; 7 + 8 + namespace StatesSample 9 + { 10 + internal class Client 11 + { 12 + private readonly byte[] _bytes; 13 + private readonly StatefulEncoder _encoder; 14 + private readonly string _host; 15 + private readonly string _nick; 16 + private readonly int _port; 17 + private readonly Server _server; 18 + private readonly Socket _socket; 19 + 20 + public Client(string host, int port, string nick) 21 + { 22 + _server = new Server("test"); 23 + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 24 + _encoder = new StatefulEncoder(); 25 + _host = host; 26 + _port = port; 27 + _nick = nick; 28 + _bytes = new byte[1024]; 29 + } 30 + 31 + private void Send(string raw) 32 + { 33 + _encoder.Push(new Line(raw)); 34 + } 35 + 36 + public void Start() 37 + { 38 + _socket.Connect(_host, _port); 39 + while (!_socket.Connected) Thread.Sleep(1000); 40 + 41 + Send("USER test 0 * test"); 42 + Send($"NICK {_nick}"); 43 + 44 + while (true) 45 + { 46 + while (_encoder.PendingBytes.Any()) 47 + { 48 + var bytesSent = _socket.Send(_encoder.PendingBytes); 49 + var sentLines = _encoder.Pop(bytesSent); 50 + foreach (var line in sentLines) Console.WriteLine($"> {line.Format()}"); 51 + } 52 + 53 + var bytesReceived = _socket.Receive(_bytes); 54 + if (bytesReceived == 0) 55 + { 56 + Console.WriteLine("! disconnected"); 57 + _socket.Shutdown(SocketShutdown.Both); 58 + _socket.Close(); 59 + break; 60 + } 61 + 62 + var receivedLines = _server.Receive(_bytes, bytesReceived); 63 + foreach (var (line, _) in receivedLines) 64 + { 65 + Console.WriteLine($"< {line.Format()}"); 66 + 67 + switch (line.Command) 68 + { 69 + case Commands.Privmsg: 70 + if (line.Params[1].Contains(_server.NickName)) 71 + Send($"PRIVMSG {line.Params[0]} :hi {line.Hostmask.NickName}!"); 72 + break; 73 + case "PING": 74 + Send($"PONG :{line.Params[0]}"); 75 + break; 76 + case Numeric.RPL_WELCOME: 77 + if (!_server.HasChannel("#test")) Send("JOIN #test"); 78 + break; 79 + } 80 + } 81 + } 82 + } 83 + } 84 + }
+3 -4
Examples/States/Program.cs
··· 1 - using System; 2 - 3 - namespace StatesSample 1 + namespace StatesSample 4 2 { 5 3 public static class Program 6 4 { 7 5 private static void Main(string[] args) 8 6 { 9 - Console.WriteLine("Hello World!"); 7 + var client = new Client("localhost", 6667, "statesbot"); 8 + client.Start(); 10 9 } 11 10 } 12 11 }
+77
IrcStates/README.md
··· 3 3 port of [jesopo/ircstates](https://github.com/jesopo/ircstates) 4 4 5 5 bare bones irc client state 6 + 7 + see the full example in [StatesSample/Client.cs](../Examples/States/Client.cs) 8 + 9 + internal class Client 10 + { 11 + private readonly byte[] _bytes; 12 + private readonly StatefulEncoder _encoder; 13 + private readonly string _host; 14 + private readonly string _nick; 15 + private readonly int _port; 16 + private readonly Server _server; 17 + private readonly Socket _socket; 18 + 19 + public Client(string host, int port, string nick) 20 + { 21 + _server = new Server("test"); 22 + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 23 + _encoder = new StatefulEncoder(); 24 + _host = host; 25 + _port = port; 26 + _nick = nick; 27 + _bytes = new byte[1024]; 28 + } 29 + 30 + private void Send(string raw) 31 + { 32 + _encoder.Push(new Line(raw)); 33 + } 34 + 35 + public void Start() 36 + { 37 + _socket.Connect(_host, _port); 38 + while (!_socket.Connected) Thread.Sleep(1000); 39 + 40 + Send("USER test 0 * test"); 41 + Send($"NICK {_nick}"); 42 + 43 + while (true) 44 + { 45 + while (_encoder.PendingBytes.Any()) 46 + { 47 + var bytesSent = _socket.Send(_encoder.PendingBytes); 48 + var sentLines = _encoder.Pop(bytesSent); 49 + foreach (var line in sentLines) Console.WriteLine($"> {line.Format()}"); 50 + } 51 + 52 + var bytesReceived = _socket.Receive(_bytes); 53 + if (bytesReceived == 0) 54 + { 55 + Console.WriteLine("! disconnected"); 56 + _socket.Shutdown(SocketShutdown.Both); 57 + _socket.Close(); 58 + break; 59 + } 60 + 61 + var receivedLines = _server.Receive(_bytes, bytesReceived); 62 + foreach (var (line, _) in receivedLines) 63 + { 64 + Console.WriteLine($"< {line.Format()}"); 65 + 66 + switch (line.Command) 67 + { 68 + case Commands.Privmsg: 69 + if (line.Params[1].Contains(_server.NickName)) 70 + Send($"PRIVMSG {line.Params[0]} :hi {line.Hostmask.NickName}!"); 71 + break; 72 + case "PING": 73 + Send($"PONG :{line.Params[0]}"); 74 + break; 75 + case Numeric.RPL_WELCOME: 76 + if (!_server.HasChannel("#test")) Send("JOIN #test"); 77 + break; 78 + } 79 + } 80 + } 81 + } 82 + }
+3 -3
IrcStates/Server.cs
··· 95 95 ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture)); 96 96 } 97 97 98 - private bool HasChannel(string name) 98 + public bool HasChannel(string name) 99 99 { 100 100 return Channels.ContainsKey(CaseFold(name)); 101 101 } ··· 201 201 } 202 202 } 203 203 204 - public List<(Line, Emit)> Recv(byte[] data) 204 + public IEnumerable<(Line, Emit)> Receive(byte[] data, int length) 205 205 { 206 206 if (data == null) return null; 207 207 208 - var lines = _decoder.Push(data, data.Length); 208 + var lines = _decoder.Push(data, length); 209 209 if (lines == null) throw new ServerDisconnectedException(); 210 210 211 211 return lines.Select(l => (l, Parse(l))).ToList();
+1 -1
IrcTokens/README.md
··· 23 23 24 24 ### stateful 25 25 26 - see the full example in [Examples/Tokens/Client.cs](../Examples/Tokens/Client.cs) 26 + see the full example in [TokensSample/Client.cs](../Examples/Tokens/Client.cs) 27 27 28 28 public class Client 29 29 {