riff/IRCBot.cs

181 lines
5.1 KiB
C#

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<string> JoinedChannels = new();
public List<string> PermanentChannels;
public List<string> UserChannels;
public string UserChannelStoragePath;
public List<Action<PRIVMSG>> Listeners = new();
private List<CommandRunner> CommandRunners = new();
private List<PollRunner> PollRunners = new();
private TcpClient Client;
private NetworkStream Stream;
public IRCBot(string host, int port, string nick, string realname, List<string> 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<PRIVMSG>)[] commands)
{
foreach ((string, Action<PRIVMSG>) 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<PRIVMSG>[] listeners)
{
foreach (Action<PRIVMSG> 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<string> channels)
{
foreach (string channel in channels)
JoinChannel(channel);
}
public void PartChannel(string channel)
{
SendLine($"PART {channel}");
JoinedChannels.Remove(channel);
}
public List<string> LoadUserChannels()
{
List<string> channels;
try
{
using (FileStream stream = File.OpenRead(UserChannelStoragePath))
{
channels = JsonSerializer.Deserialize<List<string>>(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<PRIVMSG> listener in Listeners)
listener(privmsg);
foreach (CommandRunner command in CommandRunners)
if (command.Run(privmsg))
break;
break;
}
}
}
}