From 549c80740d7c26b2c1de2fb5ee07f430115f56ae Mon Sep 17 00:00:00 2001 From: flashwave Date: Sun, 19 May 2024 21:02:17 +0000 Subject: [PATCH] Rewrote user and channel collections. --- SharpChat/ChannelInfo.cs | 20 +- SharpChat/ChannelsContext.cs | 123 +++++++++ SharpChat/ChannelsUsersContext.cs | 257 ++++++++++++++++++ SharpChat/ChatContext.cs | 159 +++++------ SharpChat/Commands/ChannelCreateCommand.cs | 8 +- SharpChat/Commands/ChannelDeleteCommand.cs | 2 +- SharpChat/Commands/ChannelJoinCommand.cs | 2 +- SharpChat/Commands/KickBanCommand.cs | 7 +- SharpChat/Commands/MessageWhisperCommand.cs | 3 +- SharpChat/Commands/PardonAddressCommand.cs | 1 - SharpChat/Commands/PardonUserCommand.cs | 6 +- SharpChat/Commands/UserAFKCommand.cs | 3 +- SharpChat/Commands/UserNickCommand.cs | 4 +- SharpChat/Commands/WhoCommand.cs | 10 +- SharpChat/Commands/WhoisCommand.cs | 3 +- SharpChat/Config/IConfig.cs | 4 - SharpChat/Events/IChatEvent.cs | 8 +- SharpChat/Misuzu/MisuzuAuthInfo.cs | 3 +- SharpChat/Packet/AuthSuccessPacket.cs | 6 +- SharpChat/Packet/ChannelCreatePacket.cs | 6 +- .../Packet/ChannelCreateResponsePacket.cs | 7 +- .../ChannelDeleteNotAllowedErrorPacket.cs | 7 +- SharpChat/Packet/ChannelDeletePacket.cs | 9 +- .../Packet/ChannelDeleteResponsePacket.cs | 7 +- .../Packet/ChannelNameInUseErrorPacket.cs | 7 +- .../Packet/ChannelNotFoundErrorPacket.cs | 7 +- .../Packet/ChannelPasswordWrongErrorPacket.cs | 7 +- .../Packet/ChannelRankTooLowErrorPacket.cs | 7 +- SharpChat/Packet/ChannelUpdatePacket.cs | 8 +- SharpChat/Packet/ChannelsPopulatePacket.cs | 5 +- .../Packet/CommandNotAllowedErrorPacket.cs | 7 +- SharpChat/Packet/MOTDPacket.cs | 2 +- SharpChat/Packet/MessageAddPacket.cs | 2 +- SharpChat/Packet/MessageBroadcastPacket.cs | 2 +- SharpChat/Packet/MessagePopulatePacket.cs | 17 +- .../Packet/UserChannelForceJoinPacket.cs | 9 +- SharpChat/Packet/UserChannelJoinPacket.cs | 4 +- SharpChat/Packet/UserUpdatePacket.cs | 4 +- SharpChat/Packet/UsersPopulatePacket.cs | 3 +- .../Packet/WhoChannelNotFoundErrorPacket.cs | 7 +- SharpChat/Packet/WhoChannelResponsePacket.cs | 6 +- SharpChat/PacketHandlerContext.cs | 4 +- SharpChat/PacketHandlers/AuthHandler.cs | 6 +- SharpChat/PacketHandlers/PingHandler.cs | 2 +- .../PacketHandlers/SendMessageHandler.cs | 18 +- SharpChat/SockChatServer.cs | 12 +- SharpChat/SockChatUtility.cs | 65 +++++ SharpChat/UserInfo.cs | 27 +- SharpChat/UsersContext.cs | 112 ++++++++ SharpChat/Utility.cs | 10 - 50 files changed, 782 insertions(+), 243 deletions(-) create mode 100644 SharpChat/ChannelsContext.cs create mode 100644 SharpChat/ChannelsUsersContext.cs create mode 100644 SharpChat/SockChatUtility.cs create mode 100644 SharpChat/UsersContext.cs delete mode 100644 SharpChat/Utility.cs diff --git a/SharpChat/ChannelInfo.cs b/SharpChat/ChannelInfo.cs index 849a131..aa643d8 100644 --- a/SharpChat/ChannelInfo.cs +++ b/SharpChat/ChannelInfo.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace SharpChat { +namespace SharpChat { public class ChannelInfo { public string Name { get; } public string Password { get; set; } @@ -12,6 +9,9 @@ namespace SharpChat { public bool HasPassword => !string.IsNullOrWhiteSpace(Password); + public bool IsPublic + => !IsTemporary && Rank < 1 && !HasPassword; + public ChannelInfo( string name, string? password = null, @@ -26,22 +26,10 @@ namespace SharpChat { OwnerId = ownerId; } - public bool NameEquals(string? name) { - return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase); - } - public bool IsOwner(UserInfo user) { return OwnerId > 0 && user != null && OwnerId == user.UserId; } - - public static bool CheckName(string name) { - return !string.IsNullOrWhiteSpace(name) && name.All(CheckNameChar); - } - - public static bool CheckNameChar(char c) { - return char.IsLetter(c) || char.IsNumber(c) || c == '-' || c == '_'; - } } } diff --git a/SharpChat/ChannelsContext.cs b/SharpChat/ChannelsContext.cs new file mode 100644 index 0000000..b5497a1 --- /dev/null +++ b/SharpChat/ChannelsContext.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SharpChat { + public class ChannelsContext { + private readonly List Channels = new(); + + public ChannelInfo? MainChannel { get; private set; } + public int TotalCount { get; private set; } + public int PublicCount { get; private set; } + + public ChannelInfo[] All => Channels.ToArray(); + + public ChannelInfo? Get( + string? name, + Func? sanitise = null + ) { + if(string.IsNullOrWhiteSpace(name)) + return null; + + foreach(ChannelInfo info in Channels) { + string chanName = info.Name; + if(sanitise != null) + chanName = sanitise(chanName); + if(!chanName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + continue; + + return info; + } + + return null; + } + + public ChannelInfo[] GetMany( + string[]? names = null, + Func? sanitiseName = null, + int minRank = 0, + bool? isPublic = null + ) { + List chans = new(); + + names ??= Array.Empty(); + for(int i = 0; i < names.Length; ++i) + names[i] = names[i].ToLowerInvariant(); + + foreach(ChannelInfo info in Channels) { + if(info.Rank > minRank) + continue; + + if(isPublic != null && info.IsPublic != isPublic) + continue; + + if(names?.Length > 0) { + string chanName = info.Name; + if(sanitiseName != null) + chanName = sanitiseName(chanName); + + bool match = false; + foreach(string name in names) + if(match = chanName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + break; + + if(!match) + continue; + } + + chans.Add(info); + } + + return chans.ToArray(); + } + + public void Add( + ChannelInfo info, + bool forceMain = false, + Func? sanitiseName = null + ) { + if(Get(info.Name, sanitiseName) != null) + throw new ArgumentException("A channel with this name has already been registered.", nameof(info)); + if(string.IsNullOrWhiteSpace(info.Name)) + throw new ArgumentException("Channel names may not be blank.", nameof(info)); + // todo: there should be more restrictions on channel names + + Channels.Add(info); + + ++TotalCount; + if(info.IsPublic) + ++PublicCount; + + if(forceMain || MainChannel == null) + MainChannel = info; + } + + public void Remove( + ChannelInfo info, + Func? sanitiseName = null + ) { + Remove(info.Name, sanitiseName); + } + + public void Remove( + string? name, + Func? sanitise = null + ) { + if(string.IsNullOrWhiteSpace(name)) + return; + + ChannelInfo? info = Get(name, sanitise); + if(info == null) + return; + + Channels.Remove(info); + + --TotalCount; + if(info.IsPublic) + --PublicCount; + + if(MainChannel == info) + MainChannel = Channels.FirstOrDefault(c => !c.IsPublic); + } + } +} diff --git a/SharpChat/ChannelsUsersContext.cs b/SharpChat/ChannelsUsersContext.cs new file mode 100644 index 0000000..7fcbcdf --- /dev/null +++ b/SharpChat/ChannelsUsersContext.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SharpChat { + public class ChannelsUsersContext { + private readonly Dictionary> ChannelUsers = new(); + private readonly Dictionary> UserChannels = new(); + private readonly Dictionary UserLastChannel = new(); + + public string GetUserLastChannel(long userId) { + return UserLastChannel.ContainsKey(userId) + ? UserLastChannel[userId] + : string.Empty; + } + + public string GetUserLastChannel(UserInfo userInfo) { + return GetUserLastChannel(userInfo.UserId); + } + + public void SetUserLastChannel(long userId, string channelName) { + channelName = channelName.ToLowerInvariant(); + if(UserLastChannel.ContainsKey(userId)) + UserLastChannel[userId] = channelName; + else + UserLastChannel.Add(userId, channelName); + } + + public void SetUserLastChannel(UserInfo userInfo, ChannelInfo channelInfo) { + SetUserLastChannel(userInfo.UserId, channelInfo.Name); + } + + public void DeleteUserLastChannel(long userId) { + if(UserLastChannel.ContainsKey(userId)) + UserLastChannel.Remove(userId); + } + + public void DeleteUserLastChannel(UserInfo userInfo) { + DeleteUserLastChannel(userInfo.UserId); + } + + public bool IsUserLastChannel(long userId, string channelName) { + return !string.IsNullOrWhiteSpace(channelName) + && GetUserLastChannel(userId).Equals(channelName, StringComparison.InvariantCultureIgnoreCase); + } + + public bool IsUserLastChannel(UserInfo userInfo, ChannelInfo channelInfo) { + return IsUserLastChannel(userInfo.UserId, channelInfo.Name); + } + + public string[] GetUserChannelNames(long userId) { + if(!UserChannels.ContainsKey(userId)) + return Array.Empty(); + + return UserChannels[userId].ToArray(); + } + + public string[] GetUserChannelNames(UserInfo userInfo) { + return GetUserChannelNames(userInfo.UserId); + } + + public long[] GetChannelUserIds(string channelName) { + channelName = channelName.ToLowerInvariant(); + if(!ChannelUsers.ContainsKey(channelName)) + return Array.Empty(); + + return ChannelUsers[channelName].ToArray(); + } + + public long[] GetChannelUserIds(string channelName, Func sanitise) { + foreach(KeyValuePair> kvp in ChannelUsers) + if(sanitise(kvp.Key).Equals(channelName, StringComparison.InvariantCultureIgnoreCase)) + return kvp.Value.ToArray(); + + return Array.Empty(); + } + + public long[] GetChannelUserIds(ChannelInfo channelInfo) { + return GetChannelUserIds(channelInfo.Name); + } + + public void Join(string channelName, long userId) { + channelName = channelName.ToLowerInvariant(); + if(ChannelUsers.ContainsKey(channelName)) + ChannelUsers[channelName].Add(userId); + else + ChannelUsers.Add(channelName, new HashSet { userId }); + + if(UserChannels.ContainsKey(userId)) + UserChannels[userId].Add(channelName); + else + UserChannels.Add(userId, new HashSet { channelName }); + + SetUserLastChannel(userId, channelName); + } + + public void Join(ChannelInfo channelInfo, UserInfo userInfo) { + Join(channelInfo.Name, userInfo.UserId); + } + + public void Leave(string channelName, long userId) { + channelName = channelName.ToLowerInvariant(); + if(ChannelUsers.ContainsKey(channelName)) { + if(ChannelUsers[channelName].Count < 2) + ChannelUsers.Remove(channelName); + else + ChannelUsers[channelName].Remove(userId); + } + + if(UserChannels.ContainsKey(userId)) { + if(UserChannels[userId].Count < 2) + UserChannels.Remove(userId); + else + UserChannels[userId].Remove(channelName); + } + + if(IsUserLastChannel(userId, channelName)) + DeleteUserLastChannel(userId); + } + + public void Leave(ChannelInfo channelInfo, UserInfo userInfo) { + Leave(channelInfo.Name, userInfo.UserId); + } + + public bool Has(string channelName, long userId) { + channelName = channelName.ToLowerInvariant(); + return ChannelUsers.ContainsKey(channelName) + && ChannelUsers[channelName].Contains(userId); + } + + public bool Has(ChannelInfo channelInfo, UserInfo userInfo) { + return Has(channelInfo.Name, userInfo.UserId); + } + + public long[] FilterUsers(string channelName, long[] userIds) { + if(userIds.Length < 1) + return userIds; + + channelName = channelName.ToLowerInvariant(); + if(!ChannelUsers.ContainsKey(channelName)) + return Array.Empty(); + + List filtered = new(); + HashSet channelUserIds = ChannelUsers[channelName]; + foreach(long userId in userIds) + if(channelUserIds.Contains(userId)) + filtered.Add(userId); + + return filtered.ToArray(); + } + + public UserInfo[] FilterUsers(ChannelInfo channelInfo, UserInfo[] userInfos) { + if(userInfos.Length < 1) + return userInfos; + + long[] filteredIds = FilterUsers(channelInfo.Name, userInfos.Select(u => u.UserId).ToArray()); + if(filteredIds.Length < 1) + return Array.Empty(); + + return userInfos.Where(u => filteredIds.Contains(u.UserId)).ToArray(); + } + + public bool HasUsers(string channelName, long[] userIds) { + return FilterUsers(channelName, userIds).SequenceEqual(userIds); + } + + public bool HasUsers(ChannelInfo channelInfo, UserInfo[] userInfos) { + return HasUsers(channelInfo.Name, userInfos.Select(u => u.UserId).ToArray()); + } + + public string[] FilterChannels(long userId, string[] channelNames) { + if(channelNames.Length < 1) + return channelNames; + + if(!UserChannels.ContainsKey(userId)) + return Array.Empty(); + + List filtered = new(); + HashSet userChannelNames = UserChannels[userId]; + foreach(string channelName in userChannelNames) + if(userChannelNames.Contains(channelName)) + filtered.Add(channelName); + + return filtered.ToArray(); + } + + public ChannelInfo[] FilterChannels(UserInfo userInfo, ChannelInfo[] channelInfos) { + if(channelInfos.Length < 1) + return channelInfos; + + string[] filteredNames = FilterChannels(userInfo.UserId, channelInfos.Select(c => c.Name).ToArray()); + if(filteredNames.Length < 1) + return Array.Empty(); + + return channelInfos.Where(c => filteredNames.Contains(c.Name.ToLowerInvariant())).ToArray(); + } + + public bool HasChannels(long userId, string[] channelNames) { + if(!UserChannels.ContainsKey(userId)) + return false; + + HashSet userChannelNames = UserChannels[userId]; + foreach(string channelName in channelNames) + if(!userChannelNames.Contains(channelName.ToLowerInvariant())) + return false; + + return true; + } + + public bool HasChannels(UserInfo userInfo, ChannelInfo[] channelInfos) { + return HasChannels(userInfo.UserId, channelInfos.Select(c => c.Name).ToArray()); + } + + public void DeleteUser(long userId) { + if(!UserChannels.ContainsKey(userId)) + return; + + HashSet channelNames = UserChannels[userId]; + UserChannels.Remove(userId); + DeleteUserLastChannel(userId); + + foreach(string channelName in channelNames) { + if(!ChannelUsers.ContainsKey(channelName)) + continue; + + ChannelUsers[channelName].Remove(userId); + } + } + + public void DeleteUser(UserInfo userInfo) { + DeleteUser(userInfo.UserId); + } + + public void DeleteChannel(string channelName) { + channelName = channelName.ToLowerInvariant(); + if(!ChannelUsers.ContainsKey(channelName)) + return; + + HashSet userIds = ChannelUsers[channelName]; + ChannelUsers.Remove(channelName); + + foreach(long userId in userIds) { + if(!UserChannels.ContainsKey(userId)) + continue; + + UserChannels[userId].Remove(channelName); + + if(IsUserLastChannel(userId, channelName)) + DeleteUserLastChannel(userId); + } + } + + public void DeleteChannel(ChannelInfo channelInfo) { + DeleteChannel(channelInfo.Name); + } + } +} diff --git a/SharpChat/ChatContext.cs b/SharpChat/ChatContext.cs index cfc1527..6b53505 100644 --- a/SharpChat/ChatContext.cs +++ b/SharpChat/ChatContext.cs @@ -9,17 +9,14 @@ using System.Threading; namespace SharpChat { public class ChatContext { - public record ChannelUserAssoc(long UserId, string ChannelName); - public readonly SemaphoreSlim ContextAccess = new(1, 1); - public Dictionary Channels { get; } = new(); + public ChannelsContext Channels { get; } = new(); public List Connections { get; } = new(); - public Dictionary Users { get; } = new(); + public UsersContext Users { get; } = new(); public IEventStorage Events { get; } - public HashSet ChannelUsers { get; } = new(); + public ChannelsUsersContext ChannelsUsers { get; } = new(); public Dictionary UserRateLimiters { get; } = new(); - public Dictionary UserLastChannel { get; } = new(); public ChatContext(IEventStorage evtStore) { Events = evtStore; @@ -41,7 +38,7 @@ namespace SharpChat { if(targetIds.Length != 2) return; - UserInfo[] users = Users.Where(kvp => targetIds.Contains(kvp.Key)).Select(kvp => kvp.Value).ToArray(); + UserInfo[] users = Users.GetMany(targetIds); UserInfo? target = users.FirstOrDefault(u => u.UserId != mce.SenderId); if(target == null) return; @@ -51,12 +48,12 @@ namespace SharpChat { mce.MessageId, DateTimeOffset.Now, mce.SenderId, - mce.SenderId == user.UserId ? $"{target.LegacyName} {mce.MessageText}" : mce.MessageText, + mce.SenderId == user.UserId ? $"{SockChatUtility.GetUserName(target)} {mce.MessageText}" : mce.MessageText, mce.IsAction, true )); } else { - ChannelInfo? channel = Channels.Values.FirstOrDefault(c => c.NameEquals(mce.ChannelName)); + ChannelInfo? channel = Channels.Get(mce.ChannelName, SockChatUtility.SanitiseChannelName); if(channel != null) SendTo(channel, new MessageAddPacket( mce.MessageId, @@ -92,7 +89,7 @@ namespace SharpChat { if(removed > 0) Logger.Write($"Removed {removed} nuked connections from the list."); - foreach(UserInfo user in Users.Values) + foreach(UserInfo user in Users.All) if(!Connections.Any(conn => conn.User == user)) { HandleDisconnect(user, UserDisconnectReason.TimeOut); Logger.Write($"Timed out {user} (no more connections)."); @@ -108,28 +105,12 @@ namespace SharpChat { } } - public bool IsInChannel(UserInfo? user, ChannelInfo? channel) { - return user != null - && channel != null - && ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name)); - } - - public string[] GetUserChannelNames(UserInfo user) { - return ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName).ToArray(); - } - public ChannelInfo[] GetUserChannels(UserInfo user) { - string[] names = GetUserChannelNames(user); - return Channels.Values.Where(c => names.Any(n => c.NameEquals(n))).ToArray(); - } - - public long[] GetChannelUserIds(ChannelInfo channel) { - return ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId).ToArray(); + return Channels.GetMany(ChannelsUsers.GetUserChannelNames(user)); } public UserInfo[] GetChannelUsers(ChannelInfo channel) { - long[] targetIds = GetChannelUserIds(channel); - return Users.Values.Where(u => targetIds.Contains(u.UserId)).ToArray(); + return Users.GetMany(ChannelsUsers.GetChannelUserIds(channel)); } public void UpdateUser( @@ -192,11 +173,11 @@ namespace SharpChat { if(hasChanged) { if(previousName != null) - SendToUserChannels(user, new UserUpdateNotificationPacket(previousName, user.LegacyNameWithStatus)); + SendToUserChannels(user, new UserUpdateNotificationPacket(previousName, SockChatUtility.GetUserNameWithStatus(user))); SendToUserChannels(user, new UserUpdatePacket( user.UserId, - user.LegacyNameWithStatus, + SockChatUtility.GetUserNameWithStatus(user), user.Colour, user.Rank, user.Permissions @@ -222,10 +203,10 @@ namespace SharpChat { public void HandleChannelEventLog(string channelName, Action handler) { foreach(StoredEventInfo msg in Events.GetChannelEventLog(channelName)) handler(msg.Type switch { - "user:connect" => new UserConnectLogPacket(msg.Created, msg.Sender?.LegacyName ?? string.Empty), + "user:connect" => new UserConnectLogPacket(msg.Created, msg.Sender == null ? string.Empty : SockChatUtility.GetUserName(msg.Sender)), "user:disconnect" => new UserDisconnectLogPacket( msg.Created, - msg.Sender?.LegacyNameWithStatus ?? string.Empty, + msg.Sender == null ? string.Empty : SockChatUtility.GetUserNameWithStatus(msg.Sender), (UserDisconnectReason)msg.Data.RootElement.GetProperty("reason").GetByte() ), _ => new MessagePopulatePacket(msg), @@ -233,11 +214,11 @@ namespace SharpChat { } public void HandleJoin(UserInfo user, ChannelInfo chan, ConnectionInfo conn, int maxMsgLength) { - if(!IsInChannel(user, chan)) { + if(!ChannelsUsers.Has(chan, user)) { SendTo(chan, new UserConnectPacket( DateTimeOffset.Now, user.UserId, - user.LegacyNameWithStatus, + SockChatUtility.GetUserNameWithStatus(user), user.Colour, user.Rank, user.Permissions @@ -247,7 +228,7 @@ namespace SharpChat { conn.Send(new AuthSuccessPacket( user.UserId, - user.LegacyNameWithStatus, + SockChatUtility.GetUserNameWithStatus(user), user.Colour, user.Rank, user.Permissions, @@ -255,32 +236,42 @@ namespace SharpChat { maxMsgLength )); conn.Send(new UsersPopulatePacket(GetChannelUsers(chan).Except(new[] { user }).Select( - user => new UsersPopulatePacket.ListEntry(user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions, true) + user => new UsersPopulatePacket.ListEntry( + user.UserId, + SockChatUtility.GetUserNameWithStatus(user), + user.Colour, + user.Rank, + user.Permissions, + true + ) ).OrderByDescending(user => user.Rank).ToArray())); HandleChannelEventLog(chan.Name, p => conn.Send(p)); - conn.Send(new ChannelsPopulatePacket(Channels.Values.Where(c => c.Rank <= user.Rank).Select( + conn.Send(new ChannelsPopulatePacket(Channels.GetMany(isPublic: true, minRank: user.Rank).Select( channel => new ChannelsPopulatePacket.ListEntry(channel.Name, channel.HasPassword, channel.IsTemporary) ).ToArray())); - Users.Add(user.UserId, user); + if(Users.Get(userId: user.UserId) == null) + Users.Add(user); - ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name)); - UserLastChannel[user.UserId] = chan; + ChannelsUsers.Join(chan.Name, user.UserId); } public void HandleDisconnect(UserInfo user, UserDisconnectReason reason = UserDisconnectReason.Leave) { UpdateUser(user, status: UserStatus.Offline); Users.Remove(user.UserId); - UserLastChannel.Remove(user.UserId); ChannelInfo[] channels = GetUserChannels(user); + ChannelsUsers.DeleteUser(user); foreach(ChannelInfo chan in channels) { - ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name)); - - SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, reason)); + SendTo(chan, new UserDisconnectPacket( + DateTimeOffset.Now, + user.UserId, + SockChatUtility.GetUserNameWithStatus(user), + reason + )); Events.AddEvent("user:disconnect", user, chan, new { reason = (int)reason }, StoredEventFlags.Log); if(chan.IsTemporary && chan.IsOwner(user)) @@ -289,7 +280,7 @@ namespace SharpChat { } public void SwitchChannel(UserInfo user, ChannelInfo chan, string password) { - if(UserLastChannel.TryGetValue(user.UserId, out ChannelInfo? ulc) && chan == ulc) { + if(ChannelsUsers.IsUserLastChannel(user, chan)) { ForceChannel(user); return; } @@ -312,29 +303,44 @@ namespace SharpChat { } public void ForceChannelSwitch(UserInfo user, ChannelInfo chan) { - if(!Channels.ContainsValue(chan)) - return; + ChannelInfo? oldChan = Channels.Get(ChannelsUsers.GetUserLastChannel(user)); - ChannelInfo oldChan = UserLastChannel[user.UserId]; + if(oldChan != null) { + SendTo(oldChan, new UserChannelLeavePacket(user.UserId)); + Events.AddEvent("chan:leave", user, oldChan, flags: StoredEventFlags.Log); + } - SendTo(oldChan, new UserChannelLeavePacket(user.UserId)); - Events.AddEvent("chan:leave", user, oldChan, flags: StoredEventFlags.Log); - SendTo(chan, new UserChannelJoinPacket(user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions)); - Events.AddEvent("chan:join", user, oldChan, flags: StoredEventFlags.Log); + SendTo(chan, new UserChannelJoinPacket( + user.UserId, + SockChatUtility.GetUserNameWithStatus(user), + user.Colour, + user.Rank, + user.Permissions + )); + + if(oldChan != null) + Events.AddEvent("chan:join", user, oldChan, flags: StoredEventFlags.Log); SendTo(user, new ContextClearPacket(ContextClearPacket.ClearMode.MessagesUsers)); SendTo(user, new UsersPopulatePacket(GetChannelUsers(chan).Except(new[] { user }).Select( - user => new UsersPopulatePacket.ListEntry(user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions, true) + user => new UsersPopulatePacket.ListEntry( + user.UserId, + SockChatUtility.GetUserNameWithStatus(user), + user.Colour, + user.Rank, + user.Permissions, + true + ) ).OrderByDescending(u => u.Rank).ToArray())); HandleChannelEventLog(chan.Name, p => SendTo(user, p)); ForceChannel(user, chan); - ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, oldChan.Name)); - ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name)); - UserLastChannel[user.UserId] = chan; + if(oldChan != null) + ChannelsUsers.Leave(oldChan, user); + ChannelsUsers.Join(chan, user); - if(oldChan.IsTemporary && oldChan.IsOwner(user)) + if(oldChan != null && oldChan.IsTemporary && oldChan.IsOwner(user)) RemoveChannel(oldChan); } @@ -346,22 +352,21 @@ namespace SharpChat { public void SendTo(UserInfo user, IServerPacket packet) { foreach(ConnectionInfo conn in Connections) - if(conn.IsAlive && conn.User == user) + if(conn.IsAuthed && conn.User!.UserId == user.UserId) conn.Send(packet); } public void SendTo(ChannelInfo channel, IServerPacket packet) { - // might be faster to grab the users first and then cascade into that SendTo - IEnumerable conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel)); - foreach(ConnectionInfo conn in conns) - conn.Send(packet); + long[] userIds = ChannelsUsers.GetChannelUserIds(channel); + foreach(ConnectionInfo conn in Connections) + if(conn.IsAuthed && userIds.Contains(conn.User!.UserId)) + conn.Send(packet); } public void SendToUserChannels(UserInfo user, IServerPacket packet) { - IEnumerable chans = Channels.Values.Where(c => IsInChannel(user, c)); - IEnumerable conns = Connections.Where(conn => conn.IsAuthed && ChannelUsers.Any(cu => cu.UserId == conn.User?.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName)))); - foreach(ConnectionInfo conn in conns) - conn.Send(packet); + ChannelInfo[] chans = GetUserChannels(user); + foreach(ChannelInfo chan in chans) + SendTo(chan, packet); } public IPAddress[] GetRemoteAddresses(UserInfo user) { @@ -369,10 +374,9 @@ namespace SharpChat { } public void ForceChannel(UserInfo user, ChannelInfo? chan = null) { - if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan)) - throw new ArgumentException("no channel???"); - - SendTo(user, new UserChannelForceJoinPacket(chan.Name)); + chan ??= Channels.Get(ChannelsUsers.GetUserLastChannel(user)); + if(chan != null) + SendTo(user, new UserChannelForceJoinPacket(chan.Name)); } public void UpdateChannel( @@ -381,9 +385,6 @@ namespace SharpChat { int? minRank = null, string? password = null ) { - if(!Channels.ContainsValue(channel)) - throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel)); - string prevName = channel.Name; if(temporary.HasValue) @@ -396,21 +397,21 @@ namespace SharpChat { channel.Password = password; // TODO: Users that no longer have access to the channel/gained access to the channel by the rank change should receive delete and create packets respectively - foreach(UserInfo user in Users.Values.Where(u => u.Rank >= channel.Rank)) { + // the server currently doesn't keep track of what channels a user is already aware of so can't really simulate this yet. + foreach(UserInfo user in Users.GetMany(minRank: channel.Rank)) SendTo(user, new ChannelUpdatePacket(prevName, channel.Name, channel.HasPassword, channel.IsTemporary)); - } } public void RemoveChannel(ChannelInfo channel) { - if(channel == null || !Channels.Any()) + if(channel == null || Channels.PublicCount > 1) return; - ChannelInfo? defaultChannel = Channels.Values.FirstOrDefault(); + ChannelInfo? defaultChannel = Channels.MainChannel; if(defaultChannel == null) return; // Remove channel from the listing - Channels.Remove(channel.Name); + Channels.Remove(channel); // Move all users back to the main channel // TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel. @@ -418,7 +419,7 @@ namespace SharpChat { SwitchChannel(user, defaultChannel, string.Empty); // Broadcast deletion of channel - foreach(UserInfo user in Users.Values.Where(u => u.Rank >= channel.Rank)) + foreach(UserInfo user in Users.GetMany(minRank: channel.Rank)) SendTo(user, new ChannelDeletePacket(channel.Name)); } } diff --git a/SharpChat/Commands/ChannelCreateCommand.cs b/SharpChat/Commands/ChannelCreateCommand.cs index cb436f4..203ac07 100644 --- a/SharpChat/Commands/ChannelCreateCommand.cs +++ b/SharpChat/Commands/ChannelCreateCommand.cs @@ -33,12 +33,12 @@ namespace SharpChat.Commands { string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0)); - if(!ChannelInfo.CheckName(createChanName)) { + if(!SockChatUtility.CheckChannelName(createChanName)) { ctx.Chat.SendTo(ctx.User, new ChannelNameFormatErrorPacket()); return; } - if(ctx.Chat.Channels.Values.Any(c => c.NameEquals(createChanName))) { + if(ctx.Chat.Channels.Get(createChanName, SockChatUtility.SanitiseChannelName) != null) { ctx.Chat.SendTo(ctx.User, new ChannelNameInUseErrorPacket(createChanName)); return; } @@ -50,8 +50,8 @@ namespace SharpChat.Commands { ownerId: ctx.User.UserId ); - ctx.Chat.Channels.Add(createChan.Name, createChan); - foreach(UserInfo ccu in ctx.Chat.Users.Values.Where(u => u.Rank >= ctx.Channel.Rank)) + ctx.Chat.Channels.Add(createChan, sanitiseName: SockChatUtility.SanitiseChannelName); + foreach(UserInfo ccu in ctx.Chat.Users.GetMany(minRank: ctx.Channel.Rank)) ctx.Chat.SendTo(ccu, new ChannelCreatePacket( ctx.Channel.Name, ctx.Channel.HasPassword, diff --git a/SharpChat/Commands/ChannelDeleteCommand.cs b/SharpChat/Commands/ChannelDeleteCommand.cs index 17a3a45..8abe99e 100644 --- a/SharpChat/Commands/ChannelDeleteCommand.cs +++ b/SharpChat/Commands/ChannelDeleteCommand.cs @@ -17,7 +17,7 @@ namespace SharpChat.Commands { } string delChanName = string.Join('_', ctx.Args); - ChannelInfo? delChan = ctx.Chat.Channels.Values.FirstOrDefault(c => c.NameEquals(delChanName)); + ChannelInfo? delChan = ctx.Chat.Channels.Get(delChanName, SockChatUtility.SanitiseChannelName); if(delChan == null) { ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(delChanName)); diff --git a/SharpChat/Commands/ChannelJoinCommand.cs b/SharpChat/Commands/ChannelJoinCommand.cs index c3239dd..0664e0b 100644 --- a/SharpChat/Commands/ChannelJoinCommand.cs +++ b/SharpChat/Commands/ChannelJoinCommand.cs @@ -9,7 +9,7 @@ namespace SharpChat.Commands { public void Dispatch(UserCommandContext ctx) { string joinChanStr = ctx.Args.FirstOrDefault() ?? string.Empty; - ChannelInfo? joinChan = ctx.Chat.Channels.Values.FirstOrDefault(c => c.NameEquals(joinChanStr)); + ChannelInfo? joinChan = ctx.Chat.Channels.Get(joinChanStr, SockChatUtility.SanitiseChannelName); if(joinChan == null) { ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(joinChanStr)); diff --git a/SharpChat/Commands/KickBanCommand.cs b/SharpChat/Commands/KickBanCommand.cs index 09770e6..2e31da2 100644 --- a/SharpChat/Commands/KickBanCommand.cs +++ b/SharpChat/Commands/KickBanCommand.cs @@ -30,13 +30,14 @@ namespace SharpChat.Commands { int banReasonIndex = 1; UserInfo? banUser = null; - if(string.IsNullOrEmpty(banUserTarget) || (banUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) { + (string name, UsersContext.NameTarget target) = SockChatUtility.ExplodeUserName(banUserTarget); + if(string.IsNullOrEmpty(name) || (banUser = ctx.Chat.Users.Get(name: name, nameTarget: target)) == null) { ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(banUserTarget)); return; } if(!ctx.User.IsSuper && banUser.Rank >= ctx.User.Rank && banUser != ctx.User) { - ctx.Chat.SendTo(ctx.User, new KickBanNotAllowedErrorPacket(banUser.LegacyName)); + ctx.Chat.SendTo(ctx.User, new KickBanNotAllowedErrorPacket(SockChatUtility.GetUserName(banUser))); return; } @@ -66,7 +67,7 @@ namespace SharpChat.Commands { MisuzuBanInfo? fbi = await Misuzu.CheckBanAsync(userId, userIp); if(fbi != null && fbi.IsBanned && !fbi.HasExpired) { - ctx.Chat.SendTo(ctx.User, new KickBanNotAllowedErrorPacket(banUser.LegacyName)); + ctx.Chat.SendTo(ctx.User, new KickBanNotAllowedErrorPacket(SockChatUtility.GetUserName(banUser))); return; } diff --git a/SharpChat/Commands/MessageWhisperCommand.cs b/SharpChat/Commands/MessageWhisperCommand.cs index 6ceb2f2..b557a26 100644 --- a/SharpChat/Commands/MessageWhisperCommand.cs +++ b/SharpChat/Commands/MessageWhisperCommand.cs @@ -17,7 +17,8 @@ namespace SharpChat.Commands { } string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty; - UserInfo? whisperUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(whisperUserStr)); + (string name, UsersContext.NameTarget target) = SockChatUtility.ExplodeUserName(whisperUserStr); + UserInfo? whisperUser = ctx.Chat.Users.Get(name: name, nameTarget: target); if(whisperUser == null) { ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(whisperUserStr)); diff --git a/SharpChat/Commands/PardonAddressCommand.cs b/SharpChat/Commands/PardonAddressCommand.cs index 38a874e..047d655 100644 --- a/SharpChat/Commands/PardonAddressCommand.cs +++ b/SharpChat/Commands/PardonAddressCommand.cs @@ -1,6 +1,5 @@ using SharpChat.Misuzu; using SharpChat.Packet; -using System; using System.Linq; using System.Net; using System.Threading.Tasks; diff --git a/SharpChat/Commands/PardonUserCommand.cs b/SharpChat/Commands/PardonUserCommand.cs index ed6bbf4..9421e78 100644 --- a/SharpChat/Commands/PardonUserCommand.cs +++ b/SharpChat/Commands/PardonUserCommand.cs @@ -1,6 +1,5 @@ using SharpChat.Misuzu; using SharpChat.Packet; -using System; using System.Linq; using System.Threading.Tasks; @@ -31,10 +30,11 @@ namespace SharpChat.Commands { return; } - UserInfo? unbanUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(unbanUserTarget)); + (string name, UsersContext.NameTarget target) = SockChatUtility.ExplodeUserName(unbanUserTarget); + UserInfo? unbanUser = ctx.Chat.Users.Get(name: name, nameTarget: target); if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) { unbanUserTargetIsName = false; - unbanUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.UserId == unbanUserId); + unbanUser = ctx.Chat.Users.Get(unbanUserId); } if(unbanUser != null) diff --git a/SharpChat/Commands/UserAFKCommand.cs b/SharpChat/Commands/UserAFKCommand.cs index 3a4f3c6..c751cf9 100644 --- a/SharpChat/Commands/UserAFKCommand.cs +++ b/SharpChat/Commands/UserAFKCommand.cs @@ -1,5 +1,4 @@ -using SharpChat.Packet; -using System.Linq; +using System.Linq; namespace SharpChat.Commands { public class UserAFKCommand : IUserCommand { diff --git a/SharpChat/Commands/UserNickCommand.cs b/SharpChat/Commands/UserNickCommand.cs index 62ab7ee..4ddc570 100644 --- a/SharpChat/Commands/UserNickCommand.cs +++ b/SharpChat/Commands/UserNickCommand.cs @@ -19,7 +19,7 @@ namespace SharpChat.Commands { int offset = 0; if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) { - targetUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.UserId == targetUserId); + targetUser = ctx.Chat.Users.Get(targetUserId); ++offset; } @@ -42,7 +42,7 @@ namespace SharpChat.Commands { else if(string.IsNullOrEmpty(nickStr)) nickStr = string.Empty; - if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Values.Any(u => u.NameEquals(nickStr))) { + if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Get(name: nickStr, nameTarget: UsersContext.NameTarget.UserAndNickName) != null) { ctx.Chat.SendTo(ctx.User, new UserNameInUseErrorPacket(nickStr)); return; } diff --git a/SharpChat/Commands/WhoCommand.cs b/SharpChat/Commands/WhoCommand.cs index c4c48b2..df0f284 100644 --- a/SharpChat/Commands/WhoCommand.cs +++ b/SharpChat/Commands/WhoCommand.cs @@ -12,13 +12,13 @@ namespace SharpChat.Commands { if(string.IsNullOrEmpty(channelName)) { ctx.Chat.SendTo(ctx.User, new WhoServerResponsePacket( - ctx.Chat.Users.Values.Select(u => u.LegacyName).ToArray(), - ctx.User.LegacyName + ctx.Chat.Users.All.Select(u => SockChatUtility.GetUserNameWithStatus(u)).ToArray(), + SockChatUtility.GetUserName(ctx.User) )); return; } - ChannelInfo? channel = ctx.Chat.Channels.Values.FirstOrDefault(c => c.NameEquals(channelName)); + ChannelInfo? channel = ctx.Chat.Channels.Get(channelName, SockChatUtility.SanitiseChannelName); if(channel == null) { ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(channelName)); @@ -32,8 +32,8 @@ namespace SharpChat.Commands { ctx.Chat.SendTo(ctx.User, new WhoChannelResponsePacket( channel.Name, - ctx.Chat.GetChannelUsers(channel).Select(user => user.LegacyName).ToArray(), - ctx.User.LegacyName + ctx.Chat.GetChannelUsers(channel).Select(user => SockChatUtility.GetUserNameWithStatus(user)).ToArray(), + SockChatUtility.GetUserNameWithStatus(ctx.User) )); } } diff --git a/SharpChat/Commands/WhoisCommand.cs b/SharpChat/Commands/WhoisCommand.cs index f149285..ea3b25a 100644 --- a/SharpChat/Commands/WhoisCommand.cs +++ b/SharpChat/Commands/WhoisCommand.cs @@ -18,7 +18,8 @@ namespace SharpChat.Commands { string ipUserStr = ctx.Args.FirstOrDefault() ?? string.Empty; UserInfo? ipUser; - if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) { + (string name, UsersContext.NameTarget target) = SockChatUtility.ExplodeUserName(ipUserStr); + if(string.IsNullOrWhiteSpace(name) || (ipUser = ctx.Chat.Users.Get(name: name, nameTarget: target)) == null) { ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(ipUserStr)); return; } diff --git a/SharpChat/Config/IConfig.cs b/SharpChat/Config/IConfig.cs index 5625389..6481cf0 100644 --- a/SharpChat/Config/IConfig.cs +++ b/SharpChat/Config/IConfig.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpChat.Config { public interface IConfig : IDisposable { diff --git a/SharpChat/Events/IChatEvent.cs b/SharpChat/Events/IChatEvent.cs index db2ee84..318e6f8 100644 --- a/SharpChat/Events/IChatEvent.cs +++ b/SharpChat/Events/IChatEvent.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SharpChat.Events { +namespace SharpChat.Events { public interface IChatEvent { } } diff --git a/SharpChat/Misuzu/MisuzuAuthInfo.cs b/SharpChat/Misuzu/MisuzuAuthInfo.cs index 1731353..eee5d7f 100644 --- a/SharpChat/Misuzu/MisuzuAuthInfo.cs +++ b/SharpChat/Misuzu/MisuzuAuthInfo.cs @@ -1,5 +1,4 @@ -using System; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace SharpChat.Misuzu { public class MisuzuAuthInfo { diff --git a/SharpChat/Packet/AuthSuccessPacket.cs b/SharpChat/Packet/AuthSuccessPacket.cs index d3b1a36..4141210 100644 --- a/SharpChat/Packet/AuthSuccessPacket.cs +++ b/SharpChat/Packet/AuthSuccessPacket.cs @@ -1,6 +1,4 @@ -using System; - -namespace SharpChat.Packet { +namespace SharpChat.Packet { public class AuthSuccessPacket : ServerPacket { private readonly long UserId; private readonly string UserName; @@ -41,7 +39,7 @@ namespace SharpChat.Packet { UserPerms.HasFlag(UserPermissions.CreateChannel) ? ( UserPerms.HasFlag(UserPermissions.SetChannelPermanent) ? 2 : 1 ) : 0, - ChannelName, + SockChatUtility.SanitiseChannelName(ChannelName), MaxMessageLength ); } diff --git a/SharpChat/Packet/ChannelCreatePacket.cs b/SharpChat/Packet/ChannelCreatePacket.cs index c25ecbe..523a690 100644 --- a/SharpChat/Packet/ChannelCreatePacket.cs +++ b/SharpChat/Packet/ChannelCreatePacket.cs @@ -1,6 +1,4 @@ -using System; - -namespace SharpChat.Packet { +namespace SharpChat.Packet { public class ChannelCreatePacket : ServerPacket { private readonly string ChannelName; private readonly bool ChannelHasPassword; @@ -19,7 +17,7 @@ namespace SharpChat.Packet { public override string Pack() { return string.Format( "4\t0\t{0}\t{1}\t{2}", - ChannelName, + SockChatUtility.SanitiseChannelName(ChannelName), ChannelHasPassword ? 1 : 0, ChannelIsTemporary ? 1 : 0 ); diff --git a/SharpChat/Packet/ChannelCreateResponsePacket.cs b/SharpChat/Packet/ChannelCreateResponsePacket.cs index 220e4aa..0bdc70d 100644 --- a/SharpChat/Packet/ChannelCreateResponsePacket.cs +++ b/SharpChat/Packet/ChannelCreateResponsePacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t0\fcrchan\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t0\fcrchan\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/ChannelDeleteNotAllowedErrorPacket.cs b/SharpChat/Packet/ChannelDeleteNotAllowedErrorPacket.cs index f9ad16c..d9880f0 100644 --- a/SharpChat/Packet/ChannelDeleteNotAllowedErrorPacket.cs +++ b/SharpChat/Packet/ChannelDeleteNotAllowedErrorPacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t1\fndchan\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t1\fndchan\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/ChannelDeletePacket.cs b/SharpChat/Packet/ChannelDeletePacket.cs index ad3f45b..d7a51f0 100644 --- a/SharpChat/Packet/ChannelDeletePacket.cs +++ b/SharpChat/Packet/ChannelDeletePacket.cs @@ -1,6 +1,4 @@ -using System; - -namespace SharpChat.Packet { +namespace SharpChat.Packet { public class ChannelDeletePacket : ServerPacket { private readonly string ChannelName; @@ -9,7 +7,10 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("4\t2\t{0}", ChannelName); + return string.Format( + "4\t2\t{0}", + SockChatUtility.SanitiseChannelName(ChannelName) + ); } } } diff --git a/SharpChat/Packet/ChannelDeleteResponsePacket.cs b/SharpChat/Packet/ChannelDeleteResponsePacket.cs index 4a95f65..75d3207 100644 --- a/SharpChat/Packet/ChannelDeleteResponsePacket.cs +++ b/SharpChat/Packet/ChannelDeleteResponsePacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t0\fdelchan\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t0\fdelchan\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/ChannelNameInUseErrorPacket.cs b/SharpChat/Packet/ChannelNameInUseErrorPacket.cs index 5bf917b..89f65a5 100644 --- a/SharpChat/Packet/ChannelNameInUseErrorPacket.cs +++ b/SharpChat/Packet/ChannelNameInUseErrorPacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t1\fnischan\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t1\fnischan\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/ChannelNotFoundErrorPacket.cs b/SharpChat/Packet/ChannelNotFoundErrorPacket.cs index 54fa0b2..f3a1efb 100644 --- a/SharpChat/Packet/ChannelNotFoundErrorPacket.cs +++ b/SharpChat/Packet/ChannelNotFoundErrorPacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t1\fnochan\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t1\fnochan\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/ChannelPasswordWrongErrorPacket.cs b/SharpChat/Packet/ChannelPasswordWrongErrorPacket.cs index 26ce622..a5c6541 100644 --- a/SharpChat/Packet/ChannelPasswordWrongErrorPacket.cs +++ b/SharpChat/Packet/ChannelPasswordWrongErrorPacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t1\fipwchan\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t1\fipwchan\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/ChannelRankTooLowErrorPacket.cs b/SharpChat/Packet/ChannelRankTooLowErrorPacket.cs index b647615..69fce27 100644 --- a/SharpChat/Packet/ChannelRankTooLowErrorPacket.cs +++ b/SharpChat/Packet/ChannelRankTooLowErrorPacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t1\fipchan\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t1\fipchan\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/ChannelUpdatePacket.cs b/SharpChat/Packet/ChannelUpdatePacket.cs index acef1fd..840a7d0 100644 --- a/SharpChat/Packet/ChannelUpdatePacket.cs +++ b/SharpChat/Packet/ChannelUpdatePacket.cs @@ -1,6 +1,4 @@ -using System; - -namespace SharpChat.Packet { +namespace SharpChat.Packet { public class ChannelUpdatePacket : ServerPacket { private readonly string ChannelNamePrevious; private readonly string ChannelNameNew; @@ -22,8 +20,8 @@ namespace SharpChat.Packet { public override string Pack() { return string.Format( "4\t1\t{0}\t{1}\t{2}\t{3}", - ChannelNamePrevious, - ChannelNameNew, + SockChatUtility.SanitiseChannelName(ChannelNamePrevious), + SockChatUtility.SanitiseChannelName(ChannelNameNew), ChannelHasPassword ? 1 : 0, ChannelIsTemporary ? 1 : 0 ); diff --git a/SharpChat/Packet/ChannelsPopulatePacket.cs b/SharpChat/Packet/ChannelsPopulatePacket.cs index fa7a4a5..1bcf69e 100644 --- a/SharpChat/Packet/ChannelsPopulatePacket.cs +++ b/SharpChat/Packet/ChannelsPopulatePacket.cs @@ -1,5 +1,4 @@ -using System; -using System.Text; +using System.Text; namespace SharpChat.Packet { public class ChannelsPopulatePacket : ServerPacket { @@ -19,7 +18,7 @@ namespace SharpChat.Packet { foreach(ListEntry entry in Entries) sb.AppendFormat( "\t{0}\t{1}\t{2}", - entry.Name, + SockChatUtility.SanitiseChannelName(entry.Name), entry.HasPassword ? 1 : 0, entry.IsTemporary ? 1 : 0 ); diff --git a/SharpChat/Packet/CommandNotAllowedErrorPacket.cs b/SharpChat/Packet/CommandNotAllowedErrorPacket.cs index 3b7f228..9984651 100644 --- a/SharpChat/Packet/CommandNotAllowedErrorPacket.cs +++ b/SharpChat/Packet/CommandNotAllowedErrorPacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t1\fcmdna\f/{1}\t{2}\t10010", Timestamp, CommandName, SequenceId); + return string.Format( + "2\t{0}\t-1\t1\fcmdna\f/{1}\t{2}\t10010", + Timestamp, + CommandName, + SequenceId + ); } } } diff --git a/SharpChat/Packet/MOTDPacket.cs b/SharpChat/Packet/MOTDPacket.cs index 7abe6aa..8048277 100644 --- a/SharpChat/Packet/MOTDPacket.cs +++ b/SharpChat/Packet/MOTDPacket.cs @@ -13,7 +13,7 @@ namespace SharpChat.Packet { public override string Pack() { return string.Format( "7\t1\t{0}\t-1\tChatBot\tinherit\t\t0\fsay\f{1}\twelcome\t0\t10010", - Timestamp, Utility.Sanitise(Body) + Timestamp, SockChatUtility.SanitiseMessageBody(Body) ); } } diff --git a/SharpChat/Packet/MessageAddPacket.cs b/SharpChat/Packet/MessageAddPacket.cs index 9e2a395..5b2bd62 100644 --- a/SharpChat/Packet/MessageAddPacket.cs +++ b/SharpChat/Packet/MessageAddPacket.cs @@ -24,7 +24,7 @@ namespace SharpChat.Packet { } public override string Pack() { - string body = Utility.Sanitise(Body); + string body = SockChatUtility.SanitiseMessageBody(Body); if(IsAction) body = string.Format("{0}", body); diff --git a/SharpChat/Packet/MessageBroadcastPacket.cs b/SharpChat/Packet/MessageBroadcastPacket.cs index a5b4056..a27f4a4 100644 --- a/SharpChat/Packet/MessageBroadcastPacket.cs +++ b/SharpChat/Packet/MessageBroadcastPacket.cs @@ -14,7 +14,7 @@ namespace SharpChat.Packet { return string.Format( "2\t{0}\t-1\t0\fsay\f{1}\t{2}\t10010", Timestamp, - Utility.Sanitise(Body), + SockChatUtility.SanitiseMessageBody(Body), SequenceId ); } diff --git a/SharpChat/Packet/MessagePopulatePacket.cs b/SharpChat/Packet/MessagePopulatePacket.cs index fe687f0..948dc66 100644 --- a/SharpChat/Packet/MessagePopulatePacket.cs +++ b/SharpChat/Packet/MessagePopulatePacket.cs @@ -1,5 +1,4 @@ using SharpChat.EventStorage; -using System; using System.Text; namespace SharpChat.Packet { @@ -32,7 +31,7 @@ namespace SharpChat.Packet { sb.AppendFormat( "{0}\t{1}\t{2}\t{3} {4} {5} {6} {7}", Event.Sender?.UserId, - Event.Sender?.LegacyNameWithStatus, + Event.Sender == null ? string.Empty : SockChatUtility.GetUserNameWithStatus(Event.Sender), Event.Sender?.Colour, Event.Sender?.Rank, Event.Sender?.Permissions.HasFlag(UserPermissions.KickUser) == true ? 1 : 0, @@ -49,7 +48,7 @@ namespace SharpChat.Packet { if(isBroadcast) sb.Append("0\fsay\f"); - string body = Utility.Sanitise(Event.Data.RootElement.GetProperty("text").GetString()); + string body = SockChatUtility.SanitiseMessageBody(Event.Data.RootElement.GetProperty("text").GetString()); if(isAction) body = string.Format("{0}", body); @@ -57,11 +56,19 @@ namespace SharpChat.Packet { break; case "chan:join": - sb.AppendFormat("{0}\t0\fjchan\f{1}", V1_CHATBOT, Event.Sender?.LegacyName); + sb.AppendFormat( + "{0}\t0\fjchan\f{1}", + V1_CHATBOT, + Event.Sender == null ? string.Empty : SockChatUtility.GetUserName(Event.Sender) + ); break; case "chan:leave": - sb.AppendFormat("{0}\t0\flchan\f{1}", V1_CHATBOT, Event.Sender?.LegacyName); + sb.AppendFormat( + "{0}\t0\flchan\f{1}", + V1_CHATBOT, + Event.Sender == null ? string.Empty : SockChatUtility.GetUserName(Event.Sender) + ); break; } diff --git a/SharpChat/Packet/UserChannelForceJoinPacket.cs b/SharpChat/Packet/UserChannelForceJoinPacket.cs index 766186d..891de24 100644 --- a/SharpChat/Packet/UserChannelForceJoinPacket.cs +++ b/SharpChat/Packet/UserChannelForceJoinPacket.cs @@ -1,6 +1,4 @@ -using System; - -namespace SharpChat.Packet { +namespace SharpChat.Packet { public class UserChannelForceJoinPacket : ServerPacket { private readonly string ChannelName; @@ -9,7 +7,10 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("5\t2\t{0}", ChannelName); + return string.Format( + "5\t2\t{0}", + SockChatUtility.SanitiseChannelName(ChannelName) + ); } } } diff --git a/SharpChat/Packet/UserChannelJoinPacket.cs b/SharpChat/Packet/UserChannelJoinPacket.cs index 2613405..ef922d2 100644 --- a/SharpChat/Packet/UserChannelJoinPacket.cs +++ b/SharpChat/Packet/UserChannelJoinPacket.cs @@ -1,6 +1,4 @@ -using System; - -namespace SharpChat.Packet { +namespace SharpChat.Packet { public class UserChannelJoinPacket : ServerPacket { private readonly long UserId; private readonly string UserName; diff --git a/SharpChat/Packet/UserUpdatePacket.cs b/SharpChat/Packet/UserUpdatePacket.cs index 26110b0..8a05586 100644 --- a/SharpChat/Packet/UserUpdatePacket.cs +++ b/SharpChat/Packet/UserUpdatePacket.cs @@ -1,6 +1,4 @@ -using System; - -namespace SharpChat.Packet { +namespace SharpChat.Packet { public class UserUpdatePacket : ServerPacket { private readonly long UserId; private readonly string UserName; diff --git a/SharpChat/Packet/UsersPopulatePacket.cs b/SharpChat/Packet/UsersPopulatePacket.cs index bcb147e..ee1c683 100644 --- a/SharpChat/Packet/UsersPopulatePacket.cs +++ b/SharpChat/Packet/UsersPopulatePacket.cs @@ -1,5 +1,4 @@ -using System; -using System.Text; +using System.Text; namespace SharpChat.Packet { public class UsersPopulatePacket : ServerPacket { diff --git a/SharpChat/Packet/WhoChannelNotFoundErrorPacket.cs b/SharpChat/Packet/WhoChannelNotFoundErrorPacket.cs index eca2055..0b55c87 100644 --- a/SharpChat/Packet/WhoChannelNotFoundErrorPacket.cs +++ b/SharpChat/Packet/WhoChannelNotFoundErrorPacket.cs @@ -11,7 +11,12 @@ namespace SharpChat.Packet { } public override string Pack() { - return string.Format("2\t{0}\t-1\t1\fwhoerr\f{1}\t{2}\t10010", Timestamp, ChannelName, SequenceId); + return string.Format( + "2\t{0}\t-1\t1\fwhoerr\f{1}\t{2}\t10010", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName), + SequenceId + ); } } } diff --git a/SharpChat/Packet/WhoChannelResponsePacket.cs b/SharpChat/Packet/WhoChannelResponsePacket.cs index e6556fc..a19f763 100644 --- a/SharpChat/Packet/WhoChannelResponsePacket.cs +++ b/SharpChat/Packet/WhoChannelResponsePacket.cs @@ -18,7 +18,11 @@ namespace SharpChat.Packet { public override string Pack() { StringBuilder sb = new(); - sb.AppendFormat("2\t{0}\t-1\t0\fwhochan\f{1}\f", Timestamp, ChannelName); + sb.AppendFormat( + "2\t{0}\t-1\t0\fwhochan\f{1}\f", + Timestamp, + SockChatUtility.SanitiseChannelName(ChannelName) + ); foreach(string userName in Users) { sb.Append(@" maxMsgLength, CachedValue maxConns ) { Misuzu = msz; - DefaultChannel = defaultChannel; + DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel)); MaxMessageLength = maxMsgLength; MaxConnections = maxConns; } @@ -114,7 +114,7 @@ namespace SharpChat.PacketHandlers { await ctx.Chat.ContextAccess.WaitAsync(); try { - UserInfo? user = ctx.Chat.Users.Values.FirstOrDefault(u => u.UserId == fai.UserId); + UserInfo? user = ctx.Chat.Users.Get(fai.UserId); if(user == null) user = new UserInfo( diff --git a/SharpChat/PacketHandlers/PingHandler.cs b/SharpChat/PacketHandlers/PingHandler.cs index 3f86431..46aca77 100644 --- a/SharpChat/PacketHandlers/PingHandler.cs +++ b/SharpChat/PacketHandlers/PingHandler.cs @@ -31,7 +31,7 @@ namespace SharpChat.PacketHandlers { ctx.Chat.ContextAccess.Wait(); try { if(LastBump < DateTimeOffset.UtcNow - BumpInterval) { - (string, string)[] bumpList = ctx.Chat.Users.Values + (string, string)[] bumpList = ctx.Chat.Users.All .Where(u => u.Status == UserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u)) .Select(u => (u.UserId.ToString(), ctx.Chat.GetRemoteAddresses(u).FirstOrDefault()?.ToString() ?? string.Empty)) .ToArray(); diff --git a/SharpChat/PacketHandlers/SendMessageHandler.cs b/SharpChat/PacketHandlers/SendMessageHandler.cs index 84f88d9..2e0f64c 100644 --- a/SharpChat/PacketHandlers/SendMessageHandler.cs +++ b/SharpChat/PacketHandlers/SendMessageHandler.cs @@ -1,14 +1,10 @@ -using SharpChat.Commands; -using SharpChat.Config; +using SharpChat.Config; using SharpChat.Events; -using SharpChat.EventStorage; -using SharpChat.Packet; using System; using System.Collections.Generic; using System.Linq; -namespace SharpChat.PacketHandlers -{ +namespace SharpChat.PacketHandlers { public class SendMessageHandler : IPacketHandler { private readonly CachedValue MaxMessageLength; @@ -47,8 +43,10 @@ namespace SharpChat.PacketHandlers ctx.Chat.ContextAccess.Wait(); try { - if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChannelInfo? channel) - || !ctx.Chat.IsInChannel(user, channel)) + ChannelInfo? channelInfo = ctx.Chat.Channels.Get( + ctx.Chat.ChannelsUsers.GetUserLastChannel(user) + ); + if(channelInfo == null) return; if(user.Status != UserStatus.Online) @@ -65,7 +63,7 @@ namespace SharpChat.PacketHandlers #endif if(messageText.StartsWith("/")) { - UserCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel); + UserCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channelInfo); IUserCommand? command = null; @@ -83,7 +81,7 @@ namespace SharpChat.PacketHandlers ctx.Chat.DispatchEvent(new MessageCreateEvent( SharpId.Next(), - channel, + channelInfo, user, DateTimeOffset.Now, messageText, diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs index 51ce5d0..a7d3be2 100644 --- a/SharpChat/SockChatServer.cs +++ b/SharpChat/SockChatServer.cs @@ -36,8 +36,6 @@ namespace SharpChat { private bool IsShuttingDown = false; - private ChannelInfo DefaultChannel { get; set; } - public SockChatServer(HttpClient httpClient, MisuzuClient msz, IEventStorage evtStore, IConfig config) { Logger.Write("Initialising Sock Chat server..."); @@ -67,15 +65,13 @@ namespace SharpChat { rank: channelCfg.SafeReadValue("minRank", 0) ); - Context.Channels.Add(channelInfo.Name, channelInfo); - DefaultChannel ??= channelInfo; + Context.Channels.Add(channelInfo); } - DefaultChannel ??= new ChannelInfo("Default"); - if(Context.Channels.Count < 1) - Context.Channels.Add(DefaultChannel.Name, DefaultChannel); + if(Context.Channels.PublicCount < 1) + Context.Channels.Add(new ChannelInfo("Default")); - GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections)); + GuestHandlers.Add(new AuthHandler(Misuzu, Context.Channels.MainChannel, MaxMessageLength, MaxConnections)); AuthedHandlers.AddRange(new IPacketHandler[] { new PingHandler(Misuzu), diff --git a/SharpChat/SockChatUtility.cs b/SharpChat/SockChatUtility.cs new file mode 100644 index 0000000..1d6aa2b --- /dev/null +++ b/SharpChat/SockChatUtility.cs @@ -0,0 +1,65 @@ +using System; +using System.Text.RegularExpressions; + +namespace SharpChat { + public static class SockChatUtility { + private static readonly Regex ChannelName = new(@"[^A-Za-z0-9\-_]", RegexOptions.CultureInvariant | RegexOptions.Compiled); + + public static string SanitiseMessageBody(string? body) { + if(string.IsNullOrEmpty(body)) + return string.Empty; + + return body.Replace("<", "<").Replace(">", ">").Replace("\n", "
").Replace("\t", " "); + } + + public static string SanitiseChannelName(string name) { + return ChannelName.Replace(name.Replace(" ", "_"), "-"); + } + + public static bool CheckChannelName(string name) { + return name.Length < 1 || ChannelName.IsMatch(name); + } + + public static string GetUserName(UserInfo info) { + return string.IsNullOrWhiteSpace(info.NickName) ? info.UserName : $"~{info.NickName}"; + } + + public static string GetUserNameWithStatus(UserInfo info) { + string name = GetUserName(info); + + if(info.Status == UserStatus.Away) + name = string.Format( + "<{0}>_{1}", + info.StatusText[..Math.Min(info.StatusText.Length, 5)].ToUpperInvariant(), + name + ); + + return name; + } + + public static (string, UsersContext.NameTarget) ExplodeUserName(string name) { + UsersContext.NameTarget target = UsersContext.NameTarget.UserName; + + if(name.StartsWith("<")) { + int gt = name.IndexOf(">_"); + if(gt > 0) { + gt += 2; + name = name[gt..]; + } + } else if(name.StartsWith("<")) { + int gt = name.IndexOf(">_"); + if(gt > 0) { + gt += 5; + name = name[gt..]; + } + } + + if(name.StartsWith("~")) { + target = UsersContext.NameTarget.NickName; + name = name[1..]; + } + + return (name, target); + } + } +} diff --git a/SharpChat/UserInfo.cs b/SharpChat/UserInfo.cs index 60c75ed..7b1a3a9 100644 --- a/SharpChat/UserInfo.cs +++ b/SharpChat/UserInfo.cs @@ -1,7 +1,4 @@ -using System; -using System.Text; - -namespace SharpChat { +namespace SharpChat { public class UserInfo { public const int DEFAULT_SIZE = 30; public const int DEFAULT_MINIMUM_DELAY = 10000; @@ -17,21 +14,6 @@ namespace SharpChat { public UserStatus Status { get; set; } public string StatusText { get; set; } - public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}"; - - public string LegacyNameWithStatus { - get { - StringBuilder sb = new(); - - if(Status == UserStatus.Away) - sb.AppendFormat("<{0}>_", StatusText[..Math.Min(StatusText.Length, 5)].ToUpperInvariant()); - - sb.Append(LegacyName); - - return sb.ToString(); - } - } - public UserInfo( long userId, string userName, @@ -54,13 +36,6 @@ namespace SharpChat { IsSuper = isSuper; } - public bool NameEquals(string? name) { - return string.Equals(name, UserName, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(name, NickName, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(name, LegacyName, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(name, LegacyNameWithStatus, StringComparison.InvariantCultureIgnoreCase); - } - public static string GetDMChannelName(UserInfo user1, UserInfo user2) { return user1.UserId < user2.UserId ? $"@{user1.UserId}-{user2.UserId}" diff --git a/SharpChat/UsersContext.cs b/SharpChat/UsersContext.cs new file mode 100644 index 0000000..17e6179 --- /dev/null +++ b/SharpChat/UsersContext.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SharpChat { + public class UsersContext { + [Flags] + public enum NameTarget { + None = 0, + UserName = 1, + NickName = 2, + UserAndNickName = UserName | NickName, + } + + private readonly List Users = new(); + + public int TotalCount { get; private set; } + + public UserInfo[] All => Users.ToArray(); + + public UserInfo? Get( + long? userId = null, + string? name = null, + NameTarget nameTarget = NameTarget.UserName + ) { + foreach(UserInfo info in Users) { + if(userId != null && info.UserId != userId) + continue; + if(name != null) { + // this could probably all fit in a single if condition, but it'd be massive and disgusting + // and require more thinking power than my goldfish brain can muster + bool match = false; + if(nameTarget.HasFlag(NameTarget.UserName) && info.UserName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + match = true; + else if(nameTarget.HasFlag(NameTarget.NickName) && info.NickName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + match = true; + if(!match) + continue; + } + + return info; + } + + return null; + } + + public UserInfo[] GetMany( + long[]? userIds = null, + string[]? names = null, + NameTarget namesTarget = NameTarget.UserName, + int? minRank = null + ) { + List users = new(); + + foreach(UserInfo info in Users) { + if(minRank != null && info.Rank < minRank) + continue; + + if(userIds != null && !userIds.Contains(info.UserId)) + continue; + + if(names?.Length > 0) { + bool match = false; + foreach(string name in names) { + if(namesTarget.HasFlag(NameTarget.UserName) && info.UserName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + match = true; + else if(namesTarget.HasFlag(NameTarget.NickName) && info.NickName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + match = true; + } + + if(!match) + continue; + } + + users.Add(info); + } + + return users.ToArray(); + } + + public void Add(UserInfo info) { + if(Get(info.UserId, info.UserName) != null) + throw new ArgumentException("A user with that id and/or name has already been registred.", nameof(info)); + + Users.Add(info); + ++TotalCount; + } + + public void Remove(UserInfo info) { + if(!Users.Contains(info)) { + Remove(info.UserId); + return; + } + + Users.Remove(info); + --TotalCount; + } + + public void Remove( + long? userId = null, + string? name = null, + NameTarget nameTarget = NameTarget.UserName + ) { + UserInfo? info = Get(userId, name, nameTarget); + if(info == null) + return; + + Users.Remove(info); + --TotalCount; + } + } +} diff --git a/SharpChat/Utility.cs b/SharpChat/Utility.cs deleted file mode 100644 index 941dc55..0000000 --- a/SharpChat/Utility.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SharpChat { - public static class Utility { - public static string Sanitise(string? body) { - if(string.IsNullOrEmpty(body)) - return string.Empty; - - return body.Replace("<", "<").Replace(">", ">").Replace("\n", "
").Replace("\t", " "); - } - } -}