From 66065b9cd527f19922f7bb404c34c8ebd266b8cf Mon Sep 17 00:00:00 2001 From: nebula Date: Mon, 30 Jun 2025 20:30:07 +0000 Subject: [PATCH] super descriptive commit message --- IRCBot.cs | 126 ++++++++++++++++++++++++++++++++++++++++++++ Program.cs | 144 ++------------------------------------------------- Types.cs | 116 +++++++++++++++++++++++++++++++++++++++++ bots/Cube.cs | 26 ++++++++++ 4 files changed, 272 insertions(+), 140 deletions(-) create mode 100644 IRCBot.cs create mode 100644 Types.cs create mode 100644 bots/Cube.cs diff --git a/IRCBot.cs b/IRCBot.cs new file mode 100644 index 0000000..1080d6d --- /dev/null +++ b/IRCBot.cs @@ -0,0 +1,126 @@ +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Channels; + +namespace riff; + +public abstract class IRCBot +{ + public string Host; + public int Port; + public string Nick; + public string RealName; + public List? Channels; + 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) + { + Host = host; + Port = port; + Nick = nick; + RealName = realname; + Client = new TcpClient(Host, Port); + Stream = Client.GetStream(); + SendLine($"NICK {Nick}"); + SendLine($"USER {Nick} 0 * :{RealName}"); + } + + 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 channel, string message) + { + if (message != string.Empty) + SendLine($"PRIVMSG {channel} :{message}"); + } + + public void JoinAllChannels() + { + if (Channels != null) + { + foreach (string channel in Channels) + { + JoinChannel(channel); + } + } + } + + public void JoinChannel(string channel) + { + SendLine($"JOIN {channel}"); + } + + public void PartChannel(string channel) + { + SendLine($"PART {channel}"); + } + + public void MainLoop() + { + foreach (PollRunner runner in PollRunners) + { + runner.Run(); + } + using var reader = new StreamReader(Stream, Encoding.UTF8); + while (true) + { + string? line = reader.ReadLine(); + if (line == null) + { + Console.WriteLine($"{Nick} disconnected"); + break; + } + IRCMessage msg = new(line); + switch (msg.Command) + { + case "PING": + if (msg.Arguments == null) + SendLine("PONG"); + else + SendLine("PONG " + msg.Arguments[0]); + 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; + } + } + } +} + diff --git a/Program.cs b/Program.cs index 728ab4a..0d59a4f 100644 --- a/Program.cs +++ b/Program.cs @@ -1,146 +1,10 @@ -using System.Data; -using System.Net.Sockets; -using System.Text; - -namespace riff; +namespace riff; class Program { static void Main(string[] args) { - var bot = new IRCBot("testbot", "~nebula"); - var subBot = new SubBot("otherbot", "also ~nebula"); - new Thread(new ThreadStart(bot.MainLoop)).Start(); - new Thread(new ThreadStart(subBot.MainLoop)).Start(); + var cube = new Cube("localhost", 6667, "testbot", "~nebula"); + new Thread(new ThreadStart(cube.MainLoop)).Start(); } - - public class IRCBot - { - private int Port = 6667; - private string Host = "localhost"; - private string? Nick; - private string? RealName; - private TcpClient Client; - private NetworkStream Stream; - - public IRCBot(string nick, string realname) - { - Nick = nick; - RealName = realname; - Client = new TcpClient(Host, Port); - Stream = Client.GetStream(); - SendLine($"NICK {Nick}"); - SendLine($"USER {Nick} 0 * :{RealName}"); - SendLine("JOIN #bots"); - } - - public void SendLine(string line) - { - Byte[] bytes = Encoding.UTF8.GetBytes(line + "\r\n"); - Stream.Write(bytes, 0, bytes.Length); - } - - public virtual void TestPrompt() - { - Console.WriteLine("Main class says hello"); - } - - public void MainLoop() - { - using var reader = new StreamReader(Stream, Encoding.UTF8); - while (true) - { - string? line = reader.ReadLine(); - if (line == null) - { - Console.WriteLine($"{Nick} disconnected"); - break; - } - if (line.StartsWith("PING")) - { - SendLine("PONG " + line[5..]); - continue; - } - TestPrompt(); - IRCMessage msg = new(line); - switch (msg.Command) - { - case "PRIVMSG": - // PRIVMSG privmsg = new(msg); - // Console.WriteLine(privmsg.Body); - break; - - } - } - } - } - - public class SubBot : IRCBot - { - public Action[] commands = []; - - public SubBot(string nick, string realname) : base(nick, realname) - { - - } - - public void HelpCommand(string line) - { - SendLine("PRIVMSG #bots :helptext"); - } - - public override void TestPrompt() - { - Console.WriteLine("hello from SubBot"); - } - } - - public class IRCMessage - { - public string? Source; - public string Command; - public List Arguments; - - public IRCMessage(string line) - { - string[] split; - List args = new(); - if (line[0] == ':') - { - split = line[1..].Split(' ', 2); - Source = split[0]; - line = split[1]; - } - if (line.IndexOf(" :") != -1) - { - split = line.Split(" :", 2); - foreach (string arg in split[0].Split(' ')) - args.Add(arg); - args.Add(split[1]); - } - else - { - foreach (string arg in line.Split(' ')) - args.Add(arg); - } - Command = args[0]; - Arguments = args[1..]; - } - } - - public class PRIVMSG - { - public string Body { get; set; } - public string Channel { get; set; } - public string Nick { get; set; } - - public PRIVMSG(IRCMessage message) - { - if (message.Source == null) - throw new NoNullAllowedException(); - Nick = message.Source.Substring(0, message.Source.IndexOf("!") - 1); - Channel = message.Arguments[0]; - Body = message.Arguments[1]; - } - } -} +} \ No newline at end of file diff --git a/Types.cs b/Types.cs new file mode 100644 index 0000000..2547b13 --- /dev/null +++ b/Types.cs @@ -0,0 +1,116 @@ +using System.Data; + +namespace riff; + +public class IRCMessage +{ + public string? Source; + public string Command; + public List? Arguments; + + public IRCMessage(string line) + { + string[] split; + List args = new(); + if (line[0] == ':') + { + split = line[1..].Split(' ', 2); + Source = split[0]; + line = split[1]; + } + if (line.IndexOf(" :") != -1) + { + split = line.Split(" :", 2); + foreach (string arg in split[0].Split(' ')) + args.Add(arg); + args.Add(split[1]); + } + else + { + foreach (string arg in line.Split(' ')) + args.Add(arg); + } + Command = args[0]; + Arguments = args.Count > 1 ? args[1..] : null; + } +} + +public class PRIVMSG +{ + public string Body { get; set; } + public string Sender { get; set; } + public string Nick { get; set; } + + public PRIVMSG(IRCMessage message) + { + if (message.Source == null || message.Arguments == null) + throw new NoNullAllowedException(); + Nick = message.Source.Substring(0, message.Source.IndexOf("!") - 1); + Sender = message.Arguments[0]; + Body = message.Arguments[1].Trim(); + } +} + +public class CommandRunner +{ + public string Trigger; + public Action Callback; + + public CommandRunner(string trigger, Action callback) + { + Callback = callback; + Trigger = trigger; + } + + public bool Run(PRIVMSG privmsg) + { + string[] split = privmsg.Body.Trim().Split(' ', 2); + if (split[0].StartsWith('!') && split[0][1..].Equals(Trigger)) + { + if (split.Length > 1) + privmsg.Body = split[1]; + else + privmsg.Body = string.Empty; + Callback(privmsg); + return true; + } + return false; + } +} + +public class PollRunner +{ + public Action Callback; + public int Interval; + + public PollRunner(int interval, Action callback) + { + Interval = interval; + Callback = callback; + } + + public async void Run() + { + while (true) + { + await Task.Delay(Interval); + Callback(); + } + } +} + +// public class RegexTrigger +// { +// public string Pattern; +// public Action Callback; + +// public RegexTrigger(string pattern, Action callback) +// { +// Pattern = pattern; +// Callback = callback; +// } + +// public bool Run(PRIVMSG privmsg) +// { +// } +// } \ No newline at end of file diff --git a/bots/Cube.cs b/bots/Cube.cs new file mode 100644 index 0000000..d72b2f9 --- /dev/null +++ b/bots/Cube.cs @@ -0,0 +1,26 @@ +using riff; + +public class Cube : IRCBot +{ + public Cube(string host, int port, string nick, string realname) : base(host, port, nick, realname) + { + + Channels = [ + "#bots" + ]; + + (string, Action)[] commands = [ + ("echo", Echo) + ]; + + HookCommands(commands); + JoinAllChannels(); + } + + private void Echo(PRIVMSG privmsg) + { + SendPrivmsg(privmsg.Sender, privmsg.Body); + } + + +} \ No newline at end of file