···11namespace IRCSharp.Tests.State;
2233-[TestClass]
43public class Motd
54{
66- [TestMethod]
77- public void MessageOfTheDay()
55+ [Test]
66+ public async Task MessageOfTheDay()
87 {
98 var server = new Server("test");
1010- server.Parse(new("001 nickname"));
1111- server.Parse(new("375 * :start of motd"));
1212- server.Parse(new("372 * :first line of motd"));
1313- server.Parse(new("372 * :second line of motd"));
99+ server.Parse(new Line("001 nickname"));
1010+ server.Parse(new Line("375 * :start of motd"));
1111+ server.Parse(new Line("372 * :first line of motd"));
1212+ server.Parse(new Line("372 * :second line of motd"));
14131515- CollectionAssert.AreEqual(new List<string> {"start of motd", "first line of motd", "second line of motd"},
1616- server.Motd);
1414+ await Assert.That(server.Motd).IsEquivalentTo(["start of motd", "first line of motd", "second line of motd"]);
1715 }
1818-}1616+}
···11-using System;
22-using System.Collections.Generic;
11+using IRCTokens;
32using System.Globalization;
44-using System.Linq;
5364// ReSharper disable InconsistentNaming
75// ReSharper disable StringLiteralTypo
8699-namespace IRCStates
77+namespace IRCStates;
88+99+public class ISupport
1010{
1111- public class ISupport
1111+ public Dictionary<string, string> Raw { get; } = [];
1212+ public string Network { get; private set; }
1313+ public ISupportChanModes ChanModes { get; private set; } = new("b,k,l,imnpst");
1414+ public ISupportPrefix Prefix { get; private set; } = new("(ov)@+");
1515+ public int? Modes { get; private set; } = 3;
1616+ public Casemap.CaseMapping CaseMapping { get; private set; } = Casemap.CaseMapping.Rfc1459;
1717+ public List<string> ChanTypes { get; private set; } = ["#"];
1818+ public List<string> StatusMsg { get; private set; } = [];
1919+ public string CallerId { get; private set; }
2020+ public string Excepts { get; private set; }
2121+ public string Invex { get; private set; }
2222+ public int? Monitor { get; private set; }
2323+ public int? Watch { get; private set; }
2424+ public bool Whox { get; private set; }
2525+2626+ /// <summary>
2727+ /// Parse the ISupport values from the line's parameters
2828+ /// </summary>
2929+ /// <param name="tokens"></param>
3030+ public void Parse(IEnumerable<string> tokens)
1231 {
1313- public Dictionary<string, string> Raw { get; } = new Dictionary<string, string>();
1414- public string Network { get; private set; }
1515- public ISupportChanModes ChanModes { get; private set; } = new ISupportChanModes("b,k,l,imnpst");
1616- public ISupportPrefix Prefix { get; private set; } = new ISupportPrefix("(ov)@+");
1717- public int? Modes { get; private set; } = 3;
1818- public Casemap.CaseMapping CaseMapping { get; private set; } = Casemap.CaseMapping.Rfc1459;
1919- public List<string> ChanTypes { get; private set; } = new List<string> { "#" };
2020- public List<string> StatusMsg { get; private set; } = new List<string>();
2121- public string CallerId { get; private set; }
2222- public string Excepts { get; private set; }
2323- public string Invex { get; private set; }
2424- public int? Monitor { get; private set; }
2525- public int? Watch { get; private set; }
2626- public bool Whox { get; private set; }
3232+ if (tokens == null)
3333+ {
3434+ return;
3535+ }
27362828- /// <summary>
2929- /// Parse the ISupport values from the line's parameters
3030- /// </summary>
3131- /// <param name="tokens"></param>
3232- public void Parse(IEnumerable<string> tokens)
3737+ // remove first and last
3838+ tokens = tokens.Skip(1).SkipLast(1);
3939+4040+ foreach (var token in tokens)
3341 {
3434- if (tokens == null) return;
4242+ var split = token.Split(['='], 2);
4343+ var key = split[0];
35443636- // remove first and last
3737- tokens = tokens.Skip(1).SkipLast(1);
4545+ var value = string.Empty;
4646+ if (split.Length > 1)
4747+ {
4848+ value = split[1];
4949+ Raw[key] = value;
5050+ }
38513939- foreach (var token in tokens)
5252+ switch (split[0])
4053 {
4141- var split = token.Split('=', 2);
4242- var key = split[0];
5454+ case "NETWORK": Network = value; break;
5555+ case "CHANMODES": ChanModes = new ISupportChanModes(value); break;
5656+ case "PREFIX": Prefix = new ISupportPrefix(value); break;
5757+ case "STATUSMSG":
5858+ StatusMsg = [];
5959+ StatusMsg.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture)));
6060+ break;
6161+ case "MODES":
6262+ if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var modes))
6363+ {
6464+ Modes = modes;
6565+ }
6666+ else
6767+ {
6868+ Modes = -1;
6969+ }
7070+7171+ break;
7272+ case "MONITOR":
7373+ if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var monitor))
7474+ {
7575+ Monitor = monitor;
7676+ }
7777+ else
7878+ {
7979+ Monitor = -1;
8080+ }
43814444- var value = string.Empty;
4545- if (split.Length > 1)
4646- {
4747- value = split[1];
4848- Raw[key] = value;
4949- }
8282+ break;
8383+ case "WATCH":
8484+ if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var watch))
8585+ {
8686+ Watch = watch;
8787+ }
8888+ else
8989+ {
9090+ Watch = -1;
9191+ }
50925151- switch (split[0])
5252- {
5353- case "NETWORK":
5454- Network = value;
5555- break;
5656- case "CHANMODES":
5757- ChanModes = new ISupportChanModes(value);
5858- break;
5959- case "PREFIX":
6060- Prefix = new ISupportPrefix(value);
6161- break;
6262- case "STATUSMSG":
6363- StatusMsg = new List<string>();
6464- StatusMsg.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture)));
6565- break;
6666- case "MODES":
6767- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var modes))
6868- Modes = modes;
6969- else
7070- Modes = -1;
7171- break;
7272- case "MONITOR":
7373- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var monitor))
7474- Monitor = monitor;
7575- else
7676- Monitor = -1;
7777- break;
7878- case "WATCH":
7979- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var watch))
8080- Watch = watch;
8181- else
8282- Watch = -1;
8383- break;
8484- case "CASEMAPPING":
8585- if (Enum.TryParse(value, true, out Casemap.CaseMapping caseMapping)) CaseMapping = caseMapping;
8686- break;
8787- case "CHANTYPES":
8888- ChanTypes = new List<string>();
8989- ChanTypes.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture)));
9090- break;
9191- case "CALLERID":
9292- CallerId = string.IsNullOrEmpty(value) ? "g" : value;
9393- break;
9494- case "EXCEPTS":
9595- Excepts = string.IsNullOrEmpty(value) ? "e" : value;
9696- break;
9797- case "INVEX":
9898- Invex = string.IsNullOrEmpty(value) ? "I" : value;
9999- break;
100100- case "WHOX":
101101- Whox = true;
102102- break;
103103- }
9393+ break;
9494+ case "CASEMAPPING":
9595+ if (Enum.TryParse(value, true, out Casemap.CaseMapping caseMapping))
9696+ {
9797+ CaseMapping = caseMapping;
9898+ }
9999+100100+ break;
101101+ case "CHANTYPES":
102102+ ChanTypes = [];
103103+ ChanTypes.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture)));
104104+ break;
105105+ case "CALLERID": CallerId = string.IsNullOrEmpty(value) ? "g" : value; break;
106106+ case "EXCEPTS": Excepts = string.IsNullOrEmpty(value) ? "e" : value; break;
107107+ case "INVEX": Invex = string.IsNullOrEmpty(value) ? "I" : value; break;
108108+ case "WHOX": Whox = true; break;
104109 }
105110 }
106111 }
+22-29
IRCStates/ISupportChanModes.cs
···11-using System.Collections.Generic;
21using System.Globalization;
33-using System.Linq;
22+43// ReSharper disable CommentTypo
5466-namespace IRCStates
55+namespace IRCStates;
66+77+// ReSharper disable once InconsistentNaming
88+public class ISupportChanModes
79{
88- // ReSharper disable once InconsistentNaming
99- public class ISupportChanModes
1010+ /// <summary>
1111+ /// Split the chanmodes and add to our known <see cref="ListModes"/>
1212+ /// </summary>
1313+ /// <param name="splitVal"></param>
1414+ public ISupportChanModes(string splitVal)
1015 {
1111- /// <summary>
1212- /// Split the chanmodes and add to our known <see cref="ListModes"/>
1313- /// </summary>
1414- /// <param name="splitVal"></param>
1515- public ISupportChanModes(string splitVal)
1616+ if (splitVal == null)
1617 {
1717- if (splitVal == null) return;
1818-1919- var split = splitVal.Split(',', 4);
2020-2121- ListModes = new List<string>();
2222- ListModes.AddRange(split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)));
2323-2424- SettingBModes = new List<string>();
2525- SettingBModes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)));
2626-2727- SettingCModes = new List<string>();
2828- SettingCModes.AddRange(split[2].Select(c => c.ToString(CultureInfo.InvariantCulture)));
2929-3030- SettingDModes = new List<string>();
3131- SettingDModes.AddRange(split[3].Select(c => c.ToString(CultureInfo.InvariantCulture)));
1818+ return;
3219 }
33203434- public List<string> ListModes { get; }
3535- public List<string> SettingBModes { get; }
3636- public List<string> SettingCModes { get; }
3737- public List<string> SettingDModes { get; }
2121+ var split = splitVal.Split([','], 4);
2222+ ListModes = split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList();
2323+ SettingBModes = split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList();
2424+ SettingCModes = split[2].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList();
2525+ SettingDModes = split[3].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList();
3826 }
2727+2828+ public List<string> ListModes { get; }
2929+ public List<string> SettingBModes { get; }
3030+ public List<string> SettingCModes { get; }
3131+ public List<string> SettingDModes { get; }
3932}
+28-39
IRCStates/ISupportPrefix.cs
···11-using System;
22-using System.Collections.Generic;
31using System.Globalization;
44-using System.Linq;
22+33+namespace IRCStates;
5466-namespace IRCStates
55+// ReSharper disable once InconsistentNaming
66+public class ISupportPrefix
77{
88- // ReSharper disable once InconsistentNaming
99- public class ISupportPrefix
88+ /// <summary>
99+ /// Split the prefix value and add them to our known <see cref="Modes"/> and <see cref="Prefixes"/>
1010+ /// </summary>
1111+ /// <param name="splitVal"></param>
1212+ /// <exception cref="ArgumentNullException"></exception>
1313+ public ISupportPrefix(string splitVal)
1014 {
1111- /// <summary>
1212- /// Split the prefix value and add them to our known <see cref="Modes"/> and <see cref="Prefixes"/>
1313- /// </summary>
1414- /// <param name="splitVal"></param>
1515- /// <exception cref="ArgumentNullException"></exception>
1616- public ISupportPrefix(string splitVal)
1515+ if (splitVal == null)
1716 {
1818- if (splitVal == null) throw new ArgumentNullException(nameof(splitVal));
1717+ throw new ArgumentNullException(nameof(splitVal));
1818+ }
19192020- var split = splitVal[1..].Split(')', 2);
2121- Modes = new List<string>();
2222- Modes.AddRange(split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)));
2323- Prefixes = new List<string>();
2424- Prefixes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)));
2525- }
2020+ var split = splitVal.Substring(1).Split([')'], 2);
2121+ Modes = split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList();
2222+ Prefixes = split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList();
2323+ }
26242727- public List<string> Modes { get; }
2828- public List<string> Prefixes { get; }
2525+ public List<string> Modes { get; }
2626+ public List<string> Prefixes { get; }
29273030- // ReSharper disable once UnusedMember.Global
3131- public string FromMode(char mode)
3232- {
3333- return FromMode(mode.ToString(CultureInfo.InvariantCulture));
3434- }
2828+ // ReSharper disable once UnusedMember.Global
2929+ public string FromMode(char mode) =>
3030+ FromMode(mode.ToString(CultureInfo.InvariantCulture));
35313636- public string FromMode(string mode)
3737- {
3838- return Modes.Contains(mode) ? Prefixes[Modes.IndexOf(mode)] : null;
3939- }
3232+ public string FromMode(string mode) =>
3333+ Modes.Contains(mode) ? Prefixes[Modes.IndexOf(mode)] : null;
40344141- public string FromPrefix(char prefix)
4242- {
4343- return FromPrefix(prefix.ToString(CultureInfo.InvariantCulture));
4444- }
3535+ public string FromPrefix(char prefix) =>
3636+ FromPrefix(prefix.ToString(CultureInfo.InvariantCulture));
45374646- public string FromPrefix(string prefix)
4747- {
4848- return Prefixes.Contains(prefix) ? Modes[Prefixes.IndexOf(prefix)] : null;
4949- }
5050- }
3838+ public string FromPrefix(string prefix) =>
3939+ Prefixes.Contains(prefix) ? Modes[Prefixes.IndexOf(prefix)] : null;
5140}
+76-76
IRCStates/Numeric.cs
···11-// ReSharper disable InconsistentNaming
11+// ReSharper disable InconsistentNaming
22// ReSharper disable UnusedMember.Global
33// ReSharper disable IdentifierTypo
44-namespace IRCStates
44+55+namespace IRCStates;
66+77+/// <summary>
88+/// Known numeric response codes
99+/// </summary>
1010+public static class Numeric
511{
66- /// <summary>
77- /// Known numeric response codes
88- /// </summary>
99- public static class Numeric
1010- {
1112#pragma warning disable CA1707 // Identifiers should not contain underscores
1212- public const string RPL_WELCOME = "001";
1313- public const string RPL_ISUPPORT = "005";
1414- public const string RPL_MOTD = "372";
1515- public const string RPL_MOTDSTART = "375";
1616- public const string RPL_ENDOFMOTD = "376";
1717- public const string ERR_NOMOTD = "422";
1818- public const string RPL_UMODEIS = "221";
1919- public const string RPL_VISIBLEHOST = "396";
2020- public const string RPL_TRYAGAIN = "263";
2121- public const string RPL_YOUREOPER = "381";
1313+ public const string RPL_WELCOME = "001";
1414+ public const string RPL_ISUPPORT = "005";
1515+ public const string RPL_MOTD = "372";
1616+ public const string RPL_MOTDSTART = "375";
1717+ public const string RPL_ENDOFMOTD = "376";
1818+ public const string ERR_NOMOTD = "422";
1919+ public const string RPL_UMODEIS = "221";
2020+ public const string RPL_VISIBLEHOST = "396";
2121+ public const string RPL_TRYAGAIN = "263";
2222+ public const string RPL_YOUREOPER = "381";
22232323- public const string ERR_NOSUCHNICK = "401";
2424- public const string ERR_NOSUCHSERVER = "402";
2424+ public const string ERR_NOSUCHNICK = "401";
2525+ public const string ERR_NOSUCHSERVER = "402";
25262626- public const string RPL_CHANNELMODEIS = "324";
2727- public const string RPL_CREATIONTIME = "329";
2828- public const string RPL_TOPIC = "332";
2929- public const string RPL_TOPICWHOTIME = "333";
2727+ public const string RPL_CHANNELMODEIS = "324";
2828+ public const string RPL_CREATIONTIME = "329";
2929+ public const string RPL_TOPIC = "332";
3030+ public const string RPL_TOPICWHOTIME = "333";
30313131- public const string RPL_WHOREPLY = "352";
3232- public const string RPL_WHOSPCRPL = "354";
3333- public const string RPL_ENDOFWHO = "315";
3434- public const string RPL_NAMREPLY = "353";
3535- public const string RPL_ENDOFNAMES = "366";
3232+ public const string RPL_WHOREPLY = "352";
3333+ public const string RPL_WHOSPCRPL = "354";
3434+ public const string RPL_ENDOFWHO = "315";
3535+ public const string RPL_NAMREPLY = "353";
3636+ public const string RPL_ENDOFNAMES = "366";
36373737- public const string RPL_WHOWASUSER = "314";
3838- public const string RPL_ENDOFWHOWAS = "369";
3838+ public const string RPL_WHOWASUSER = "314";
3939+ public const string RPL_ENDOFWHOWAS = "369";
39404040- public const string RPL_BANLIST = "367";
4141- public const string RPL_ENDOFBANLIST = "368";
4242- public const string RPL_QUIETLIST = "728";
4343- public const string RPL_ENDOFQUIETLIST = "729";
4141+ public const string RPL_BANLIST = "367";
4242+ public const string RPL_ENDOFBANLIST = "368";
4343+ public const string RPL_QUIETLIST = "728";
4444+ public const string RPL_ENDOFQUIETLIST = "729";
44454545- public const string RPL_LOGGEDIN = "900";
4646- public const string RPL_LOGGEDOUT = "901";
4747- public const string RPL_SASLSUCCESS = "903";
4848- public const string ERR_SASLFAIL = "904";
4949- public const string ERR_SASLTOOLONG = "905";
5050- public const string ERR_SASLABORTED = "906";
5151- public const string ERR_SASLALREADY = "907";
5252- public const string RPL_SASLMECHS = "908";
4646+ public const string RPL_LOGGEDIN = "900";
4747+ public const string RPL_LOGGEDOUT = "901";
4848+ public const string RPL_SASLSUCCESS = "903";
4949+ public const string ERR_SASLFAIL = "904";
5050+ public const string ERR_SASLTOOLONG = "905";
5151+ public const string ERR_SASLABORTED = "906";
5252+ public const string ERR_SASLALREADY = "907";
5353+ public const string RPL_SASLMECHS = "908";
53545454- public const string RPL_WHOISUSER = "311";
5555- public const string RPL_WHOISSERVER = "312";
5656- public const string RPL_WHOISOPERATOR = "313";
5757- public const string RPL_WHOISIDLE = "317";
5858- public const string RPL_WHOISCHANNELS = "319";
5959- public const string RPL_WHOISACCOUNT = "330";
6060- public const string RPL_WHOISHOST = "378";
6161- public const string RPL_WHOISMODES = "379";
6262- public const string RPL_WHOISSECURE = "671";
6363- public const string RPL_AWAY = "301";
6464- public const string RPL_ENDOFWHOIS = "318";
5555+ public const string RPL_WHOISUSER = "311";
5656+ public const string RPL_WHOISSERVER = "312";
5757+ public const string RPL_WHOISOPERATOR = "313";
5858+ public const string RPL_WHOISIDLE = "317";
5959+ public const string RPL_WHOISCHANNELS = "319";
6060+ public const string RPL_WHOISACCOUNT = "330";
6161+ public const string RPL_WHOISHOST = "378";
6262+ public const string RPL_WHOISMODES = "379";
6363+ public const string RPL_WHOISSECURE = "671";
6464+ public const string RPL_AWAY = "301";
6565+ public const string RPL_ENDOFWHOIS = "318";
65666666- public const string ERR_ERRONEUSNICKNAME = "432";
6767- public const string ERR_NICKNAMEINUSE = "433";
6868- public const string ERR_BANNICKCHANGE = "435";
6969- public const string ERR_UNAVAILRESOURCE = "437";
7070- public const string ERR_NICKTOOFAST = "438";
7171- public const string ERR_CANTCHANGENICK = "447";
6767+ public const string ERR_ERRONEUSNICKNAME = "432";
6868+ public const string ERR_NICKNAMEINUSE = "433";
6969+ public const string ERR_BANNICKCHANGE = "435";
7070+ public const string ERR_UNAVAILRESOURCE = "437";
7171+ public const string ERR_NICKTOOFAST = "438";
7272+ public const string ERR_CANTCHANGENICK = "447";
72737373- public const string ERR_NOSUCHCHANNEL = "403";
7474- public const string ERR_TOOMANYCHANNELS = "405";
7575- public const string ERR_USERONCHANNEL = "443";
7676- public const string ERR_LINKCHANNEL = "470";
7777- public const string ERR_BADCHANNAME = "479";
7878- public const string ERR_BADCHANNEL = "926";
7474+ public const string ERR_NOSUCHCHANNEL = "403";
7575+ public const string ERR_TOOMANYCHANNELS = "405";
7676+ public const string ERR_USERONCHANNEL = "443";
7777+ public const string ERR_LINKCHANNEL = "470";
7878+ public const string ERR_BADCHANNAME = "479";
7979+ public const string ERR_BADCHANNEL = "926";
79808080- public const string ERR_BANNEDFROMCHAN = "474";
8181- public const string ERR_INVITEONLYCHAN = "473";
8282- public const string ERR_BADCHANNELKEY = "475";
8383- public const string ERR_CHANNELISFULL = "471";
8484- public const string ERR_NEEDREGGEDNICK = "477";
8585- public const string ERR_THROTTLE = "480";
8181+ public const string ERR_BANNEDFROMCHAN = "474";
8282+ public const string ERR_INVITEONLYCHAN = "473";
8383+ public const string ERR_BADCHANNELKEY = "475";
8484+ public const string ERR_CHANNELISFULL = "471";
8585+ public const string ERR_NEEDREGGEDNICK = "477";
8686+ public const string ERR_THROTTLE = "480";
86878787- public const string RPL_LOGOFF = "601";
8888- public const string RPL_MONOFFLINE = "731";
8888+ public const string RPL_LOGOFF = "601";
8989+ public const string RPL_MONOFFLINE = "731";
89909090- public const string RPL_RSACHALLENGE2 = "740";
9191- public const string RPL_ENDOFRSACHALLENGE2 = "741";
9191+ public const string RPL_RSACHALLENGE2 = "740";
9292+ public const string RPL_ENDOFRSACHALLENGE2 = "741";
9293#pragma warning restore CA1707 // Identifiers should not contain underscores
9393- }
9494}
+1-1
IRCStates/README.md
···11-# IRCStates
11+# IRCStates
2233port of [jesopo/ircstates](https://github.com/jesopo/ircstates)
44
+944-898
IRCStates/Server.cs
···11-using System;
22-using System.Collections.Generic;
31using System.Globalization;
44-using System.Linq;
52using IRCTokens;
33+// ReSharper disable InvertIf
44+65// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
76// ReSharper disable MemberCanBePrivate.Global
87// ReSharper disable UnusedAutoPropertyAccessor.Global
98// ReSharper disable CommentTypo
109// ReSharper disable IdentifierTypo
11101212-namespace IRCStates
1111+namespace IRCStates;
1212+1313+public class Server(string name)
1314{
1414- public class Server
1515- {
1616- public const string WhoType = "525"; // randomly generated
1717- private readonly StatefulDecoder _decoder;
1515+ public const string WhoType = "525"; // randomly generated
1616+ private readonly StatefulDecoder _decoder = new();
18171919- private readonly Dictionary<string, string> _tempCaps;
1818+ private readonly Dictionary<string, string> _tempCaps = [];
20192121- public Server(string name)
2222- {
2323- Name = name;
2424- Registered = false;
2525- Modes = new List<string>();
2626- Motd = new List<string>();
2727- _decoder = new StatefulDecoder();
2828- Users = new Dictionary<string, User>();
2929- Channels = new Dictionary<string, Channel>();
3030- ISupport = new ISupport();
3131- HasCap = false;
3232- _tempCaps = new Dictionary<string, string>();
3333- AvailableCaps = new Dictionary<string, string>();
3434- AgreedCaps = new List<string>();
3535- }
2020+ public string Name { get; set; } = name;
2121+ public string NickName { get; set; }
2222+ public string NickNameLower { get; set; }
2323+ public string UserName { get; set; }
2424+ public string HostName { get; set; }
2525+ public string RealName { get; set; }
2626+ public string Account { get; set; }
2727+ public string Away { get; set; }
36283737- public string Name { get; set; }
3838- public string NickName { get; set; }
3939- public string NickNameLower { get; set; }
4040- public string UserName { get; set; }
4141- public string HostName { get; set; }
4242- public string RealName { get; set; }
4343- public string Account { get; set; }
4444- public string Away { get; set; }
2929+ public bool Registered { get; set; }
3030+ public List<string> Modes { get; set; } = [];
3131+ public List<string> Motd { get; set; } = [];
3232+ public Dictionary<string, User> Users { get; set; } = [];
3333+ public Dictionary<string, Channel> Channels { get; set; } = [];
3434+ public Dictionary<string, string> AvailableCaps { get; set; } = [];
3535+ public List<string> AgreedCaps { get; set; } = [];
45364646- public bool Registered { get; set; }
4747- public List<string> Modes { get; set; }
4848- public List<string> Motd { get; set; }
4949- public Dictionary<string, User> Users { get; set; }
5050- public Dictionary<string, Channel> Channels { get; set; }
5151- public Dictionary<string, string> AvailableCaps { get; set; }
5252- public List<string> AgreedCaps { get; set; }
3737+ // ReSharper disable once InconsistentNaming
3838+ public ISupport ISupport { get; set; } = new();
3939+ public bool HasCap { get; set; }
53405454- // ReSharper disable once InconsistentNaming
5555- public ISupport ISupport { get; set; }
5656- public bool HasCap { get; set; }
4141+ public override string ToString() => $"Server(name={Name})";
57425858- public override string ToString()
5959- {
6060- return $"Server(name={Name})";
6161- }
4343+ /// <summary>
4444+ /// Use <see cref="ISupport"/>'s case mapping to convert to lowercase
4545+ /// </summary>
4646+ /// <param name="str"></param>
4747+ /// <returns></returns>
4848+ public string CaseFold(string str) => Casemap.CaseFold(ISupport.CaseMapping, str);
62496363- /// <summary>
6464- /// Use <see cref="ISupport"/>'s case mapping to convert to lowercase
6565- /// </summary>
6666- /// <param name="str"></param>
6767- /// <returns></returns>
6868- public string CaseFold(string str)
6969- {
7070- return Casemap.CaseFold(ISupport.CaseMapping, str);
7171- }
5050+ /// <summary>
5151+ /// Is the current nickname this client?
5252+ /// </summary>
5353+ /// <param name="nickname"></param>
5454+ /// <returns></returns>
5555+ private bool IsMe(string nickname) => CaseFold(nickname) == NickNameLower;
5656+5757+ /// <summary>
5858+ /// Check for a user - not case-sensitive
5959+ /// </summary>
6060+ /// <param name="nickname"></param>
6161+ /// <returns></returns>
6262+ private bool HasUser(string nickname) => Users.ContainsKey(CaseFold(nickname));
72637373- /// <summary>
7474- /// Is the current nickname this client?
7575- /// </summary>
7676- /// <param name="nickname"></param>
7777- /// <returns></returns>
7878- private bool IsMe(string nickname)
7979- {
8080- return CaseFold(nickname) == NickNameLower;
8181- }
6464+ /// <summary>
6565+ /// Get existing user by case-insensitive nickname
6666+ /// </summary>
6767+ /// <param name="nickname"></param>
6868+ /// <returns></returns>
6969+ private User GetUser(string nickname) => HasUser(nickname) ? Users[CaseFold(nickname)] : null;
82708383- /// <summary>
8484- /// Check for a user - not case-sensitive
8585- /// </summary>
8686- /// <param name="nickname"></param>
8787- /// <returns></returns>
8888- private bool HasUser(string nickname)
8989- {
9090- return Users.ContainsKey(CaseFold(nickname));
9191- }
7171+ /// <summary>
7272+ /// Create and add user
7373+ /// </summary>
7474+ /// <param name="nickname"></param>
7575+ /// <returns></returns>
7676+ private User AddUser(string nickname)
7777+ {
7878+ var user = CreateUser(nickname);
7979+ Users[CaseFold(nickname)] = user;
8080+ return user;
8181+ }
92829393- /// <summary>
9494- /// Get existing user by case-insensitive nickname
9595- /// </summary>
9696- /// <param name="nickname"></param>
9797- /// <returns></returns>
9898- private User GetUser(string nickname)
9999- {
100100- return HasUser(nickname) ? Users[CaseFold(nickname)] : null;
101101- }
8383+ /// <summary>
8484+ /// Build a new <see cref="User"/> and update correct case-mapped nick
8585+ /// </summary>
8686+ /// <param name="nickname"></param>
8787+ /// <returns></returns>
8888+ private User CreateUser(string nickname)
8989+ {
9090+ var user = new User();
9191+ user.SetNickName(nickname, CaseFold(nickname));
9292+ return user;
9393+ }
10294103103- /// <summary>
104104- /// Create and add user
105105- /// </summary>
106106- /// <param name="nickname"></param>
107107- /// <returns></returns>
108108- private User AddUser(string nickname)
109109- {
110110- var user = CreateUser(nickname);
111111- Users[CaseFold(nickname)] = user;
112112- return user;
113113- }
9595+ /// <summary>
9696+ /// Is the channel a valid ISupport type?
9797+ /// </summary>
9898+ /// <param name="target"></param>
9999+ /// <returns></returns>
100100+ private bool IsChannel(string target) =>
101101+ !string.IsNullOrEmpty(target) &&
102102+ ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture));
114103115115- /// <summary>
116116- /// Build a new <see cref="User"/> and update correct case-mapped nick
117117- /// </summary>
118118- /// <param name="nickname"></param>
119119- /// <returns></returns>
120120- private User CreateUser(string nickname)
121121- {
122122- var user = new User();
123123- user.SetNickName(nickname, CaseFold(nickname));
124124- return user;
125125- }
104104+ /// <summary>
105105+ /// Is the channel known to this client?
106106+ /// </summary>
107107+ /// <param name="name"></param>
108108+ /// <returns></returns>
109109+ public bool HasChannel(string name) => IsChannel(name) && Channels.ContainsKey(CaseFold(name));
126110127127- /// <summary>
128128- /// Is the channel a valid ISupport type?
129129- /// </summary>
130130- /// <param name="target"></param>
131131- /// <returns></returns>
132132- private bool IsChannel(string target)
133133- {
134134- return !string.IsNullOrEmpty(target) &&
135135- ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture));
136136- }
111111+ /// <summary>
112112+ /// Get the channel if it's known to us
113113+ /// </summary>
114114+ /// <param name="name"></param>
115115+ /// <returns></returns>
116116+ private Channel GetChannel(string name) => HasChannel(name) ? Channels[CaseFold(name)] : null;
137117138138- /// <summary>
139139- /// Is the channel known to this client?
140140- /// </summary>
141141- /// <param name="name"></param>
142142- /// <returns></returns>
143143- public bool HasChannel(string name)
144144- {
145145- return IsChannel(name) && Channels.ContainsKey(CaseFold(name));
146146- }
118118+ /// <summary>
119119+ /// Add a <see cref="User"/> to a <see cref="Channel"/>
120120+ /// </summary>
121121+ /// <param name="channel"></param>
122122+ /// <param name="user"></param>
123123+ /// <returns>the <see cref="ChannelUser"/> that was added</returns>
124124+ private ChannelUser UserJoin(Channel channel, User user)
125125+ {
126126+ var channelUser = new ChannelUser();
127127+ user.Channels.Add(CaseFold(channel.Name));
128128+ channel.Users[user.NickNameLower] = channelUser;
129129+ return channelUser;
130130+ }
147131148148- /// <summary>
149149- /// Get the channel if it's known to us
150150- /// </summary>
151151- /// <param name="name"></param>
152152- /// <returns></returns>
153153- private Channel GetChannel(string name)
132132+ /// <summary>
133133+ /// Set own <see cref="NickName"/>, <see cref="UserName"/>, and <see cref="HostName"/>
134134+ /// from a given <see cref="Hostmask"/>
135135+ /// </summary>
136136+ /// <param name="hostmask"></param>
137137+ private void SelfHostmask(Hostmask hostmask)
138138+ {
139139+ NickName = hostmask.NickName;
140140+ if (hostmask.UserName != null)
154141 {
155155- return HasChannel(name) ? Channels[CaseFold(name)] : null;
142142+ UserName = hostmask.UserName;
156143 }
157144158158- /// <summary>
159159- /// Add a <see cref="User"/> to a <see cref="Channel"/>
160160- /// </summary>
161161- /// <param name="channel"></param>
162162- /// <param name="user"></param>
163163- /// <returns>the <see cref="ChannelUser"/> that was added</returns>
164164- private ChannelUser UserJoin(Channel channel, User user)
145145+ if (hostmask.HostName != null)
165146 {
166166- var channelUser = new ChannelUser();
167167- user.Channels.Add(CaseFold(channel.Name));
168168- channel.Users[user.NickNameLower] = channelUser;
169169- return channelUser;
147147+ HostName = hostmask.HostName;
170148 }
149149+ }
171150172172- /// <summary>
173173- /// Set own <see cref="NickName"/>, <see cref="UserName"/>, and <see cref="HostName"/>
174174- /// from a given <see cref="Hostmask"/>
175175- /// </summary>
176176- /// <param name="hostmask"></param>
177177- private void SelfHostmask(Hostmask hostmask)
178178- {
179179- NickName = hostmask.NickName;
180180- if (hostmask.UserName != null) UserName = hostmask.UserName;
181181- if (hostmask.HostName != null) HostName = hostmask.HostName;
182182- }
151151+ private void SelfHostmask(string raw)
152152+ {
153153+ SelfHostmask(new Hostmask(raw));
154154+ }
183155184184- private void SelfHostmask(string raw)
156156+ /// <summary>
157157+ /// Remove a user from a channel. Used to handle PART and KICK
158158+ /// </summary>
159159+ /// <param name="line"></param>
160160+ /// <param name="nickName"></param>
161161+ /// <param name="channelName"></param>
162162+ /// <param name="reasonIndex"></param>
163163+ /// <returns></returns>
164164+ private (Emit, User) UserPart(Line line, string nickName, string channelName, int reasonIndex)
165165+ {
166166+ var emit = new Emit();
167167+ var channelLower = CaseFold(channelName);
168168+ if (line.Params.Count >= reasonIndex + 1)
185169 {
186186- SelfHostmask(new Hostmask(raw));
170170+ emit.Text = line.Params[reasonIndex];
187171 }
188172189189- /// <summary>
190190- /// Remove a user from a channel. Used to handle PART and KICK
191191- /// </summary>
192192- /// <param name="line"></param>
193193- /// <param name="nickName"></param>
194194- /// <param name="channelName"></param>
195195- /// <param name="reasonIndex"></param>
196196- /// <returns></returns>
197197- private (Emit, User) UserPart(Line line, string nickName, string channelName, int reasonIndex)
173173+ User user = null;
174174+ if (HasChannel(channelName))
198175 {
199199- var emit = new Emit();
200200- var channelLower = CaseFold(channelName);
201201- if (line.Params.Count >= reasonIndex + 1) emit.Text = line.Params[reasonIndex];
202202-203203- User user = null;
204204- if (HasChannel(channelName))
176176+ var channel = GetChannel(channelName);
177177+ emit.Channel = channel;
178178+ var nickLower = CaseFold(nickName);
179179+ if (HasUser(nickLower))
205180 {
206206- var channel = GetChannel(channelName);
207207- emit.Channel = channel;
208208- var nickLower = CaseFold(nickName);
209209- if (HasUser(nickLower))
181181+ user = Users[nickLower];
182182+ user.Channels.Remove(channelLower);
183183+ channel.Users.Remove(nickLower);
184184+ if (!user.Channels.Any())
210185 {
211211- user = Users[nickLower];
212212- user.Channels.Remove(channelLower);
213213- channel.Users.Remove(nickLower);
214214- if (!user.Channels.Any()) Users.Remove(nickLower);
186186+ Users.Remove(nickLower);
215187 }
188188+ }
216189217217- if (IsMe(nickName))
190190+ if (IsMe(nickName))
191191+ {
192192+ Channels.Remove(channelLower);
193193+ foreach (var userToRemove in channel.Users.Keys.Select(u => Users[u]))
218194 {
219219- Channels.Remove(channelLower);
220220- foreach (var userToRemove in channel.Users.Keys.Select(u => Users[u]))
195195+ userToRemove.Channels.Remove(channelLower);
196196+ if (!userToRemove.Channels.Any())
221197 {
222222- userToRemove.Channels.Remove(channelLower);
223223- if (!userToRemove.Channels.Any()) Users.Remove(userToRemove.NickNameLower);
198198+ Users.Remove(userToRemove.NickNameLower);
224199 }
225200 }
226201 }
227227-228228- return (emit, user);
229202 }
230203231231- /// <summary>
232232- /// Update modes on a <see cref="Channel"/> given modes and parameters
233233- /// </summary>
234234- /// <param name="channel"></param>
235235- /// <param name="modes"></param>
236236- /// <param name="parameters"></param>
237237- private void SetChannelModes(Channel channel, IEnumerable<(bool, string)> modes, IList<string> parameters)
204204+ return (emit, user);
205205+ }
206206+207207+ /// <summary>
208208+ /// Update modes on a <see cref="Channel"/> given modes and parameters
209209+ /// </summary>
210210+ /// <param name="channel"></param>
211211+ /// <param name="modes"></param>
212212+ /// <param name="parameters"></param>
213213+ private void SetChannelModes(Channel channel, IEnumerable<(bool, string)> modes, IList<string> parameters)
214214+ {
215215+ foreach (var (add, c) in modes)
238216 {
239239- foreach (var (add, c) in modes)
217217+ var listMode = ISupport.ChanModes.ListModes.Contains(c);
218218+ if (ISupport.Prefix.Modes.Contains(c))
240219 {
241241- var listMode = ISupport.ChanModes.ListModes.Contains(c);
242242- if (ISupport.Prefix.Modes.Contains(c))
220220+ var nicknameLower = CaseFold(parameters.First());
221221+ parameters.RemoveAt(0);
222222+ if (!HasUser(nicknameLower))
243223 {
244244- var nicknameLower = CaseFold(parameters.First());
245245- parameters.RemoveAt(0);
246246- if (!HasUser(nicknameLower)) continue;
224224+ continue;
225225+ }
247226248248- var channelUser = channel.Users[nicknameLower];
249249- if (add)
227227+ var channelUser = channel.Users[nicknameLower];
228228+ if (add)
229229+ {
230230+ if (!channelUser.Modes.Contains(c))
250231 {
251251- if (!channelUser.Modes.Contains(c)) channelUser.Modes.Add(c);
252252- }
253253- else if (channelUser.Modes.Contains(c))
254254- {
255255- channelUser.Modes.Remove(c);
232232+ channelUser.Modes.Add(c);
256233 }
257234 }
258258- else if (add && (listMode ||
259259- ISupport.ChanModes.SettingBModes.Contains(c) ||
260260- ISupport.ChanModes.SettingCModes.Contains(c)))
235235+ else if (channelUser.Modes.Contains(c))
261236 {
262262- channel.AddMode(c, parameters.First(), listMode);
263263- parameters.RemoveAt(0);
237237+ channelUser.Modes.Remove(c);
264238 }
265265- else if (!add && (listMode || ISupport.ChanModes.SettingBModes.Contains(c)))
266266- {
267267- channel.RemoveMode(c, parameters.First());
268268- parameters.RemoveAt(0);
269269- }
270270- else if (add)
271271- {
272272- channel.AddMode(c, null, false);
273273- }
274274- else
275275- {
276276- channel.RemoveMode(c, null);
277277- }
239239+ }
240240+ else if (add && (listMode ||
241241+ ISupport.ChanModes.SettingBModes.Contains(c) ||
242242+ ISupport.ChanModes.SettingCModes.Contains(c)))
243243+ {
244244+ channel.AddMode(c, parameters.First(), listMode);
245245+ parameters.RemoveAt(0);
246246+ }
247247+ else if (!add && (listMode || ISupport.ChanModes.SettingBModes.Contains(c)))
248248+ {
249249+ channel.RemoveMode(c, parameters.First());
250250+ parameters.RemoveAt(0);
251251+ }
252252+ else if (add)
253253+ {
254254+ channel.AddMode(c, null, false);
255255+ }
256256+ else
257257+ {
258258+ channel.RemoveMode(c, null);
278259 }
279260 }
261261+ }
280262281281- /// <summary>
282282- /// Handle incoming bytes
283283- /// </summary>
284284- /// <param name="data"></param>
285285- /// <param name="length"></param>
286286- /// <returns>parsed lines and emits</returns>
287287- /// <exception cref="ServerDisconnectedException"></exception>
288288- public IEnumerable<(Line, Emit)> Receive(byte[] data, int length)
263263+ /// <summary>
264264+ /// Handle incoming bytes
265265+ /// </summary>
266266+ /// <param name="data"></param>
267267+ /// <param name="length"></param>
268268+ /// <returns>parsed lines and emits</returns>
269269+ /// <exception cref="ServerDisconnectedException"></exception>
270270+ public IEnumerable<(Line, Emit)> Receive(byte[] data, int length)
271271+ {
272272+ if (data == null)
289273 {
290290- if (data == null) return null;
274274+ return null;
275275+ }
291276292292- var lines = _decoder.Push(data, length);
293293- if (lines == null) throw new ServerDisconnectedException();
277277+ var lines = _decoder.Push(data, length);
278278+ if (lines == null)
279279+ {
280280+ throw new ServerDisconnectedException();
281281+ }
294282295295- return lines.Select(l => (l, Parse(l)));
283283+ return lines.Select(l => (l, Parse(l)));
284284+ }
285285+286286+ /// <summary>
287287+ /// Delegate a <see cref="Line"/> to the correct handler
288288+ /// </summary>
289289+ /// <param name="line"></param>
290290+ /// <returns></returns>
291291+ public Emit Parse(Line line)
292292+ {
293293+ if (line == null)
294294+ {
295295+ return null;
296296 }
297297298298- /// <summary>
299299- /// Delegate a <see cref="Line"/> to the correct handler
300300- /// </summary>
301301- /// <param name="line"></param>
302302- /// <returns></returns>
303303- public Emit Parse(Line line)
298298+ var emit = line.Command switch
304299 {
305305- if (line == null) return null;
300300+ Numeric.RPL_WELCOME => HandleWelcome(line),
301301+ Numeric.RPL_ISUPPORT => HandleISupport(line),
302302+ Numeric.RPL_MOTDSTART => HandleMotd(line),
303303+ Numeric.RPL_MOTD => HandleMotd(line),
304304+ Commands.Nick => HandleNick(line),
305305+ Commands.Join => HandleJoin(line),
306306+ Commands.Part => HandlePart(line),
307307+ Commands.Kick => HandleKick(line),
308308+ Commands.Quit => HandleQuit(line),
309309+ Commands.Error => HandleError(line),
310310+ Numeric.RPL_NAMREPLY => HandleNames(line),
311311+ Numeric.RPL_CREATIONTIME => HandleCreationTime(line),
312312+ Commands.Topic => HandleTopic(line),
313313+ Numeric.RPL_TOPIC => HandleTopicNumeric(line),
314314+ Numeric.RPL_TOPICWHOTIME => HandleTopicTime(line),
315315+ Commands.Mode => HandleMode(line),
316316+ Numeric.RPL_CHANNELMODEIS => HandleChannelModeIs(line),
317317+ Numeric.RPL_UMODEIS => HandleUModeIs(line),
318318+ Commands.Privmsg => HandleMessage(line),
319319+ Commands.Notice => HandleMessage(line),
320320+ Commands.Tagmsg => HandleMessage(line),
321321+ Numeric.RPL_VISIBLEHOST => HandleVisibleHost(line),
322322+ Numeric.RPL_WHOREPLY => HandleWhoReply(line),
323323+ Numeric.RPL_WHOSPCRPL => HandleWhox(line),
324324+ Numeric.RPL_WHOISUSER => HandleWhoIsUser(line),
325325+ Commands.Chghost => HandleChghost(line),
326326+ Commands.Setname => HandleSetname(line),
327327+ Commands.Away => HandleAway(line),
328328+ Commands.Account => HandleAccount(line),
329329+ Commands.Cap => HandleCap(line),
330330+ Numeric.RPL_LOGGEDIN => HandleLoggedIn(line),
331331+ Numeric.RPL_LOGGEDOUT => HandleLoggedOut(line),
332332+ _ => null
333333+ };
334334+335335+ if (emit != null)
336336+ {
337337+ emit.Command = line.Command;
338338+ }
339339+ else
340340+ {
341341+ emit = new Emit();
342342+ }
306343307307- var emit = line.Command switch
308308- {
309309- Numeric.RPL_WELCOME => HandleWelcome(line),
310310- Numeric.RPL_ISUPPORT => HandleISupport(line),
311311- Numeric.RPL_MOTDSTART => HandleMotd(line),
312312- Numeric.RPL_MOTD => HandleMotd(line),
313313- Commands.Nick => HandleNick(line),
314314- Commands.Join => HandleJoin(line),
315315- Commands.Part => HandlePart(line),
316316- Commands.Kick => HandleKick(line),
317317- Commands.Quit => HandleQuit(line),
318318- Commands.Error => HandleError(line),
319319- Numeric.RPL_NAMREPLY => HandleNames(line),
320320- Numeric.RPL_CREATIONTIME => HandleCreationTime(line),
321321- Commands.Topic => HandleTopic(line),
322322- Numeric.RPL_TOPIC => HandleTopicNumeric(line),
323323- Numeric.RPL_TOPICWHOTIME => HandleTopicTime(line),
324324- Commands.Mode => HandleMode(line),
325325- Numeric.RPL_CHANNELMODEIS => HandleChannelModeIs(line),
326326- Numeric.RPL_UMODEIS => HandleUModeIs(line),
327327- Commands.Privmsg => HandleMessage(line),
328328- Commands.Notice => HandleMessage(line),
329329- Commands.Tagmsg => HandleMessage(line),
330330- Numeric.RPL_VISIBLEHOST => HandleVisibleHost(line),
331331- Numeric.RPL_WHOREPLY => HandleWhoReply(line),
332332- Numeric.RPL_WHOSPCRPL => HandleWhox(line),
333333- Numeric.RPL_WHOISUSER => HandleWhoIsUser(line),
334334- Commands.Chghost => HandleChghost(line),
335335- Commands.Setname => HandleSetname(line),
336336- Commands.Away => HandleAway(line),
337337- Commands.Account => HandleAccount(line),
338338- Commands.Cap => HandleCap(line),
339339- Numeric.RPL_LOGGEDIN => HandleLoggedIn(line),
340340- Numeric.RPL_LOGGEDOUT => HandleLoggedOut(line),
341341- _ => null
342342- };
344344+ return emit;
345345+ }
343346344344- if (emit != null)
345345- emit.Command = line.Command;
346346- else
347347- emit = new Emit();
347347+ /// <summary>
348348+ /// Handles SETNAME command
349349+ /// </summary>
350350+ /// <param name="line"></param>
351351+ /// <returns></returns>
352352+ private Emit HandleSetname(Line line)
353353+ {
354354+ var emit = new Emit();
355355+ var realname = line.Params[0];
356356+ var nicknameLower = CaseFold(line.Hostmask.NickName);
348357349349- return emit;
358358+ if (IsMe(nicknameLower))
359359+ {
360360+ emit.Self = true;
361361+ RealName = realname;
350362 }
351363352352- /// <summary>
353353- /// Handles SETNAME command
354354- /// </summary>
355355- /// <param name="line"></param>
356356- /// <returns></returns>
357357- private Emit HandleSetname(Line line)
364364+ if (Users.TryGetValue(nicknameLower, out var user))
358365 {
359359- var emit = new Emit();
360360- var realname = line.Params[0];
361361- var nicknameLower = CaseFold(line.Hostmask.NickName);
366366+ emit.User = user;
367367+ user.RealName = realname;
368368+ }
362369363363- if (IsMe(nicknameLower))
364364- {
365365- emit.Self = true;
366366- RealName = realname;
367367- }
370370+ return emit;
371371+ }
368372369369- if (Users.TryGetValue(nicknameLower, out var user))
370370- {
371371- emit.User = user;
372372- user.RealName = realname;
373373- }
373373+ /// <summary>
374374+ /// Handles AWAY command
375375+ /// </summary>
376376+ /// <param name="line"></param>
377377+ /// <returns></returns>
378378+ private Emit HandleAway(Line line)
379379+ {
380380+ var emit = new Emit();
381381+ var away = line.Params.FirstOrDefault();
382382+ var nicknameLower = CaseFold(line.Hostmask.NickName);
374383375375- return emit;
384384+ if (IsMe(nicknameLower))
385385+ {
386386+ emit.Self = true;
387387+ Away = away;
376388 }
377389378378- /// <summary>
379379- /// Handles AWAY command
380380- /// </summary>
381381- /// <param name="line"></param>
382382- /// <returns></returns>
383383- private Emit HandleAway(Line line)
390390+ if (Users.TryGetValue(nicknameLower, out var user))
384391 {
385385- var emit = new Emit();
386386- var away = line.Params.FirstOrDefault();
387387- var nicknameLower = CaseFold(line.Hostmask.NickName);
392392+ emit.User = user;
393393+ user.Away = away;
394394+ }
388395389389- if (IsMe(nicknameLower))
390390- {
391391- emit.Self = true;
392392- Away = away;
393393- }
396396+ return emit;
397397+ }
394398395395- if (Users.TryGetValue(nicknameLower, out var user))
396396- {
397397- emit.User = user;
398398- user.Away = away;
399399- }
399399+ /// <summary>
400400+ /// Handles ACCOUNT command
401401+ /// </summary>
402402+ /// <param name="line"></param>
403403+ /// <returns></returns>
404404+ private Emit HandleAccount(Line line)
405405+ {
406406+ var emit = new Emit();
407407+ var account = line.Params[0].Trim('*');
408408+ var nicknameLower = CaseFold(line.Hostmask.NickName);
400409401401- return emit;
410410+ if (IsMe(nicknameLower))
411411+ {
412412+ emit.Self = true;
413413+ Account = account;
402414 }
403415404404- /// <summary>
405405- /// Handles ACCOUNT command
406406- /// </summary>
407407- /// <param name="line"></param>
408408- /// <returns></returns>
409409- private Emit HandleAccount(Line line)
416416+ if (Users.TryGetValue(nicknameLower, out var user))
410417 {
411411- var emit = new Emit();
412412- var account = line.Params[0].Trim('*');
413413- var nicknameLower = CaseFold(line.Hostmask.NickName);
414414-415415- if (IsMe(nicknameLower))
416416- {
417417- emit.Self = true;
418418- Account = account;
419419- }
418418+ emit.User = user;
419419+ user.Account = account;
420420+ }
420421421421- if (Users.TryGetValue(nicknameLower, out var user))
422422- {
423423- emit.User = user;
424424- user.Account = account;
425425- }
422422+ return emit;
423423+ }
426424427427- return emit;
428428- }
425425+ /// <summary>
426426+ /// Handles CAP command
427427+ /// </summary>
428428+ /// <param name="line"></param>
429429+ /// <returns></returns>
430430+ private Emit HandleCap(Line line)
431431+ {
432432+ HasCap = true;
433433+ var subcommand = line.Params[1].ToUpperInvariant();
434434+ var multiline = line.Params[2] == "*";
435435+ var caps = line.Params[multiline ? 3 : 2];
429436430430- /// <summary>
431431- /// Handles CAP command
432432- /// </summary>
433433- /// <param name="line"></param>
434434- /// <returns></returns>
435435- private Emit HandleCap(Line line)
437437+ var tokens = new Dictionary<string, string>();
438438+ var tokensStr = new List<string>();
439439+ foreach (var cap in caps.Split([' '], StringSplitOptions.RemoveEmptyEntries))
436440 {
437437- HasCap = true;
438438- var subcommand = line.Params[1].ToUpperInvariant();
439439- var multiline = line.Params[2] == "*";
440440- var caps = line.Params[multiline ? 3 : 2];
441441+ tokensStr.Add(cap);
442442+ var kv = cap.Split(['='], 2);
443443+ tokens[kv[0]] = kv.Length > 1 ? kv[1] : string.Empty;
444444+ }
441445442442- var tokens = new Dictionary<string, string>();
443443- var tokensStr = new List<string>();
444444- foreach (var cap in caps.Split(' ', StringSplitOptions.RemoveEmptyEntries))
445445- {
446446- tokensStr.Add(cap);
447447- var kv = cap.Split('=', 2);
448448- tokens[kv[0]] = kv.Length > 1 ? kv[1] : string.Empty;
449449- }
446446+ var emit = new Emit { Subcommand = subcommand, Finished = !multiline, Tokens = tokensStr };
450447451451- var emit = new Emit {Subcommand = subcommand, Finished = !multiline, Tokens = tokensStr};
448448+ switch (subcommand)
449449+ {
450450+ case "LS":
451451+ _tempCaps.UpdateWith(tokens);
452452+ if (!multiline)
453453+ {
454454+ AvailableCaps.UpdateWith(_tempCaps);
455455+ _tempCaps.Clear();
456456+ }
452457453453- switch (subcommand)
454454- {
455455- case "LS":
456456- _tempCaps.UpdateWith(tokens);
457457- if (!multiline)
458458+ break;
459459+ case "NEW": AvailableCaps.UpdateWith(tokens); break;
460460+ case "DEL":
461461+ foreach (var key in tokens.Keys.Where(key => AvailableCaps.ContainsKey(key)))
462462+ {
463463+ AvailableCaps.Remove(key);
464464+ if (AgreedCaps.Contains(key))
458465 {
459459- AvailableCaps.UpdateWith(_tempCaps);
460460- _tempCaps.Clear();
466466+ AgreedCaps.Remove(key);
461467 }
468468+ }
462469463463- break;
464464- case "NEW":
465465- AvailableCaps.UpdateWith(tokens);
466466- break;
467467- case "DEL":
468468- foreach (var key in tokens.Keys.Where(key => AvailableCaps.ContainsKey(key)))
470470+ break;
471471+ case "ACK":
472472+ foreach (var key in tokens.Keys)
473473+ if (key.StartsWith("-"))
469474 {
470470- AvailableCaps.Remove(key);
471471- if (AgreedCaps.Contains(key)) AgreedCaps.Remove(key);
472472- }
473473-474474- break;
475475- case "ACK":
476476- foreach (var key in tokens.Keys)
477477- if (key.StartsWith('-'))
475475+ var k = key.Substring(1);
476476+ if (AgreedCaps.Contains(k))
478477 {
479479- var k = key[1..];
480480- if (AgreedCaps.Contains(k)) AgreedCaps.Remove(k);
478478+ AgreedCaps.Remove(k);
481479 }
482482- else if (!AgreedCaps.Contains(key) && AvailableCaps.ContainsKey(key))
483483- {
484484- AgreedCaps.Add(key);
485485- }
480480+ }
481481+ else if (!AgreedCaps.Contains(key) && AvailableCaps.ContainsKey(key))
482482+ {
483483+ AgreedCaps.Add(key);
484484+ }
486485487487- break;
488488- }
489489-490490- return emit;
491491- }
492492-493493- /// <summary>
494494- /// Handles RPL_LOGGEDIN numeric
495495- /// </summary>
496496- /// <param name="line"></param>
497497- /// <returns></returns>
498498- private Emit HandleLoggedIn(Line line)
499499- {
500500- SelfHostmask(new Hostmask(line.Params[1]));
501501- Account = line.Params[2];
502502- return new Emit();
486486+ break;
503487 }
504488505505- /// <summary>
506506- /// Handles CHGHOST command
507507- /// </summary>
508508- /// <param name="line"></param>
509509- /// <returns></returns>
510510- private Emit HandleChghost(Line line)
511511- {
512512- var emit = new Emit();
513513- var username = line.Params[0];
514514- var hostname = line.Params[1];
515515- var nicknameLower = CaseFold(line.Hostmask.NickName);
489489+ return emit;
490490+ }
516491517517- if (IsMe(nicknameLower))
518518- {
519519- emit.Self = true;
520520- UserName = username;
521521- HostName = hostname;
522522- }
492492+ /// <summary>
493493+ /// Handles RPL_LOGGEDIN numeric
494494+ /// </summary>
495495+ /// <param name="line"></param>
496496+ /// <returns></returns>
497497+ private Emit HandleLoggedIn(Line line)
498498+ {
499499+ SelfHostmask(new Hostmask(line.Params[1]));
500500+ Account = line.Params[2];
501501+ return new Emit();
502502+ }
523503524524- if (Users.TryGetValue(nicknameLower, out var user))
525525- {
526526- emit.User = user;
527527- user.UserName = username;
528528- user.HostName = hostname;
529529- }
504504+ /// <summary>
505505+ /// Handles CHGHOST command
506506+ /// </summary>
507507+ /// <param name="line"></param>
508508+ /// <returns></returns>
509509+ private Emit HandleChghost(Line line)
510510+ {
511511+ var emit = new Emit();
512512+ var username = line.Params[0];
513513+ var hostname = line.Params[1];
514514+ var nicknameLower = CaseFold(line.Hostmask.NickName);
530515531531- return emit;
516516+ if (IsMe(nicknameLower))
517517+ {
518518+ emit.Self = true;
519519+ UserName = username;
520520+ HostName = hostname;
532521 }
533522534534- /// <summary>
535535- /// Handles RPL_WHOISUSER numeric
536536- /// </summary>
537537- /// <param name="line"></param>
538538- /// <returns></returns>
539539- private Emit HandleWhoIsUser(Line line)
523523+ if (Users.TryGetValue(nicknameLower, out var user))
540524 {
541541- var emit = new Emit();
542542- var nickname = line.Params[1];
543543- var username = line.Params[2];
544544- var hostname = line.Params[3];
545545- var realname = line.Params[5];
525525+ emit.User = user;
526526+ user.UserName = username;
527527+ user.HostName = hostname;
528528+ }
546529547547- if (IsMe(nickname))
548548- {
549549- emit.Self = true;
550550- UserName = username;
551551- HostName = hostname;
552552- RealName = realname;
553553- }
530530+ return emit;
531531+ }
554532555555- if (HasUser(nickname))
556556- {
557557- var user = Users[CaseFold(nickname)];
558558- emit.User = user;
559559- user.UserName = username;
560560- user.HostName = hostname;
561561- user.RealName = realname;
562562- }
533533+ /// <summary>
534534+ /// Handles RPL_WHOISUSER numeric
535535+ /// </summary>
536536+ /// <param name="line"></param>
537537+ /// <returns></returns>
538538+ private Emit HandleWhoIsUser(Line line)
539539+ {
540540+ var emit = new Emit();
541541+ var nickname = line.Params[1];
542542+ var username = line.Params[2];
543543+ var hostname = line.Params[3];
544544+ var realname = line.Params[5];
563545564564- return emit;
546546+ if (IsMe(nickname))
547547+ {
548548+ emit.Self = true;
549549+ UserName = username;
550550+ HostName = hostname;
551551+ RealName = realname;
565552 }
566553567567- /// <summary>
568568- /// Handles RPL_WHOSPCRPL numeric
569569- /// </summary>
570570- /// <param name="line"></param>
571571- /// <returns></returns>
572572- private Emit HandleWhox(Line line)
554554+ if (HasUser(nickname))
573555 {
574574- var emit = new Emit();
575575- if (line.Params[1] == WhoType && line.Params.Count == 8)
576576- {
577577- var nickname = line.Params[5];
578578- var username = line.Params[2];
579579- var hostname = line.Params[4];
580580- var realname = line.Params[7];
581581- var account = line.Params[6] == "0" ? null : line.Params[6];
582582-583583- if (IsMe(nickname))
584584- {
585585- emit.Self = true;
586586- UserName = username;
587587- HostName = hostname;
588588- RealName = realname;
589589- Account = account;
590590- }
591591-592592- if (HasUser(nickname))
593593- {
594594- var user = Users[CaseFold(nickname)];
595595- emit.User = user;
596596- user.UserName = username;
597597- user.HostName = hostname;
598598- user.RealName = realname;
599599- user.Account = account;
600600- }
601601- }
602602-603603- return emit;
556556+ var user = Users[CaseFold(nickname)];
557557+ emit.User = user;
558558+ user.UserName = username;
559559+ user.HostName = hostname;
560560+ user.RealName = realname;
604561 }
605562606606- /// <summary>
607607- /// Handles RPL_WHOREPLY numeric
608608- /// </summary>
609609- /// <param name="line"></param>
610610- /// <returns></returns>
611611- private Emit HandleWhoReply(Line line)
563563+ return emit;
564564+ }
565565+566566+ /// <summary>
567567+ /// Handles RPL_WHOSPCRPL numeric
568568+ /// </summary>
569569+ /// <param name="line"></param>
570570+ /// <returns></returns>
571571+ private Emit HandleWhox(Line line)
572572+ {
573573+ var emit = new Emit();
574574+ if (line.Params[1] == WhoType && line.Params.Count == 8)
612575 {
613613- var emit = new Emit {Target = line.Params[1]};
614576 var nickname = line.Params[5];
615577 var username = line.Params[2];
616616- var hostname = line.Params[3];
617617- var realname = line.Params[7].Split(' ', 2)[1];
578578+ var hostname = line.Params[4];
579579+ var realname = line.Params[7];
580580+ var account = line.Params[6] == "0" ? null : line.Params[6];
618581619582 if (IsMe(nickname))
620583 {
···622585 UserName = username;
623586 HostName = hostname;
624587 RealName = realname;
588588+ Account = account;
625589 }
626590627591 if (HasUser(nickname))
···631595 user.UserName = username;
632596 user.HostName = hostname;
633597 user.RealName = realname;
598598+ user.Account = account;
634599 }
635635-636636- return emit;
637600 }
638601639639- /// <summary>
640640- /// Handles RPL_VISIBLEHOST numeric
641641- /// </summary>
642642- /// <param name="line"></param>
643643- /// <returns></returns>
644644- private Emit HandleVisibleHost(Line line)
645645- {
646646- var split = line.Params[1].Split('@', 2);
647647- switch (split.Length)
648648- {
649649- case 1:
650650- HostName = split[0];
651651- break;
652652- case 2:
653653- HostName = split[1];
654654- UserName = split[0];
655655- break;
656656- }
602602+ return emit;
603603+ }
604604+605605+ /// <summary>
606606+ /// Handles RPL_WHOREPLY numeric
607607+ /// </summary>
608608+ /// <param name="line"></param>
609609+ /// <returns></returns>
610610+ private Emit HandleWhoReply(Line line)
611611+ {
612612+ var emit = new Emit { Target = line.Params[1] };
613613+ var nickname = line.Params[5];
614614+ var username = line.Params[2];
615615+ var hostname = line.Params[3];
616616+ var realname = line.Params[7].Split([' '], 2)[1];
657617658658- return new Emit();
618618+ if (IsMe(nickname))
619619+ {
620620+ emit.Self = true;
621621+ UserName = username;
622622+ HostName = hostname;
623623+ RealName = realname;
659624 }
660625661661- /// <summary>
662662- /// Handles PRIVMSG, NOTICE, and TAGMSG commands
663663- /// </summary>
664664- /// <param name="line"></param>
665665- /// <returns></returns>
666666- private Emit HandleMessage(Line line)
626626+ if (HasUser(nickname))
667627 {
668668- var emit = new Emit();
669669- var message = line.Params.Count > 1 ? line.Params[1] : null;
670670- if (message != null) emit.Text = message;
628628+ var user = Users[CaseFold(nickname)];
629629+ emit.User = user;
630630+ user.UserName = username;
631631+ user.HostName = hostname;
632632+ user.RealName = realname;
633633+ }
671634672672- var nick = CaseFold(line.Hostmask.NickName);
673673- if (IsMe(nick))
674674- {
675675- emit.SelfSource = true;
676676- SelfHostmask(line.Hostmask);
677677- }
635635+ return emit;
636636+ }
678637679679- var user = GetUser(nick) ?? AddUser(nick);
680680- emit.User = user;
638638+ /// <summary>
639639+ /// Handles RPL_VISIBLEHOST numeric
640640+ /// </summary>
641641+ /// <param name="line"></param>
642642+ /// <returns></returns>
643643+ private Emit HandleVisibleHost(Line line)
644644+ {
645645+ var split = line.Params[1].Split(['@'], 2);
646646+ switch (split.Length)
647647+ {
648648+ case 1: HostName = split[0]; break;
649649+ case 2:
650650+ HostName = split[1];
651651+ UserName = split[0];
652652+ break;
653653+ }
681654682682- if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName;
683683- if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName;
655655+ return new Emit();
656656+ }
684657685685- var target = line.Params[0];
686686- var statusMsg = new List<string>();
687687- while (target.Length > 0)
688688- {
689689- var t = target[0].ToString(CultureInfo.InvariantCulture);
690690- if (ISupport.StatusMsg.Contains(t))
691691- {
692692- statusMsg.Add(t);
693693- target = target[1..];
694694- }
695695- else
696696- {
697697- break;
698698- }
699699- }
658658+ /// <summary>
659659+ /// Handles PRIVMSG, NOTICE, and TAGMSG commands
660660+ /// </summary>
661661+ /// <param name="line"></param>
662662+ /// <returns></returns>
663663+ private Emit HandleMessage(Line line)
664664+ {
665665+ var emit = new Emit();
666666+ var message = line.Params.Count > 1 ? line.Params[1] : null;
667667+ if (message != null)
668668+ {
669669+ emit.Text = message;
670670+ }
700671701701- emit.Target = line.Params[0];
672672+ var nick = CaseFold(line.Hostmask.NickName);
673673+ if (IsMe(nick))
674674+ {
675675+ emit.SelfSource = true;
676676+ SelfHostmask(line.Hostmask);
677677+ }
702678703703- if (IsChannel(target) && HasChannel(target))
704704- emit.Channel = GetChannel(target);
705705- else if (IsMe(target)) emit.SelfTarget = true;
679679+ var user = GetUser(nick) ?? AddUser(nick);
680680+ emit.User = user;
706681707707- return emit;
682682+ if (line.Hostmask.UserName != null)
683683+ {
684684+ user.UserName = line.Hostmask.UserName;
708685 }
709686710710- /// <summary>
711711- /// Handles RPL_UMODEIS numeric
712712- /// </summary>
713713- /// <param name="line"></param>
714714- /// <returns></returns>
715715- private Emit HandleUModeIs(Line line)
687687+ if (line.Hostmask.HostName != null)
716688 {
717717- foreach (var c in line.Params[1]
718718- .TrimStart('+')
719719- .Select(m => m.ToString(CultureInfo.InvariantCulture))
720720- .Where(m => !Modes.Contains(m)))
721721- Modes.Add(c);
722722-723723- return new Emit();
689689+ user.HostName = line.Hostmask.HostName;
724690 }
725691726726- /// <summary>
727727- /// Handles RPL_CHANNELMODEIS numeric
728728- /// </summary>
729729- /// <param name="line"></param>
730730- /// <returns></returns>
731731- private Emit HandleChannelModeIs(Line line)
692692+ var target = line.Params[0];
693693+ var statusMsg = new List<string>();
694694+ while (target.Length > 0)
732695 {
733733- var emit = new Emit();
734734- if (HasChannel(line.Params[1]))
696696+ var t = target[0].ToString(CultureInfo.InvariantCulture);
697697+ if (ISupport.StatusMsg.Contains(t))
735698 {
736736- var channel = GetChannel(line.Params[1]);
737737- emit.Channel = channel;
738738- var modes = line.Params[2]
739739- .TrimStart('+')
740740- .Select(p => (true, p.ToString(CultureInfo.InvariantCulture)));
741741- var parameters = line.Params.Skip(3).ToList();
742742- SetChannelModes(channel, modes, parameters);
699699+ statusMsg.Add(t);
700700+ target = target.Substring(1);
743701 }
702702+ else
703703+ {
704704+ break;
705705+ }
706706+ }
744707745745- return emit;
708708+ emit.Target = line.Params[0];
709709+710710+ if (IsChannel(target) && HasChannel(target))
711711+ {
712712+ emit.Channel = GetChannel(target);
713713+ }
714714+ else if (IsMe(target))
715715+ {
716716+ emit.SelfTarget = true;
746717 }
747718748748- /// <summary>
749749- /// Handles MODE command
750750- /// </summary>
751751- /// <param name="line"></param>
752752- /// <returns></returns>
753753- private Emit HandleMode(Line line)
719719+ return emit;
720720+ }
721721+722722+ /// <summary>
723723+ /// Handles RPL_UMODEIS numeric
724724+ /// </summary>
725725+ /// <param name="line"></param>
726726+ /// <returns></returns>
727727+ private Emit HandleUModeIs(Line line)
728728+ {
729729+ foreach (var c in line.Params[1]
730730+ .TrimStart('+')
731731+ .Select(m => m.ToString(CultureInfo.InvariantCulture))
732732+ .Where(m => !Modes.Contains(m)))
733733+ Modes.Add(c);
734734+735735+ return new Emit();
736736+ }
737737+738738+ /// <summary>
739739+ /// Handles RPL_CHANNELMODEIS numeric
740740+ /// </summary>
741741+ /// <param name="line"></param>
742742+ /// <returns></returns>
743743+ private Emit HandleChannelModeIs(Line line)
744744+ {
745745+ var emit = new Emit();
746746+ if (HasChannel(line.Params[1]))
754747 {
755755- var emit = new Emit();
756756- var target = line.Params[0];
757757- var modeString = line.Params[1];
758758- var parameters = line.Params.Skip(2).ToList();
748748+ var channel = GetChannel(line.Params[1]);
749749+ emit.Channel = channel;
750750+ var modes = line.Params[2]
751751+ .TrimStart('+')
752752+ .Select(p => (true, p.ToString(CultureInfo.InvariantCulture)));
753753+ var parameters = line.Params.Skip(3).ToList();
754754+ SetChannelModes(channel, modes, parameters);
755755+ }
759756760760- var modifier = '+';
761761- var modes = new List<(bool, string)>();
762762- var tokens = new List<string>();
757757+ return emit;
758758+ }
763759764764- foreach (var c in modeString)
765765- if (new[] {'+', '-'}.Contains(c))
766766- {
767767- modifier = c;
768768- }
769769- else
770770- {
771771- modes.Add((modifier == '+', c.ToString(CultureInfo.InvariantCulture)));
772772- tokens.Add($"{modifier}{c}");
773773- }
760760+ /// <summary>
761761+ /// Handles MODE command
762762+ /// </summary>
763763+ /// <param name="line"></param>
764764+ /// <returns></returns>
765765+ private Emit HandleMode(Line line)
766766+ {
767767+ var emit = new Emit();
768768+ var target = line.Params[0];
769769+ var modeString = line.Params[1];
770770+ var parameters = line.Params.Skip(2).ToList();
774771775775- emit.Tokens = tokens;
772772+ var modifier = '+';
773773+ var modes = new List<(bool, string)>();
774774+ var tokens = new List<string>();
776775777777- if (IsMe(target))
776776+ foreach (var c in modeString)
777777+ if (new[] { '+', '-' }.Contains(c))
778778 {
779779- emit.SelfTarget = true;
780780- foreach (var (add, c) in modes)
781781- if (add && !Modes.Contains(c))
782782- Modes.Add(c);
783783- else if (Modes.Contains(c)) Modes.Remove(c);
779779+ modifier = c;
784780 }
785785- else if (HasChannel(target))
781781+ else
786782 {
787787- var channel = GetChannel(CaseFold(target));
788788- emit.Channel = channel;
789789- SetChannelModes(channel, modes, parameters);
783783+ modes.Add((modifier == '+', c.ToString(CultureInfo.InvariantCulture)));
784784+ tokens.Add($"{modifier}{c}");
790785 }
791786792792- return emit;
793793- }
787787+ emit.Tokens = tokens;
794788795795- /// <summary>
796796- /// Handles RPL_TOPICWHOTIME numeric
797797- /// </summary>
798798- /// <param name="line"></param>
799799- /// <returns></returns>
800800- private Emit HandleTopicTime(Line line)
789789+ if (IsMe(target))
801790 {
802802- var emit = new Emit();
803803- if (HasChannel(line.Params[1]))
791791+ emit.SelfTarget = true;
792792+ foreach (var (add, c) in modes)
804793 {
805805- var channel = GetChannel(line.Params[1]);
806806- emit.Channel = channel;
807807- channel.TopicSetter = line.Params[2];
808808- channel.TopicTime = DateTimeOffset
809809- .FromUnixTimeSeconds(int.Parse(line.Params[3], CultureInfo.InvariantCulture)).DateTime;
794794+ if (add && !Modes.Contains(c))
795795+ {
796796+ Modes.Add(c);
797797+ }
798798+ else if (Modes.Contains(c))
799799+ {
800800+ Modes.Remove(c);
801801+ }
810802 }
803803+ }
804804+ else if (HasChannel(target))
805805+ {
806806+ var channel = GetChannel(CaseFold(target));
807807+ emit.Channel = channel;
808808+ SetChannelModes(channel, modes, parameters);
809809+ }
810810+811811+ return emit;
812812+ }
811813812812- return emit;
814814+ /// <summary>
815815+ /// Handles RPL_TOPICWHOTIME numeric
816816+ /// </summary>
817817+ /// <param name="line"></param>
818818+ /// <returns></returns>
819819+ private Emit HandleTopicTime(Line line)
820820+ {
821821+ var emit = new Emit();
822822+ if (HasChannel(line.Params[1]))
823823+ {
824824+ var channel = GetChannel(line.Params[1]);
825825+ emit.Channel = channel;
826826+ channel.TopicSetter = line.Params[2];
827827+ channel.TopicTime = DateTimeOffset
828828+ .FromUnixTimeSeconds(int.Parse(line.Params[3], CultureInfo.InvariantCulture)).DateTime;
813829 }
814830815815- /// <summary>
816816- /// Handles RPL_TOPIC numeric
817817- /// </summary>
818818- /// <param name="line"></param>
819819- /// <returns></returns>
820820- private Emit HandleTopicNumeric(Line line)
831831+ return emit;
832832+ }
833833+834834+ /// <summary>
835835+ /// Handles RPL_TOPIC numeric
836836+ /// </summary>
837837+ /// <param name="line"></param>
838838+ /// <returns></returns>
839839+ private Emit HandleTopicNumeric(Line line)
840840+ {
841841+ var emit = new Emit();
842842+ if (HasChannel(line.Params[1]))
821843 {
822822- var emit = new Emit();
823823- if (HasChannel(line.Params[1]))
824824- {
825825- var channel = GetChannel(line.Params[1]);
826826- emit.Channel = channel;
827827- channel.Topic = line.Params[2];
828828- }
844844+ var channel = GetChannel(line.Params[1]);
845845+ emit.Channel = channel;
846846+ channel.Topic = line.Params[2];
847847+ }
848848+849849+ return emit;
850850+ }
829851830830- return emit;
852852+ /// <summary>
853853+ /// Handles TOPIC command
854854+ /// </summary>
855855+ /// <param name="line"></param>
856856+ /// <returns></returns>
857857+ private Emit HandleTopic(Line line)
858858+ {
859859+ var emit = new Emit();
860860+ if (HasChannel(line.Params[0]))
861861+ {
862862+ var channel = GetChannel(line.Params[0]);
863863+ emit.Channel = channel;
864864+ channel.Topic = line.Params[1];
865865+ channel.TopicSetter = line.Hostmask.ToString();
866866+ channel.TopicTime = DateTime.UtcNow;
831867 }
832868833833- /// <summary>
834834- /// Handles TOPIC command
835835- /// </summary>
836836- /// <param name="line"></param>
837837- /// <returns></returns>
838838- private Emit HandleTopic(Line line)
869869+ return emit;
870870+ }
871871+872872+ /// <summary>
873873+ /// Handles RPL_CREATIONTIME numeric
874874+ /// </summary>
875875+ /// <param name="line"></param>
876876+ /// <returns></returns>
877877+ private Emit HandleCreationTime(Line line)
878878+ {
879879+ var emit = new Emit();
880880+ if (HasChannel(line.Params[1]))
839881 {
840840- var emit = new Emit();
841841- if (HasChannel(line.Params[0]))
842842- {
843843- var channel = GetChannel(line.Params[0]);
844844- emit.Channel = channel;
845845- channel.Topic = line.Params[1];
846846- channel.TopicSetter = line.Hostmask.ToString();
847847- channel.TopicTime = DateTime.UtcNow;
848848- }
849849-850850- return emit;
882882+ var channel = GetChannel(line.Params[1]);
883883+ emit.Channel = channel;
884884+ channel.Created = DateTimeOffset
885885+ .FromUnixTimeSeconds(int.Parse(line.Params[2], CultureInfo.InvariantCulture)).DateTime;
851886 }
852887853853- /// <summary>
854854- /// Handles RPL_CREATIONTIME numeric
855855- /// </summary>
856856- /// <param name="line"></param>
857857- /// <returns></returns>
858858- private Emit HandleCreationTime(Line line)
888888+ return emit;
889889+ }
890890+891891+ /// <summary>
892892+ /// Handles RPL_NAMREPLY numeric
893893+ /// </summary>
894894+ /// <param name="line"></param>
895895+ /// <returns></returns>
896896+ private Emit HandleNames(Line line)
897897+ {
898898+ var emit = new Emit();
899899+ if (!HasChannel(line.Params[2]))
859900 {
860860- var emit = new Emit();
861861- if (HasChannel(line.Params[1]))
862862- {
863863- var channel = GetChannel(line.Params[1]);
864864- emit.Channel = channel;
865865- channel.Created = DateTimeOffset
866866- .FromUnixTimeSeconds(int.Parse(line.Params[2], CultureInfo.InvariantCulture)).DateTime;
867867- }
868868-869901 return emit;
870902 }
871903872872- /// <summary>
873873- /// Handles RPL_NAMREPLY numeric
874874- /// </summary>
875875- /// <param name="line"></param>
876876- /// <returns></returns>
877877- private Emit HandleNames(Line line)
904904+ var channel = GetChannel(line.Params[2]);
905905+ emit.Channel = channel;
906906+ var nicknames = line.Params[3].Split([' '], StringSplitOptions.RemoveEmptyEntries);
907907+ var users = new List<User>();
908908+ emit.Users = users;
909909+910910+ foreach (var nick in nicknames)
878911 {
879879- var emit = new Emit();
880880- if (!HasChannel(line.Params[2])) return emit;
912912+ var modes = nick.Select(c => ISupport.Prefix.FromPrefix(c)).TakeWhile(m => m != null).ToList();
913913+ var hostmask = new Hostmask(nick.Substring(modes.Count));
914914+ var user = GetUser(hostmask.NickName) ?? AddUser(hostmask.NickName);
881915882882- var channel = GetChannel(line.Params[2]);
883883- emit.Channel = channel;
884884- var nicknames = line.Params[3].Split(' ', StringSplitOptions.RemoveEmptyEntries);
885885- var users = new List<User>();
886886- emit.Users = users;
916916+ users.Add(user);
917917+ var channelUser = UserJoin(channel, user);
887918888888- foreach (var nick in nicknames)
919919+ if (hostmask.UserName != null)
889920 {
890890- var modes = "";
891891- foreach (var c in nick)
892892- {
893893- var mode = ISupport.Prefix.FromPrefix(c);
894894- if (mode != null)
895895- modes += mode;
896896- else
897897- break;
898898- }
921921+ user.UserName = hostmask.UserName;
922922+ }
899923900900- var hostmask = new Hostmask(nick[modes.Length..]);
901901- var user = GetUser(hostmask.NickName) ?? AddUser(hostmask.NickName);
924924+ if (hostmask.HostName != null)
925925+ {
926926+ user.HostName = hostmask.HostName;
927927+ }
902928903903- users.Add(user);
904904- var channelUser = UserJoin(channel, user);
929929+ if (IsMe(hostmask.NickName))
930930+ {
931931+ SelfHostmask(hostmask);
932932+ }
905933906906- if (hostmask.UserName != null) user.UserName = hostmask.UserName;
907907- if (hostmask.HostName != null) user.HostName = hostmask.HostName;
934934+ channelUser.Modes.AddRange(
935935+ modes.Select(c => c.ToString(CultureInfo.InvariantCulture)).Except(channelUser.Modes));
936936+ }
908937909909- if (IsMe(hostmask.NickName)) SelfHostmask(hostmask);
938938+ return emit;
939939+ }
910940911911- foreach (var mode in modes.Select(c => c.ToString(CultureInfo.InvariantCulture)))
912912- if (!channelUser.Modes.Contains(mode))
913913- channelUser.Modes.Add(mode);
914914- }
941941+ /// <summary>
942942+ /// Handles ERROR command
943943+ /// </summary>
944944+ /// <param name="line"></param>
945945+ /// <returns></returns>
946946+ private Emit HandleError(Line line)
947947+ {
948948+ Users.Clear();
949949+ Channels.Clear();
950950+ return new Emit();
951951+ }
915952916916- return emit;
953953+ /// <summary>
954954+ /// Handles QUIT command
955955+ /// </summary>
956956+ /// <param name="line"></param>
957957+ /// <returns></returns>
958958+ private Emit HandleQuit(Line line)
959959+ {
960960+ var emit = new Emit();
961961+ var nick = line.Hostmask.NickName;
962962+ if (line.Params.Any())
963963+ {
964964+ emit.Text = line.Params[0];
917965 }
918966919919- /// <summary>
920920- /// Handles ERROR command
921921- /// </summary>
922922- /// <param name="line"></param>
923923- /// <returns></returns>
924924- private Emit HandleError(Line line)
967967+ if (IsMe(nick) || line.Source == null)
925968 {
969969+ emit.Self = true;
926970 Users.Clear();
927971 Channels.Clear();
928928- return new Emit();
972972+ }
973973+ else if (HasUser(nick))
974974+ {
975975+ var user = GetUser(nick);
976976+ Users.Remove(user.NickNameLower);
977977+ emit.User = user;
978978+ foreach (var channel in user.Channels.Select(c => Channels[c]))
979979+ {
980980+ channel.Users.Remove(user.NickNameLower);
981981+ }
929982 }
930983931931- /// <summary>
932932- /// Handles QUIT command
933933- /// </summary>
934934- /// <param name="line"></param>
935935- /// <returns></returns>
936936- private Emit HandleQuit(Line line)
937937- {
938938- var emit = new Emit();
939939- var nick = line.Hostmask.NickName;
940940- if (line.Params.Any()) emit.Text = line.Params[0];
984984+ return emit;
985985+ }
941986942942- if (IsMe(nick) || line.Source == null)
987987+ /// <summary>
988988+ /// Handles RPL_LOGGEDOUT numeric
989989+ /// </summary>
990990+ /// <param name="line"></param>
991991+ /// <returns></returns>
992992+ private Emit HandleLoggedOut(Line line)
993993+ {
994994+ Account = null;
995995+ SelfHostmask(line.Params[1]);
996996+ return new Emit();
997997+ }
998998+999999+ /// <summary>
10001000+ /// Handles KICK command
10011001+ /// </summary>
10021002+ /// <param name="line"></param>
10031003+ /// <returns></returns>
10041004+ private Emit HandleKick(Line line)
10051005+ {
10061006+ var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2);
10071007+ if (kicked != null)
10081008+ {
10091009+ emit.UserTarget = kicked;
10101010+ if (IsMe(kicked.NickName))
9431011 {
9441012 emit.Self = true;
945945- Users.Clear();
946946- Channels.Clear();
9471013 }
948948- else if (HasUser(nick))
10141014+10151015+ var kicker = line.Hostmask.NickName;
10161016+ if (IsMe(kicker))
9491017 {
950950- var user = GetUser(nick);
951951- Users.Remove(user.NickNameLower);
952952- emit.User = user;
953953- foreach (var channel in user.Channels.Select(c => Channels[c]))
954954- channel.Users.Remove(user.NickNameLower);
10181018+ emit.SelfSource = true;
9551019 }
9561020957957- return emit;
10211021+ emit.UserSource = GetUser(kicker) ?? CreateUser(kicker);
9581022 }
9591023960960- /// <summary>
961961- /// Handles RPL_LOGGEDOUT numeric
962962- /// </summary>
963963- /// <param name="line"></param>
964964- /// <returns></returns>
965965- private Emit HandleLoggedOut(Line line)
10241024+ return emit;
10251025+ }
10261026+10271027+ /// <summary>
10281028+ /// Handles PART command
10291029+ /// </summary>
10301030+ /// <param name="line"></param>
10311031+ /// <returns></returns>
10321032+ private Emit HandlePart(Line line)
10331033+ {
10341034+ var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1);
10351035+ if (user != null)
9661036 {
967967- Account = null;
968968- SelfHostmask(line.Params[1]);
969969- return new Emit();
10371037+ emit.User = user;
10381038+ emit.Self = IsMe(user.NickName);
9701039 }
9711040972972- /// <summary>
973973- /// Handles KICK command
974974- /// </summary>
975975- /// <param name="line"></param>
976976- /// <returns></returns>
977977- private Emit HandleKick(Line line)
978978- {
979979- var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2);
980980- if (kicked != null)
981981- {
982982- emit.UserTarget = kicked;
983983- if (IsMe(kicked.NickName)) emit.Self = true;
984984-985985- var kicker = line.Hostmask.NickName;
986986- if (IsMe(kicker)) emit.SelfSource = true;
10411041+ return emit;
10421042+ }
9871043988988- emit.UserSource = GetUser(kicker) ?? CreateUser(kicker);
989989- }
10441044+ /// <summary>
10451045+ /// Handles JOIN command
10461046+ /// </summary>
10471047+ /// <param name="line"></param>
10481048+ /// <returns></returns>
10491049+ private Emit HandleJoin(Line line)
10501050+ {
10511051+ var extended = line.Params.Count == 3;
10521052+ var account = extended ? line.Params[1].Trim('*') : null;
10531053+ var realname = extended ? line.Params[2] : null;
10541054+ var emit = new Emit();
9901055991991- return emit;
992992- }
10561056+ var channelName = line.Params[0];
10571057+ var nick = line.Hostmask.NickName;
9931058994994- /// <summary>
995995- /// Handles PART command
996996- /// </summary>
997997- /// <param name="line"></param>
998998- /// <returns></returns>
999999- private Emit HandlePart(Line line)
10591059+ // handle own join
10601060+ if (IsMe(nick))
10001061 {
10011001- var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1);
10021002- if (user != null)
10621062+ emit.Self = true;
10631063+ if (!HasChannel(channelName))
10031064 {
10041004- emit.User = user;
10051005- emit.Self = IsMe(user.NickName);
10651065+ var channel = new Channel();
10661066+ channel.SetName(channelName, CaseFold(channelName));
10671067+ Channels[CaseFold(channelName)] = channel;
10061068 }
1007106910081008- return emit;
10701070+ SelfHostmask(line.Hostmask);
10711071+ if (extended)
10721072+ {
10731073+ Account = account;
10741074+ RealName = realname;
10751075+ }
10091076 }
1010107710111011- /// <summary>
10121012- /// Handles JOIN command
10131013- /// </summary>
10141014- /// <param name="line"></param>
10151015- /// <returns></returns>
10161016- private Emit HandleJoin(Line line)
10781078+ if (HasChannel(channelName))
10171079 {
10181018- var extended = line.Params.Count == 3;
10191019- var account = extended ? line.Params[1].Trim('*') : null;
10201020- var realname = extended ? line.Params[2] : null;
10211021- var emit = new Emit();
10801080+ var channel = GetChannel(channelName);
10811081+ emit.Channel = channel;
1022108210231023- var channelName = line.Params[0];
10241024- var nick = line.Hostmask.NickName;
10831083+ if (!HasUser(nick))
10841084+ {
10851085+ AddUser(nick);
10861086+ }
1025108710261026- // handle own join
10271027- if (IsMe(nick))
10881088+ var user = GetUser(nick);
10891089+ emit.User = user;
10901090+ if (line.Hostmask.UserName != null)
10281091 {
10291029- emit.Self = true;
10301030- if (!HasChannel(channelName))
10311031- {
10321032- var channel = new Channel();
10331033- channel.SetName(channelName, CaseFold(channelName));
10341034- Channels[CaseFold(channelName)] = channel;
10351035- }
10921092+ user.UserName = line.Hostmask.UserName;
10931093+ }
1036109410371037- SelfHostmask(line.Hostmask);
10381038- if (extended)
10391039- {
10401040- Account = account;
10411041- RealName = realname;
10421042- }
10951095+ if (line.Hostmask.HostName != null)
10961096+ {
10971097+ user.HostName = line.Hostmask.HostName;
10431098 }
1044109910451045- if (HasChannel(channelName))
11001100+ if (extended)
10461101 {
10471047- var channel = GetChannel(channelName);
10481048- emit.Channel = channel;
10491049-10501050- if (!HasUser(nick)) AddUser(nick);
10511051-10521052- var user = GetUser(nick);
10531053- emit.User = user;
10541054- if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName;
10551055- if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName;
10561056- if (extended)
10571057- {
10581058- user.Account = account;
10591059- user.RealName = realname;
10601060- }
10611061-10621062- UserJoin(channel, user);
11021102+ user.Account = account;
11031103+ user.RealName = realname;
10631104 }
1064110510651065- return emit;
11061106+ UserJoin(channel, user);
10661107 }
1067110810681068- /// <summary>
10691069- /// Handles NICK command
10701070- /// </summary>
10711071- /// <param name="line"></param>
10721072- /// <returns></returns>
10731073- private Emit HandleNick(Line line)
10741074- {
10751075- var newNick = line.Params[0];
10761076- var oldNick = line.Hostmask.NickName;
11091109+ return emit;
11101110+ }
1077111110781078- var emit = new Emit();
11121112+ /// <summary>
11131113+ /// Handles NICK command
11141114+ /// </summary>
11151115+ /// <param name="line"></param>
11161116+ /// <returns></returns>
11171117+ private Emit HandleNick(Line line)
11181118+ {
11191119+ var newNick = line.Params[0];
11201120+ var oldNick = line.Hostmask.NickName;
1079112110801080- if (HasUser(oldNick))
10811081- {
10821082- var user = GetUser(oldNick);
10831083- var oldNickLower = user.NickNameLower;
10841084- var newNickLower = CaseFold(newNick);
11221122+ var emit = new Emit();
1085112310861086- emit.User = user;
10871087- Users.Remove(oldNickLower);
10881088- Users[newNickLower] = user;
10891089- user.SetNickName(newNick, newNickLower);
11241124+ if (HasUser(oldNick))
11251125+ {
11261126+ var user = GetUser(oldNick);
11271127+ var oldNickLower = user.NickNameLower;
11281128+ var newNickLower = CaseFold(newNick);
1090112910911091- foreach (var channelLower in user.Channels)
10921092- {
10931093- var channel = GetChannel(channelLower);
10941094- var channelUser = channel.Users[oldNickLower];
10951095- channel.Users.Remove(oldNickLower);
10961096- channel.Users[newNickLower] = channelUser;
10971097- }
10981098- }
11301130+ emit.User = user;
11311131+ Users.Remove(oldNickLower);
11321132+ Users[newNickLower] = user;
11331133+ user.SetNickName(newNick, newNickLower);
1099113411001100- if (IsMe(oldNick))
11351135+ foreach (var channelLower in user.Channels)
11011136 {
11021102- emit.Self = true;
11031103- NickName = newNick;
11041104- NickNameLower = CaseFold(newNick);
11371137+ var channel = GetChannel(channelLower);
11381138+ var channelUser = channel.Users[oldNickLower];
11391139+ channel.Users.Remove(oldNickLower);
11401140+ channel.Users[newNickLower] = channelUser;
11051141 }
11061106-11071107- return emit;
11081142 }
1109114311101110- /// <summary>
11111111- /// Handles RPL_MOTDSTART and RPL_MOTD numerics
11121112- /// </summary>
11131113- /// <param name="line"></param>
11141114- /// <returns></returns>
11151115- private Emit HandleMotd(Line line)
11441144+ if (IsMe(oldNick))
11161145 {
11171117- if (line.Command == Numeric.RPL_MOTDSTART) Motd.Clear();
11181118-11191119- var emit = new Emit {Text = line.Params[1]};
11201120- Motd.Add(line.Params[1]);
11211121- return emit;
11461146+ emit.Self = true;
11471147+ NickName = newNick;
11481148+ NickNameLower = CaseFold(newNick);
11221149 }
1123115011241124- /// <summary>
11251125- /// Handles RPL_ISUPPORT numeric
11261126- /// </summary>
11271127- /// <param name="line"></param>
11281128- /// <returns></returns>
11291129- private Emit HandleISupport(Line line)
11511151+ return emit;
11521152+ }
11531153+11541154+ /// <summary>
11551155+ /// Handles RPL_MOTDSTART and RPL_MOTD numerics
11561156+ /// </summary>
11571157+ /// <param name="line"></param>
11581158+ /// <returns></returns>
11591159+ private Emit HandleMotd(Line line)
11601160+ {
11611161+ if (line.Command == Numeric.RPL_MOTDSTART)
11301162 {
11311131- ISupport = new ISupport();
11321132- ISupport.Parse(line.Params);
11331133- return new Emit();
11631163+ Motd.Clear();
11341164 }
1135116511361136- /// <summary>
11371137- /// Handles RPL_WELCOME numeric
11381138- /// </summary>
11391139- /// <param name="line"></param>
11401140- /// <returns></returns>
11411141- private Emit HandleWelcome(Line line)
11421142- {
11431143- NickName = line.Params[0];
11441144- NickNameLower = CaseFold(line.Params[0]);
11451145- Registered = true;
11461146- return new Emit();
11471147- }
11661166+ var emit = new Emit { Text = line.Params[1] };
11671167+ Motd.Add(line.Params[1]);
11681168+ return emit;
11691169+ }
11701170+11711171+ /// <summary>
11721172+ /// Handles RPL_ISUPPORT numeric
11731173+ /// </summary>
11741174+ /// <param name="line"></param>
11751175+ /// <returns></returns>
11761176+ private Emit HandleISupport(Line line)
11771177+ {
11781178+ ISupport = new ISupport();
11791179+ ISupport.Parse(line.Params);
11801180+ return new Emit();
11811181+ }
11821182+11831183+ /// <summary>
11841184+ /// Handles RPL_WELCOME numeric
11851185+ /// </summary>
11861186+ /// <param name="line"></param>
11871187+ /// <returns></returns>
11881188+ private Emit HandleWelcome(Line line)
11891189+ {
11901190+ NickName = line.Params[0];
11911191+ NickNameLower = CaseFold(line.Params[0]);
11921192+ Registered = true;
11931193+ return new Emit();
11481194 }
11491195}
+9-12
IRCStates/ServerDisconnectedException.cs
···11-using System;
11+namespace IRCStates;
2233-namespace IRCStates
33+public class ServerDisconnectedException : Exception
44{
55- public class ServerDisconnectedException : Exception
55+ public ServerDisconnectedException(string message) : base(message)
66 {
77- public ServerDisconnectedException(string message) : base(message)
88- {
99- }
77+ }
1081111- public ServerDisconnectedException(string message, Exception innerException) : base(message, innerException)
1212- {
1313- }
99+ public ServerDisconnectedException(string message, Exception innerException) : base(message, innerException)
1010+ {
1111+ }
14121515- public ServerDisconnectedException()
1616- {
1717- }
1313+ public ServerDisconnectedException()
1414+ {
1815 }
1916}
+15-21
IRCStates/User.cs
···11-using System.Collections.Generic;
11+namespace IRCStates;
2233-namespace IRCStates
33+public class User
44{
55- public class User
66- {
77- public string NickName { get; private set; }
88- public string NickNameLower { get; private set; }
55+ public string NickName { get; private set; }
66+ public string NickNameLower { get; private set; }
971010- public string UserName { get; set; }
1111- public string HostName { get; set; }
1212- public string RealName { get; set; }
1313- public string Account { get; set; }
1414- public string Away { get; set; }
1515- public HashSet<string> Channels { get; private set; } = new HashSet<string>();
88+ public string UserName { get; set; }
99+ public string HostName { get; set; }
1010+ public string RealName { get; set; }
1111+ public string Account { get; set; }
1212+ public string Away { get; set; }
1313+ public HashSet<string> Channels { get; private set; } = [];
16141717- public override string ToString()
1818- {
1919- return $"User(nickname={NickName})";
2020- }
1515+ public override string ToString() => $"User(nickname={NickName})";
21162222- public void SetNickName(string nick, string nickLower)
2323- {
2424- NickName = nick;
2525- NickNameLower = nickLower;
2626- }
1717+ public void SetNickName(string nick, string nickLower)
1818+ {
1919+ NickName = nick;
2020+ NickNameLower = nickLower;
2721 }
2822}
+92-42
IRCTokens/EnumerableExtensions.cs
···11-using System;
22-using System.Collections.Generic;
33-using System.Linq;
11+namespace IRCTokens;
4255-namespace IRCTokens
33+public static class EnumerableExtensions
64{
77- public static class EnumerableExtensions
55+ public static IEnumerable<byte[]> Split(this byte[] bytes, byte separator)
86 {
99- public static IEnumerable<byte[]> Split(this byte[] bytes, byte separator)
77+ if (bytes == null || bytes.Length == 0)
108 {
1111- if (bytes == null || bytes.Length == 0) return new List<byte[]>();
99+ return [];
1010+ }
12111313- var newLineIndices = bytes.Select((b, i) => b == separator ? i : -1).Where(i => i != -1).ToArray();
1414- var lines = new byte[newLineIndices.Length + 1][];
1515- var currentIndex = 0;
1616- var arrIndex = 0;
1212+ var newLineIndices = bytes.Select((b, i) => b == separator ? i : -1).Where(i => i != -1).ToArray();
1313+ var lines = new byte[newLineIndices.Length + 1][];
1414+ var currentIndex = 0;
1515+ var arrIndex = 0;
17161818- for (var i = 0; i < newLineIndices.Length && currentIndex < bytes.Length; ++i)
1919- {
2020- var n = new byte[newLineIndices[i] - currentIndex];
2121- Array.Copy(bytes, currentIndex, n, 0, newLineIndices[i] - currentIndex);
2222- currentIndex = newLineIndices[i] + 1;
2323- lines[arrIndex++] = n;
2424- }
1717+ for (var i = 0; i < newLineIndices.Length && currentIndex < bytes.Length; ++i)
1818+ {
1919+ var n = new byte[newLineIndices[i] - currentIndex];
2020+ Array.Copy(bytes, currentIndex, n, 0, newLineIndices[i] - currentIndex);
2121+ currentIndex = newLineIndices[i] + 1;
2222+ lines[arrIndex++] = n;
2323+ }
2424+2525+ // Handle the last string at the end of the array if there is one.
2626+ if (currentIndex < bytes.Length)
2727+ {
2828+ lines[arrIndex] = bytes.Skip(currentIndex).ToArray();
2929+ }
3030+ // We had a separator character at the end of a string. Rather than just allowing
3131+ // a null character, we'll replace the last element in the array with an empty string.
3232+ else if (arrIndex == newLineIndices.Length)
3333+ {
3434+ lines[arrIndex] = [];
3535+ }
25362626- // Handle the last string at the end of the array if there is one.
2727- if (currentIndex < bytes.Length)
2828- lines[arrIndex] = bytes.Skip(currentIndex).ToArray();
2929- else if (arrIndex == newLineIndices.Length)
3030- // We had a separator character at the end of a string. Rather than just allowing
3131- // a null character, we'll replace the last element in the array with an empty string.
3232- lines[arrIndex] = Array.Empty<byte>();
3737+ return lines.ToArray();
3838+ }
33393434- return lines.ToArray();
4040+ public static byte[] Trim(this IEnumerable<byte> bytes, byte separator)
4141+ {
4242+ if (bytes == null)
4343+ {
4444+ return [];
3545 }
36463737- public static byte[] Trim(this IEnumerable<byte> bytes, byte separator)
4747+ var byteList = new List<byte>(bytes);
4848+ var i = 0;
4949+5050+ if (!byteList.Any())
3851 {
3939- if (bytes == null) return Array.Empty<byte>();
5252+ return byteList.ToArray();
5353+ }
5454+5555+ while (byteList[i] == separator)
5656+ {
5757+ byteList.RemoveAt(i);
5858+ i++;
5959+ }
6060+6161+ i = byteList.Count - 1;
6262+ while (byteList[i] == separator)
6363+ {
6464+ byteList.RemoveAt(i);
6565+ i--;
6666+ }
6767+6868+ return byteList.ToArray();
6969+ }
7070+7171+#if !(REFERENCE_ASSEMBLY && (NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER))
7272+ /// <summary>
7373+ /// Bypasses a specified number of contiguous elements from the end of the sequence and returns the remaining elements.
7474+ /// Backported from <a href="https://github.com/dotnet/reactive/blob/ebab5fc37e8c0888f7b96107852a8794e9af1735/Ix.NET/Source/System.Interactive/System/Linq/Operators/SkipLast.cs">netcore</a>
7575+ /// </summary>
7676+ /// <typeparam name="TSource">Source sequence element type.</typeparam>
7777+ /// <param name="source">Source sequence.</param>
7878+ /// <param name="count">
7979+ /// The number of elements to skip from the end of the sequence before returning the remaining elements.
8080+ /// </param>
8181+ /// <returns>Sequence bypassing the specified number of elements counting from the end of the source sequence.</returns>
8282+ public static IEnumerable<TSource> SkipLast<TSource>(this IEnumerable<TSource> source, int count)
8383+ {
8484+ if (source == null)
8585+ {
8686+ throw new ArgumentNullException(nameof(source));
8787+ }
8888+8989+ if (count < 0)
9090+ {
9191+ throw new ArgumentOutOfRangeException(nameof(count));
9292+ }
40934141- var byteList = new List<byte>(bytes);
4242- var i = 0;
9494+ return SkipLastCore(source, count);
9595+ }
43964444- if (!byteList.Any()) return byteList.ToArray();
9797+ private static IEnumerable<TSource> SkipLastCore<TSource>(this IEnumerable<TSource> source, int count)
9898+ {
9999+ var q = new Queue<TSource>();
451004646- while (byteList[i] == separator)
4747- {
4848- byteList.RemoveAt(i);
4949- i++;
5050- }
101101+ foreach (var x in source)
102102+ {
103103+ q.Enqueue(x);
511045252- i = byteList.Count - 1;
5353- while (byteList[i] == separator)
105105+ if (q.Count > count)
54106 {
5555- byteList.RemoveAt(i);
5656- i--;
107107+ yield return q.Dequeue();
57108 }
5858-5959- return byteList.ToArray();
60109 }
61110 }
111111+#endif
62112}
+36-44
IRCTokens/Hostmask.cs
···11-using System;
11+namespace IRCTokens;
2233-namespace IRCTokens
33+/// <summary>Represents the three parts of a hostmask. Parse with the constructor.</summary>
44+public class Hostmask : IEquatable<Hostmask>
45{
55- /// <summary>
66- /// Represents the three parts of a hostmask. Parse with the constructor.
77- /// </summary>
88- public class Hostmask : IEquatable<Hostmask>
99- {
1010- private readonly string _source;
66+ private readonly string _source;
1171212- public Hostmask(string source)
88+ public Hostmask(string source)
99+ {
1010+ if (source == null)
1311 {
1414- if (source == null) return;
1515-1616- _source = source;
1717-1818- if (source.Contains('@', StringComparison.Ordinal))
1919- {
2020- var split = source.Split('@');
2121-2222- NickName = split[0];
2323- HostName = split[1];
2424- }
2525- else
2626- {
2727- NickName = source;
2828- }
2929-3030- if (NickName.Contains('!', StringComparison.Ordinal))
3131- {
3232- var userSplit = NickName.Split('!');
3333- NickName = userSplit[0];
3434- UserName = userSplit[1];
3535- }
1212+ return;
3613 }
37143838- public string NickName { get; set; }
3939- public string UserName { get; set; }
4040- public string HostName { get; set; }
1515+ _source = source;
41164242- public bool Equals(Hostmask other)
1717+ if (source.Contains('@'))
4318 {
4444- if (other == null) return false;
1919+ var split = source.Split('@');
45204646- return _source == other._source;
2121+ NickName = split[0];
2222+ HostName = split[1];
4723 }
4848-4949- public override string ToString()
2424+ else
5025 {
5151- return _source;
2626+ NickName = source;
5227 }
53285454- public override int GetHashCode()
2929+ if (NickName.Contains('!'))
5530 {
5656- return _source.GetHashCode(StringComparison.Ordinal);
3131+ var userSplit = NickName.Split('!');
3232+ NickName = userSplit[0];
3333+ UserName = userSplit[1];
5734 }
3535+ }
58365959- public override bool Equals(object obj)
3737+ public string NickName { get; set; }
3838+ public string UserName { get; set; }
3939+ public string HostName { get; set; }
4040+4141+ public bool Equals(Hostmask other)
4242+ {
4343+ if (other == null)
6044 {
6161- return Equals(obj as Hostmask);
4545+ return false;
6246 }
4747+4848+ return _source == other._source;
6349 }
5050+5151+ public override string ToString() => _source;
5252+5353+ public override int GetHashCode() => _source.GetHashCode();
5454+5555+ public override bool Equals(object obj) => Equals(obj as Hostmask);
6456}