181 lines
5.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|