diff --git a/IRCBot.cs b/IRCBot.cs index 9605393..1b912f3 100644 --- a/IRCBot.cs +++ b/IRCBot.cs @@ -1,7 +1,7 @@ +using System.Data; using System.Net.Sockets; -using System.Runtime.CompilerServices; using System.Text; -using System.Threading.Channels; +using System.Text.Json; namespace riff; @@ -11,23 +11,33 @@ public abstract class IRCBot public int Port; public string Nick; public string RealName; - public List Channels = new(); + 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) + 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) @@ -69,10 +79,10 @@ public abstract class IRCBot public void JoinChannel(string channel) { SendLine($"JOIN {channel}"); - Channels.Add(channel); + JoinedChannels.Add(channel); } - public void JoinChannels(string[] channels) + public void JoinChannels(List channels) { foreach (string channel in channels) JoinChannel(channel); @@ -81,13 +91,52 @@ public abstract class IRCBot public void PartChannel(string channel) { SendLine($"PART {channel}"); - Channels.Remove(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.Run(); + runner.Start(); using var reader = new StreamReader(Stream, Encoding.UTF8); while (true) { @@ -97,15 +146,25 @@ public abstract class IRCBot Console.WriteLine($"{Nick} disconnected"); break; } + Console.WriteLine(line); IRCMessage msg = new(line); switch (msg.Command) { case "PING": - if (msg.Arguments == null) + 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) diff --git a/Program.cs b/Program.cs index e36663a..6af0960 100644 --- a/Program.cs +++ b/Program.cs @@ -4,7 +4,15 @@ class Program { static void Main(string[] args) { - var cube = new Cube("localhost", 6667, "mysterious_cube", "~nebula https://git.tilde.town/nebula/riff"); + List mysteriousCubeChannels = [ + "#testbot", + ]; + var cube = new MysteriousCube( + "localhost", + 6667, + "mysterious_cube", + "~nebula https://git.tilde.town/nebula/riff", + mysteriousCubeChannels); new Thread(new ThreadStart(cube.MainLoop)).Start(); } } \ No newline at end of file diff --git a/Types.cs b/Types.cs index bf84122..9c83bcf 100644 --- a/Types.cs +++ b/Types.cs @@ -6,7 +6,7 @@ public class IRCMessage { public string? Source; public string Command; - public List? Arguments; + public List Arguments; public IRCMessage(string line) { @@ -31,7 +31,7 @@ public class IRCMessage args.Add(arg); } Command = args[0]; - Arguments = args.Count > 1 ? args[1..] : null; + Arguments = args.Count > 1 ? args[1..] : new List(); } } @@ -43,7 +43,7 @@ public class PRIVMSG public PRIVMSG(IRCMessage message) { - if (message.Source == null || message.Arguments == null) + if (message.Source == null) throw new NoNullAllowedException(); Nick = message.Source.Substring(0, message.Source.IndexOf("!")); Body = message.Arguments[1].Trim(); @@ -91,7 +91,7 @@ public class PollRunner Callback = callback; } - public async void Run() + public async void Start() { while (true) { diff --git a/bots/Cube.cs b/bots/MysteriousCube.cs similarity index 68% rename from bots/Cube.cs rename to bots/MysteriousCube.cs index 6e33ec3..382a4d2 100644 --- a/bots/Cube.cs +++ b/bots/MysteriousCube.cs @@ -2,31 +2,34 @@ using System.Data; using System.Text.Json; using riff; -public class Cube : IRCBot +public class MysteriousCube : IRCBot { private HttpClient RequestClient = new(); private const string AllTriviaQuestionsPath = "data/TriviaQuestions.json"; private const string UnaskedTriviaQuestionsPath = "data/UnaskedTriviaQuestions.json"; private TriviaQuestion[] AllTriviaQuestions; private List UnaskedTriviaQuestions; + private bool ListeningForAnswer = false; + private TriviaQuestion? CurrentQuestion = null; private Random Rand = new(); - public Cube(string host, int port, string nick, string realname) : base(host, port, nick, realname) + public MysteriousCube(string host, int port, string nick, string realname, List permanentChannels) + : base(host, port, nick, realname, permanentChannels) { AllTriviaQuestions = LoadAllQuestions(); UnaskedTriviaQuestions = LoadUnaskedQuestions(); - string[] channels = [ - "#testbot", - ]; - (string, Action)[] commands = [ ("echo", Echo), ("trivia", PostQuestion) ]; + Action[] listeners = [ + AnswerListener + ]; + HookCommands(commands); - JoinChannels(channels); + HookListeners(listeners); } private void Echo(PRIVMSG privmsg) @@ -37,7 +40,31 @@ public class Cube : IRCBot private void PostQuestion(PRIVMSG privmsg) { var question = GetQuestion(); - SendPrivmsg(privmsg.Sender, question.Question); + CurrentQuestion = question; + ListeningForAnswer = true; + SendPrivmsg(privmsg.Sender, $"Answer 'true' or 'false': {question.Question}"); + } + + private void AnswerListener(PRIVMSG privmsg) + { + if (!ListeningForAnswer || CurrentQuestion == null) + return; + string line = privmsg.Body.ToLower().Trim(); + bool? answer = null; + if (line.StartsWith("true")) + answer = true; + else if (line.StartsWith("false")) + answer = false; + else + return; + ListeningForAnswer = false; + string response; + if (answer == CurrentQuestion.Answer) + response = $"{privmsg.Nick} is correct!"; + else + response = $"{privmsg.Nick} is wrong!"; + CurrentQuestion = null; + SendPrivmsg(privmsg.Sender, response); } private TriviaQuestion GetQuestion()