using System.Data; using System.Net.Sockets; using System.Text; using System.Text.Json; namespace riff; public abstract class IRCBot { public string Host; public int Port; public string Nick; public string RealName; public List JoinedChannels = new(); public List PermanentChannels; public List UserChannels; public string UserChannelStoragePath; public List> Listeners = new(); private List CommandRunners = new(); private List PollRunners = new(); private TcpClient Client; private NetworkStream Stream; public IRCBot(string host, int port, string nick, string realname, List permanentChannels) { Host = host; Port = port; Nick = nick; RealName = realname; PermanentChannels = permanentChannels; UserChannelStoragePath = $"data/{Nick}_user_channels.json"; UserChannels = LoadUserChannels(); Client = new TcpClient(Host, Port); Stream = Client.GetStream(); SendLine($"NICK {Nick}"); SendLine($"USER {Nick} 0 * :{RealName}"); foreach (string channel in PermanentChannels) JoinChannel(channel); foreach (string channel in UserChannels) JoinChannel(channel); } public void HookCommands((string, Action)[] commands) { foreach ((string, Action) command in commands) { CommandRunners.Add(new CommandRunner(command.Item1, command.Item2)); } } public void HookPollRunners((int, Action)[] runners) { foreach ((int, Action) runner in runners) { PollRunners.Add(new PollRunner(runner.Item1, runner.Item2)); } } public void HookListeners(Action[] listeners) { foreach (Action listener in listeners) { Listeners.Add(listener); } } private void SendLine(string line) { Byte[] bytes = Encoding.UTF8.GetBytes(line + "\r\n"); Stream.Write(bytes, 0, bytes.Length); } public void SendPrivmsg(string destination, string message) { if (message != string.Empty) SendLine($"PRIVMSG {destination} :{message}"); } public void JoinChannel(string channel) { SendLine($"JOIN {channel}"); JoinedChannels.Add(channel); } public void JoinChannels(List channels) { foreach (string channel in channels) JoinChannel(channel); } public void PartChannel(string channel) { SendLine($"PART {channel}"); JoinedChannels.Remove(channel); } public List LoadUserChannels() { List channels; try { using (FileStream stream = File.OpenRead(UserChannelStoragePath)) { channels = JsonSerializer.Deserialize>(stream)!; if (channels == null) throw new NoNullAllowedException(); return channels; } } catch (FileNotFoundException) { channels = []; return channels; } } public void AddUserChannel(string channel) { UserChannels.Add(channel); JoinChannel(channel); WriteUserChannels(); } public void RemoveUserChannel(string channel) { UserChannels.Remove(channel); PartChannel(channel); WriteUserChannels(); } public void WriteUserChannels() { File.WriteAllText(UserChannelStoragePath, JsonSerializer.Serialize(UserChannels)); } public void MainLoop() { foreach (PollRunner runner in PollRunners) runner.Start(); using var reader = new StreamReader(Stream, Encoding.UTF8); while (true) { string? line = reader.ReadLine(); if (line == null) { Console.WriteLine($"{Nick} disconnected"); break; } Console.WriteLine(line); IRCMessage msg = new(line); switch (msg.Command) { case "PING": if (msg.Arguments.Count == 0) SendLine("PONG"); else SendLine("PONG " + msg.Arguments[0]); break; case "INVITE": AddUserChannel(msg.Arguments[1]); break; case "KICK": string channel = msg.Arguments[0]; UserChannels.Remove(channel); JoinedChannels.Remove(channel); WriteUserChannels(); break; case "PRIVMSG": PRIVMSG privmsg = new(msg); foreach (Action listener in Listeners) listener(privmsg); foreach (CommandRunner command in CommandRunners) if (command.Run(privmsg)) break; break; } } } }