538 lines
17 KiB
C#
538 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Runtime.Serialization;
|
|
using IrcTokens;
|
|
|
|
namespace IrcStates
|
|
{
|
|
public class Server
|
|
{
|
|
public const string WhoType = "525"; // randomly generated
|
|
private readonly StatefulDecoder _decoder;
|
|
|
|
private Dictionary<string, string> TempCaps;
|
|
|
|
public Server(string name)
|
|
{
|
|
Name = name;
|
|
Registered = false;
|
|
Modes = new List<string>();
|
|
Motd = new List<string>();
|
|
_decoder = new StatefulDecoder();
|
|
Users = new Dictionary<string, User>();
|
|
Channels = new Dictionary<string, Channel>();
|
|
ISupport = new ISupport();
|
|
HasCap = false;
|
|
TempCaps = new Dictionary<string, string>();
|
|
AvailableCaps = new Dictionary<string, string>();
|
|
AgreedCaps = new List<string>();
|
|
}
|
|
|
|
public string Name { get; set; }
|
|
public string NickName { get; set; }
|
|
public string NickNameLower { get; set; }
|
|
public string UserName { get; set; }
|
|
public string HostName { get; set; }
|
|
public string RealName { get; set; }
|
|
public string Account { get; set; }
|
|
public string Away { get; set; }
|
|
|
|
public bool Registered { get; set; }
|
|
public List<string> Modes { get; set; }
|
|
public List<string> Motd { get; set; }
|
|
public Dictionary<string, User> Users { get; set; }
|
|
public Dictionary<string, Channel> Channels { get; set; }
|
|
public Dictionary<string, string> AvailableCaps { get; set; }
|
|
public List<string> AgreedCaps { get; set; }
|
|
|
|
public ISupport ISupport { get; set; }
|
|
public bool HasCap { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"Server(name={Name})";
|
|
}
|
|
|
|
public string CaseFold(string str)
|
|
{
|
|
return Casemap.CaseFold(ISupport.CaseMapping, str);
|
|
}
|
|
|
|
public bool CaseFoldEquals(string s1, string s2)
|
|
{
|
|
return CaseFold(s1) == CaseFold(s2);
|
|
}
|
|
|
|
public bool IsMe(string nickname)
|
|
{
|
|
return CaseFold(nickname) == NickNameLower;
|
|
}
|
|
|
|
public bool HasUser(string nickname)
|
|
{
|
|
return Users.ContainsKey(CaseFold(nickname));
|
|
}
|
|
|
|
private void AddUser(string nickname, string nicknameLower)
|
|
{
|
|
var user = CreateUser(nickname, nicknameLower);
|
|
Users[nicknameLower] = user;
|
|
}
|
|
|
|
private User CreateUser(string nickname, string nicknameLower)
|
|
{
|
|
var user = new User();
|
|
user.SetNickName(nickname, nicknameLower);
|
|
return user;
|
|
}
|
|
|
|
public bool IsChannel(string target)
|
|
{
|
|
return !string.IsNullOrEmpty(target) &&
|
|
ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
public bool HasChannel(string name)
|
|
{
|
|
return Channels.ContainsKey(CaseFold(name));
|
|
}
|
|
|
|
public Channel GetChannel(string name)
|
|
{
|
|
return HasChannel(name) ? Channels[name] : null;
|
|
}
|
|
|
|
private ChannelUser UserJoin(Channel channel, User user)
|
|
{
|
|
var channelUser = new ChannelUser();
|
|
user.Channels.Add(CaseFold(channel.Name));
|
|
channel.Users[user.NickNameLower] = channelUser;
|
|
return channelUser;
|
|
}
|
|
|
|
private void SelfHostmask(Hostmask hostmask)
|
|
{
|
|
NickName = hostmask.NickName;
|
|
if (hostmask.UserName != null) UserName = hostmask.UserName;
|
|
if (hostmask.HostName != null) HostName = hostmask.HostName;
|
|
}
|
|
|
|
private (Emit, User) UserPart(Line line, string nickName, string channelName, int reasonIndex)
|
|
{
|
|
var emit = new Emit();
|
|
var channelLower = CaseFold(channelName);
|
|
if (line.Params.Count >= reasonIndex + 1) emit.Text = line.Params[reasonIndex];
|
|
|
|
User user = null;
|
|
if (HasChannel(channelName))
|
|
{
|
|
var channel = Channels[channelLower];
|
|
emit.Channel = channel;
|
|
var nickLower = CaseFold(nickName);
|
|
if (HasUser(nickLower))
|
|
{
|
|
user = Users[nickLower];
|
|
user.Channels.Remove(channelLower);
|
|
channel.Users.Remove(nickLower);
|
|
if (!user.Channels.Any()) Users.Remove(nickLower);
|
|
}
|
|
|
|
if (nickLower == NickNameLower)
|
|
{
|
|
Channels.Remove(channelLower);
|
|
foreach (var userToRemove in channel.Users.Keys.Select(u => Users[u]))
|
|
{
|
|
userToRemove.Channels.Remove(channelLower);
|
|
if (!userToRemove.Channels.Any()) Users.Remove(userToRemove.NickNameLower);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (emit, user);
|
|
}
|
|
|
|
public List<(Line, Emit)> Recv(byte[] data)
|
|
{
|
|
if (data == null) return null;
|
|
|
|
var lines = _decoder.Push(data, data.Length);
|
|
if (lines == null) throw new ServerDisconnectedException();
|
|
|
|
return lines.Select(l => (l, Parse(l))).ToList();
|
|
}
|
|
|
|
public Emit Parse(Line line)
|
|
{
|
|
if (line == null) return null;
|
|
|
|
switch (line.Command)
|
|
{
|
|
case Numeric.RPL_WELCOME: return HandleWelcome(line);
|
|
case Numeric.RPL_ISUPPORT: return HandleISupport(line);
|
|
case Numeric.RPL_MOTDSTART:
|
|
case Numeric.RPL_MOTD:
|
|
return HandleMotd(line);
|
|
case Commands.Nick: return HandleNick(line);
|
|
case Commands.Join: return HandleJoin(line);
|
|
case Commands.Part: return HandlePart(line);
|
|
case Commands.Kick: return HandleKick(line);
|
|
case Commands.Quit: return HandleQuit(line);
|
|
case Commands.Error: return HandleError(line);
|
|
case Numeric.RPL_NAMREPLY: return HandleNames(line);
|
|
case Numeric.RPL_CREATIONTIME: return HandleCreationTime(line);
|
|
case Commands.Topic: return HandleTopic(line);
|
|
case Numeric.RPL_TOPIC: return HandleTopicNumeric(line);
|
|
case Numeric.RPL_TOPICWHOTIME: return HandleTopicTime(line);
|
|
case Commands.Mode: return HandleMode(line);
|
|
case Numeric.RPL_CHANNELMODEIS: return HandleChannelModeIs(line);
|
|
case Numeric.RPL_UMODEIS: return HandleUModeIs(line);
|
|
case Commands.Privmsg:
|
|
case Commands.Notice:
|
|
case Commands.Tagmsg:
|
|
return HandleMessage(line);
|
|
case Numeric.RPL_VISIBLEHOST: return HandleVisibleHost(line);
|
|
case Numeric.RPL_WHOREPLY: return HandleWhoReply(line);
|
|
case Numeric.RPL_WHOSPCRPL: return HandleWhox(line);
|
|
case Numeric.RPL_WHOISUSER: return HandleWhoIsUser(line);
|
|
case Commands.Chghost: return HandleChghost(line);
|
|
case Commands.Setname: return HandleSetname(line);
|
|
case Commands.Away: return HandleAway(line);
|
|
case Commands.Account: return HandleAccount(line);
|
|
case Commands.Cap: return HandleCap(line);
|
|
case Numeric.RPL_LOGGEDIN: return HandleLoggedIn(line);
|
|
case Numeric.RPL_LOGGEDOUT: return HandleLoggedOut(line);
|
|
}
|
|
|
|
return new Emit();
|
|
}
|
|
|
|
private Emit HandleSetname(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleAway(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleAccount(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleCap(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleLoggedIn(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleChghost(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleWhoIsUser(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleWhox(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleWhoReply(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleVisibleHost(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleMessage(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleUModeIs(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleChannelModeIs(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleMode(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleTopicTime(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleTopicNumeric(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleTopic(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleCreationTime(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleNames(Line line)
|
|
{
|
|
var emit = new Emit();
|
|
var channelLower = CaseFold(line.Params[2]);
|
|
|
|
if (!Channels.ContainsKey(channelLower)) return emit;
|
|
var channel = Channels[channelLower];
|
|
emit.Channel = channel;
|
|
var nicknames = line.Params[3].Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
var users = new List<User>();
|
|
emit.Users = users;
|
|
|
|
foreach (var nick in nicknames)
|
|
{
|
|
var modes = "";
|
|
foreach (var c in nick)
|
|
{
|
|
var mode = ISupport.Prefix.FromPrefix(c);
|
|
if (mode != null)
|
|
modes += mode;
|
|
else
|
|
break;
|
|
}
|
|
|
|
var hostmask = new Hostmask(nick.Substring(modes.Length));
|
|
var nickLower = CaseFold(hostmask.NickName);
|
|
if (!Users.ContainsKey(nickLower))
|
|
{
|
|
AddUser(hostmask.NickName, nickLower);
|
|
}
|
|
|
|
var user = Users[nickLower];
|
|
users.Add(user);
|
|
var channelUser = UserJoin(channel, user);
|
|
|
|
if (hostmask.UserName != null) user.UserName = hostmask.UserName;
|
|
if (hostmask.HostName != null) user.HostName = hostmask.HostName;
|
|
|
|
if (nickLower == NickNameLower) SelfHostmask(hostmask);
|
|
|
|
foreach (var mode in modes.Select(c => c.ToString(CultureInfo.InvariantCulture)))
|
|
{
|
|
if (!channelUser.Modes.Contains(mode))
|
|
{
|
|
channelUser.Modes.Add(mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return emit;
|
|
}
|
|
|
|
private Emit HandleError(Line line)
|
|
{
|
|
Users.Clear();
|
|
Channels.Clear();
|
|
return new Emit();
|
|
}
|
|
|
|
private Emit HandleQuit(Line line)
|
|
{
|
|
var emit = new Emit();
|
|
var nickLower = CaseFold(line.Hostmask.NickName);
|
|
if (line.Params.Any()) emit.Text = line.Params[0];
|
|
|
|
if (nickLower == NickNameLower || line.Source == null)
|
|
{
|
|
emit.Self = true;
|
|
Users.Clear();
|
|
Channels.Clear();
|
|
}
|
|
else if (Users.ContainsKey(nickLower))
|
|
{
|
|
var user = Users[nickLower];
|
|
Users.Remove(nickLower);
|
|
emit.User = user;
|
|
foreach (var channel in user.Channels.Select(c => Channels[c]))
|
|
{
|
|
channel.Users.Remove(user.NickNameLower);
|
|
}
|
|
}
|
|
|
|
return emit;
|
|
}
|
|
|
|
private Emit HandleLoggedOut(Line line)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Emit HandleKick(Line line)
|
|
{
|
|
var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2);
|
|
if (kicked != null)
|
|
{
|
|
emit.UserTarget = kicked;
|
|
if (kicked.NickNameLower == NickNameLower) emit.Self = true;
|
|
|
|
var kickerLower = CaseFold(line.Hostmask.NickName);
|
|
if (kickerLower == NickNameLower) emit.SelfSource = true;
|
|
|
|
emit.UserSource = Users.ContainsKey(kickerLower)
|
|
? Users[kickerLower]
|
|
: CreateUser(line.Hostmask.NickName, kickerLower);
|
|
}
|
|
|
|
return emit;
|
|
}
|
|
|
|
private Emit HandlePart(Line line)
|
|
{
|
|
var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1);
|
|
if (user != null)
|
|
{
|
|
emit.User = user;
|
|
if (user.NickNameLower == NickNameLower) emit.Self = true;
|
|
}
|
|
|
|
return emit;
|
|
}
|
|
|
|
|
|
private Emit HandleJoin(Line line)
|
|
{
|
|
var extended = line.Params.Count == 3;
|
|
var account = extended ? line.Params[1].Trim('*') : null;
|
|
var realname = extended ? line.Params[2] : null;
|
|
var emit = new Emit();
|
|
|
|
var channelLower = CaseFold(line.Params[0]);
|
|
var nickLower = CaseFold(line.Hostmask.NickName);
|
|
|
|
// handle own join
|
|
if (nickLower == NickNameLower)
|
|
{
|
|
emit.Self = true;
|
|
if (!HasChannel(channelLower))
|
|
{
|
|
var channel = new Channel();
|
|
channel.SetName(line.Params[0], channelLower);
|
|
Channels[channelLower] = channel;
|
|
}
|
|
|
|
SelfHostmask(line.Hostmask);
|
|
if (extended)
|
|
{
|
|
Account = account;
|
|
RealName = realname;
|
|
}
|
|
}
|
|
|
|
if (HasChannel(channelLower))
|
|
{
|
|
var channel = Channels[channelLower];
|
|
emit.Channel = channel;
|
|
|
|
if (!HasUser(nickLower)) AddUser(line.Hostmask.NickName, nickLower);
|
|
|
|
var user = Users[nickLower];
|
|
emit.User = user;
|
|
if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName;
|
|
if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName;
|
|
if (extended)
|
|
{
|
|
user.Account = account;
|
|
user.RealName = realname;
|
|
}
|
|
|
|
UserJoin(channel, user);
|
|
}
|
|
|
|
return emit;
|
|
}
|
|
|
|
|
|
private Emit HandleNick(Line line)
|
|
{
|
|
var nick = line.Params[0];
|
|
var nickLower = CaseFold(line.Hostmask.NickName);
|
|
|
|
var emit = new Emit();
|
|
|
|
if (Users.ContainsKey(nickLower))
|
|
{
|
|
var user = Users[nickLower];
|
|
Users.Remove(nickLower);
|
|
emit.User = user;
|
|
|
|
var oldNickLower = user.NickNameLower;
|
|
var newNickLower = CaseFold(nick);
|
|
user.SetNickName(nick, newNickLower);
|
|
Users[newNickLower] = user;
|
|
foreach (var channelLower in user.Channels)
|
|
{
|
|
var channel = Channels[channelLower];
|
|
var channelUser = channel.Users[oldNickLower];
|
|
channel.Users.Remove(oldNickLower);
|
|
channel.Users[newNickLower] = channelUser;
|
|
}
|
|
}
|
|
|
|
if (nickLower == NickNameLower)
|
|
{
|
|
emit.Self = true;
|
|
NickName = nick;
|
|
NickNameLower = CaseFold(nick);
|
|
}
|
|
|
|
return emit;
|
|
}
|
|
|
|
private Emit HandleMotd(Line line)
|
|
{
|
|
if (line.Command == Numeric.RPL_MOTDSTART) Motd.Clear();
|
|
|
|
var emit = new Emit {Text = line.Params[1]};
|
|
Motd.Add(line.Params[1]);
|
|
return emit;
|
|
}
|
|
|
|
private Emit HandleISupport(Line line)
|
|
{
|
|
ISupport = new ISupport();
|
|
ISupport.Parse(line.Params);
|
|
return new Emit();
|
|
}
|
|
|
|
|
|
private Emit HandleWelcome(Line line)
|
|
{
|
|
NickName = line.Params[0];
|
|
NickNameLower = CaseFold(line.Params[0]);
|
|
Registered = true;
|
|
return new Emit();
|
|
}
|
|
}
|
|
}
|