diff --git a/SharpChat/ChatChannel.cs b/SharpChat/ChatChannel.cs index 9632c09..61f8c0a 100644 --- a/SharpChat/ChatChannel.cs +++ b/SharpChat/ChatChannel.cs @@ -4,19 +4,35 @@ using System.Text; namespace SharpChat { public class ChatChannel { - public string Name { get; set; } - public string Password { get; set; } = string.Empty; - public bool IsTemporary { get; set; } = false; - public int Rank { get; set; } = 0; - public ChatUser Owner { get; set; } = null; + public string Name { get; } + public string Password { get; set; } + public bool IsTemporary { get; set; } + public int Rank { get; set; } + public long OwnerId { get; set; } public bool HasPassword => !string.IsNullOrWhiteSpace(Password); - public ChatChannel() { } + public ChatChannel( + ChatUser owner, + string name, + string password = null, + bool isTemporary = false, + int rank = 0 + ) : this(name, password, isTemporary, rank, owner?.UserId ?? 0) {} - public ChatChannel(string name) { + public ChatChannel( + string name, + string password = null, + bool isTemporary = false, + int rank = 0, + long ownerId = 0 + ) { Name = name; + Password = password ?? string.Empty; + IsTemporary = isTemporary; + Rank = rank; + OwnerId = ownerId; } public string Pack() { @@ -35,6 +51,12 @@ namespace SharpChat { return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase); } + public bool IsOwner(ChatUser user) { + return OwnerId > 0 + && user != null + && OwnerId == user.UserId; + } + public override int GetHashCode() { return Name.GetHashCode(); } diff --git a/SharpChat/ChatColour.cs b/SharpChat/ChatColour.cs index 0ae128c..8c67aa2 100644 --- a/SharpChat/ChatColour.cs +++ b/SharpChat/ChatColour.cs @@ -1,4 +1,6 @@ -namespace SharpChat { +using System.Diagnostics.CodeAnalysis; + +namespace SharpChat { public struct ChatColour { public byte Red { get; } public byte Green { get; } @@ -21,6 +23,21 @@ Inherits = false; } + public override bool Equals([NotNullWhen(true)] object obj) { + return obj is ChatColour colour && Equals(colour); + } + + public bool Equals(ChatColour other) { + return Red == other.Red + && Green == other.Green + && Blue == other.Blue + && Inherits == other.Inherits; + } + + public override int GetHashCode() { + return ToMisuzu(); + } + public override string ToString() { return Inherits ? "inherit" @@ -50,5 +67,13 @@ ? None : FromRawRGB(raw); } + + public static bool operator ==(ChatColour left, ChatColour right) { + return left.Equals(right); + } + + public static bool operator !=(ChatColour left, ChatColour right) { + return !(left == right); + } } } diff --git a/SharpChat/ChatContext.cs b/SharpChat/ChatContext.cs index ed4557b..4e66896 100644 --- a/SharpChat/ChatContext.cs +++ b/SharpChat/ChatContext.cs @@ -19,6 +19,7 @@ namespace SharpChat { public IEventStorage Events { get; } public HashSet ChannelUsers { get; } = new(); public Dictionary UserRateLimiters { get; } = new(); + public Dictionary UserLastChannel { get; } = new(); public ChatContext(IEventStorage evtStore) { Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore)); @@ -71,6 +72,65 @@ namespace SharpChat { return Users.Where(u => ids.Contains(u.UserId)).ToArray(); } + public void UpdateUser( + ChatUser user, + string userName = null, + string nickName = null, + ChatColour? colour = null, + ChatUserStatus? status = null, + string statusText = null, + int? rank = null, + ChatUserPermissions? perms = null, + bool silent = false + ) { + if(user == null) + throw new ArgumentNullException(nameof(user)); + + bool hasChanged = false; + string previousName = null; + + if(userName != null && !user.UserName.Equals(userName)) { + user.UserName = userName; + hasChanged = true; + } + + if(nickName != null && !user.NickName.Equals(nickName)) { + if(!silent) + previousName = string.IsNullOrWhiteSpace(user.NickName) ? user.UserName : user.NickName; + + user.NickName = nickName; + hasChanged = true; + } + + if(colour.HasValue && user.Colour != colour.Value) { + user.Colour = colour.Value; + hasChanged = true; + } + + if(status.HasValue && user.Status != status.Value) { + user.Status = status.Value; + hasChanged = true; + } + + if(statusText != null && !user.StatusText.Equals(statusText)) { + user.StatusText = statusText; + hasChanged = true; + } + + if(rank != null && user.Rank != rank) { + user.Rank = (int)rank; + hasChanged = true; + } + + if(perms.HasValue && user.Permissions != perms) { + user.Permissions = perms.Value; + hasChanged = true; + } + + if(hasChanged) + SendToUserChannels(user, new UserUpdatePacket(user, previousName)); + } + public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) { if(duration > TimeSpan.Zero) SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration)); @@ -102,12 +162,13 @@ namespace SharpChat { Users.Add(user); ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name)); - user.CurrentChannel = chan; + UserLastChannel[user.UserId] = chan; } public void HandleDisconnect(ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) { - user.Status = ChatUserStatus.Offline; + UpdateUser(user, status: ChatUserStatus.Offline); Users.Remove(user); + UserLastChannel.Remove(user.UserId); ChatChannel[] channels = GetUserChannels(user); @@ -117,25 +178,25 @@ namespace SharpChat { SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason)); Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason)); - if(chan.IsTemporary && chan.Owner == user) + if(chan.IsTemporary && chan.IsOwner(user)) RemoveChannel(chan); } } public void SwitchChannel(ChatUser user, ChatChannel chan, string password) { - if(user.CurrentChannel == chan) { + if(UserLastChannel.TryGetValue(user.UserId, out ChatChannel ulc) && chan == ulc) { ForceChannel(user); return; } - if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.Owner != user) { + if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.IsOwner(user)) { if(chan.Rank > user.Rank) { SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name)); ForceChannel(user); return; } - if(chan.Password != password) { + if(!string.IsNullOrEmpty(chan.Password) && chan.Password != password) { SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name)); ForceChannel(user); return; @@ -149,7 +210,7 @@ namespace SharpChat { if(!Channels.Contains(chan)) return; - ChatChannel oldChan = user.CurrentChannel; + ChatChannel oldChan = UserLastChannel[user.UserId]; SendTo(oldChan, new UserChannelLeavePacket(user)); Events.AddEvent(new UserChannelLeaveEvent(DateTimeOffset.Now, user, oldChan)); @@ -166,9 +227,9 @@ namespace SharpChat { ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, oldChan.Name)); ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name)); - user.CurrentChannel = chan; + UserLastChannel[user.UserId] = chan; - if(oldChan.IsTemporary && oldChan.Owner == user) + if(oldChan.IsTemporary && oldChan.IsOwner(user)) RemoveChannel(oldChan); } @@ -204,6 +265,18 @@ namespace SharpChat { conn.Send(packet); } + public void SendToUserChannels(ChatUser user, IServerPacket packet) { + if(user == null) + throw new ArgumentNullException(nameof(user)); + if(packet == null) + throw new ArgumentNullException(nameof(packet)); + + IEnumerable chans = Channels.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(ChatConnection conn in conns) + conn.Send(packet); + } + public IPAddress[] GetRemoteAddresses(ChatUser user) { return Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct().ToArray(); } @@ -212,26 +285,18 @@ namespace SharpChat { if(user == null) throw new ArgumentNullException(nameof(user)); - SendTo(user, new UserChannelForceJoinPacket(chan ?? user.CurrentChannel)); + if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan)) + throw new ArgumentException("no channel???"); + + SendTo(user, new UserChannelForceJoinPacket(chan)); } - public void UpdateChannel(ChatChannel channel, string name = null, bool? temporary = null, int? hierarchy = null, string password = null) { + public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string password = null) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(!Channels.Contains(channel)) throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel)); - string prevName = channel.Name; - int prevHierarchy = channel.Rank; - bool nameUpdated = !string.IsNullOrWhiteSpace(name) && name != prevName; - - if(nameUpdated) { - if(!ChatChannel.CheckName(name)) - throw new ArgumentException("Name contains invalid characters.", nameof(name)); - - channel.Name = name; - } - if(temporary.HasValue) channel.IsTemporary = temporary.Value; @@ -241,12 +306,9 @@ namespace SharpChat { if(password != null) channel.Password = password; - // Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively + // TODO: Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) { - SendTo(user, new ChannelUpdatePacket(prevName, channel)); - - if(nameUpdated) - ForceChannel(user); + SendTo(user, new ChannelUpdatePacket(channel.Name, channel)); } } diff --git a/SharpChat/ChatUser.cs b/SharpChat/ChatUser.cs index 6d8e489..09ba00c 100644 --- a/SharpChat/ChatUser.cs +++ b/SharpChat/ChatUser.cs @@ -1,5 +1,4 @@ -using SharpChat.Misuzu; -using System; +using System; using System.Text; namespace SharpChat { @@ -8,57 +7,51 @@ namespace SharpChat { public const int DEFAULT_MINIMUM_DELAY = 10000; public const int DEFAULT_RISKY_OFFSET = 5; - private const int RANK_NO_FLOOD = 9; - - public long UserId { get; set; } - public string Username { get; set; } + public long UserId { get; } + public string UserName { get; set; } public ChatColour Colour { get; set; } public int Rank { get; set; } - public string Nickname { get; set; } public ChatUserPermissions Permissions { get; set; } - public ChatUserStatus Status { get; set; } = ChatUserStatus.Online; - public string StatusMessage { get; set; } + public string NickName { get; set; } + public ChatUserStatus Status { get; set; } + public string StatusText { get; set; } - public bool HasFloodProtection - => Rank < RANK_NO_FLOOD; - - // This needs to be a session thing - public ChatChannel CurrentChannel { get; set; } - - public string DisplayName { + public string LegacyName { get { StringBuilder sb = new(); if(Status == ChatUserStatus.Away) - sb.AppendFormat("<{0}>_", StatusMessage[..Math.Min(StatusMessage.Length, 5)].ToUpperInvariant()); + sb.AppendFormat("<{0}>_", StatusText[..Math.Min(StatusText.Length, 5)].ToUpperInvariant()); - if(string.IsNullOrWhiteSpace(Nickname)) - sb.Append(Username); + if(string.IsNullOrWhiteSpace(NickName)) + sb.Append(UserName); else { sb.Append('~'); - sb.Append(Nickname); + sb.Append(NickName); } return sb.ToString(); } } - public ChatUser() { } - - public ChatUser(MisuzuAuthInfo auth) { - UserId = auth.UserId; - ApplyAuth(auth); - } - - public void ApplyAuth(MisuzuAuthInfo auth) { - Username = auth.Username; - - if(Status == ChatUserStatus.Offline) - Status = ChatUserStatus.Online; - - Colour = ChatColour.FromMisuzu(auth.ColourRaw); - Rank = auth.Rank; - Permissions = auth.Permissions; + public ChatUser( + long userId, + string userName, + ChatColour colour, + int rank, + ChatUserPermissions perms, + string nickName = null, + ChatUserStatus status = ChatUserStatus.Online, + string statusText = null + ) { + UserId = userId; + UserName = userName ?? throw new ArgumentNullException(nameof(userName)); + Colour = colour; + Rank = rank; + Permissions = perms; + NickName = nickName ?? string.Empty; + Status = status; + StatusText = statusText ?? string.Empty; } public bool Can(ChatUserPermissions perm, bool strict = false) { @@ -71,27 +64,29 @@ namespace SharpChat { sb.Append(UserId); sb.Append('\t'); - sb.Append(DisplayName); + sb.Append(LegacyName); sb.Append('\t'); sb.Append(Colour); sb.Append('\t'); sb.Append(Rank); sb.Append(' '); sb.Append(Can(ChatUserPermissions.KickUser) ? '1' : '0'); - sb.Append(" 0 "); // view logs + sb.Append(' '); + sb.Append(Can(ChatUserPermissions.ViewLogs) ? '1' : '0'); + sb.Append(' '); sb.Append(Can(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(' '); - sb.Append(Can(ChatUserPermissions.CreateChannel | ChatUserPermissions.SetChannelPermanent, true) ? 2 : ( - Can(ChatUserPermissions.CreateChannel) ? 1 : 0 + sb.Append(Can(ChatUserPermissions.CreateChannel | ChatUserPermissions.SetChannelPermanent, true) ? '2' : ( + Can(ChatUserPermissions.CreateChannel) ? '1' : '0' )); return sb.ToString(); } public bool NameEquals(string name) { - return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(name, DisplayName, StringComparison.InvariantCultureIgnoreCase); + return string.Equals(name, UserName, StringComparison.InvariantCultureIgnoreCase) + || string.Equals(name, NickName, StringComparison.InvariantCultureIgnoreCase) + || string.Equals(name, LegacyName, StringComparison.InvariantCultureIgnoreCase); } public override int GetHashCode() { diff --git a/SharpChat/ChatUserPermissions.cs b/SharpChat/ChatUserPermissions.cs index 49901e1..a4f7fee 100644 --- a/SharpChat/ChatUserPermissions.cs +++ b/SharpChat/ChatUserPermissions.cs @@ -21,5 +21,6 @@ namespace SharpChat { EditOwnMessage = 0x00002000, EditAnyMessage = 0x00004000, SeeIPAddress = 0x00008000, + ViewLogs = 0x00040000, } } diff --git a/SharpChat/Commands/AFKCommand.cs b/SharpChat/Commands/AFKCommand.cs index 9c2778a..a5cce48 100644 --- a/SharpChat/Commands/AFKCommand.cs +++ b/SharpChat/Commands/AFKCommand.cs @@ -20,9 +20,11 @@ namespace SharpChat.Commands { statusText = statusText[..MAX_LENGTH].Trim(); } - ctx.User.Status = ChatUserStatus.Away; - ctx.User.StatusMessage = statusText; - ctx.Chat.SendTo(ctx.Channel, new UserUpdatePacket(ctx.User)); + ctx.Chat.UpdateUser( + ctx.User, + status: ChatUserStatus.Away, + statusText: statusText + ); } } } diff --git a/SharpChat/Commands/CreateChannelCommand.cs b/SharpChat/Commands/CreateChannelCommand.cs index e9d9713..9e588e8 100644 --- a/SharpChat/Commands/CreateChannelCommand.cs +++ b/SharpChat/Commands/CreateChannelCommand.cs @@ -43,12 +43,11 @@ namespace SharpChat.Commands { return; } - ChatChannel createChan = new() { - Name = createChanName, - IsTemporary = !ctx.User.Can(ChatUserPermissions.SetChannelPermanent), - Rank = createChanHierarchy, - Owner = ctx.User, - }; + ChatChannel createChan = new( + ctx.User, createChanName, + isTemporary: !ctx.User.Can(ChatUserPermissions.SetChannelPermanent), + rank: createChanHierarchy + ); ctx.Chat.Channels.Add(createChan); foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank)) diff --git a/SharpChat/Commands/DeleteChannelCommand.cs b/SharpChat/Commands/DeleteChannelCommand.cs index 94d4563..78f2248 100644 --- a/SharpChat/Commands/DeleteChannelCommand.cs +++ b/SharpChat/Commands/DeleteChannelCommand.cs @@ -24,7 +24,7 @@ namespace SharpChat.Commands { return; } - if(!ctx.User.Can(ChatUserPermissions.DeleteChannel) && delChan.Owner != ctx.User) { + if(!ctx.User.Can(ChatUserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) { ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETE_FAILED, true, delChan.Name)); return; } diff --git a/SharpChat/Commands/KickBanCommand.cs b/SharpChat/Commands/KickBanCommand.cs index 7e4393c..16a694c 100644 --- a/SharpChat/Commands/KickBanCommand.cs +++ b/SharpChat/Commands/KickBanCommand.cs @@ -36,7 +36,7 @@ namespace SharpChat.Commands { } if(banUser == ctx.User || banUser.Rank >= ctx.User.Rank) { - ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName)); + ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName)); return; } @@ -66,7 +66,7 @@ namespace SharpChat.Commands { MisuzuBanInfo fbi = await Misuzu.CheckBanAsync(userId, userIp); if(fbi.IsBanned && !fbi.HasExpired) { - ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName)); + ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName)); return; } diff --git a/SharpChat/Commands/NickCommand.cs b/SharpChat/Commands/NickCommand.cs index 1bb272e..5cabba7 100644 --- a/SharpChat/Commands/NickCommand.cs +++ b/SharpChat/Commands/NickCommand.cs @@ -34,21 +34,20 @@ namespace SharpChat.Commands { .Replace("\f", string.Empty).Replace("\t", string.Empty) .Replace(' ', '_').Trim(); - if(nickStr == targetUser.Username) - nickStr = null; + if(nickStr == targetUser.UserName) + nickStr = string.Empty; else if(nickStr.Length > 15) nickStr = nickStr[..15]; else if(string.IsNullOrEmpty(nickStr)) - nickStr = null; + nickStr = string.Empty; if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) { ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.NAME_IN_USE, true, nickStr)); return; } - string previousName = targetUser == ctx.User ? (targetUser.Nickname ?? targetUser.Username) : null; - targetUser.Nickname = nickStr; - ctx.Chat.SendTo(ctx.Channel, new UserUpdatePacket(targetUser, previousName)); + string previousName = targetUser == ctx.User ? (targetUser.NickName ?? targetUser.UserName) : null; + ctx.Chat.UpdateUser(targetUser, nickName: nickStr, silent: previousName == null); } } } diff --git a/SharpChat/Commands/PasswordChannelCommand.cs b/SharpChat/Commands/PasswordChannelCommand.cs index 2eb0fb3..24700dc 100644 --- a/SharpChat/Commands/PasswordChannelCommand.cs +++ b/SharpChat/Commands/PasswordChannelCommand.cs @@ -8,7 +8,7 @@ namespace SharpChat.Commands { } public void Dispatch(ChatCommandContext ctx) { - if(!ctx.User.Can(ChatUserPermissions.SetChannelPassword) || ctx.Channel.Owner != ctx.User) { + if(!ctx.User.Can(ChatUserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) { ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); return; } diff --git a/SharpChat/Commands/RankChannelCommand.cs b/SharpChat/Commands/RankChannelCommand.cs index 4197334..1fca492 100644 --- a/SharpChat/Commands/RankChannelCommand.cs +++ b/SharpChat/Commands/RankChannelCommand.cs @@ -10,7 +10,7 @@ namespace SharpChat.Commands { } public void Dispatch(ChatCommandContext ctx) { - if(!ctx.User.Can(ChatUserPermissions.SetChannelHierarchy) || ctx.Channel.Owner != ctx.User) { + if(!ctx.User.Can(ChatUserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) { ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); return; } diff --git a/SharpChat/Commands/RemoteAddressCommand.cs b/SharpChat/Commands/RemoteAddressCommand.cs index 3da853d..1fc4b5a 100644 --- a/SharpChat/Commands/RemoteAddressCommand.cs +++ b/SharpChat/Commands/RemoteAddressCommand.cs @@ -24,7 +24,7 @@ namespace SharpChat.Commands { } foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser)) - ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.IP_ADDRESS, false, ipUser.Username, ip)); + ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.IP_ADDRESS, false, ipUser.UserName, ip)); } } } diff --git a/SharpChat/Commands/SilenceApplyCommand.cs b/SharpChat/Commands/SilenceApplyCommand.cs index eb97cbc..afc9567 100644 --- a/SharpChat/Commands/SilenceApplyCommand.cs +++ b/SharpChat/Commands/SilenceApplyCommand.cs @@ -50,7 +50,7 @@ namespace SharpChat.Commands { //silUser.SilencedUntil = silenceUntil; ctx.Chat.SendTo(silUser, new LegacyCommandResponse(LCR.SILENCED, false)); - ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_SILENCED, false, silUser.DisplayName)); + ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_SILENCED, false, silUser.LegacyName)); } } } diff --git a/SharpChat/Commands/SilenceRevokeCommand.cs b/SharpChat/Commands/SilenceRevokeCommand.cs index 06c5ce8..98c3fe3 100644 --- a/SharpChat/Commands/SilenceRevokeCommand.cs +++ b/SharpChat/Commands/SilenceRevokeCommand.cs @@ -34,7 +34,7 @@ namespace SharpChat.Commands { //unsilUser.SilencedUntil = DateTimeOffset.MinValue; ctx.Chat.SendTo(unsilUser, new LegacyCommandResponse(LCR.UNSILENCED, false)); - ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_UNSILENCED, false, unsilUser.DisplayName)); + ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_UNSILENCED, false, unsilUser.LegacyName)); } } } diff --git a/SharpChat/Commands/WhisperCommand.cs b/SharpChat/Commands/WhisperCommand.cs index f114852..a6a4d30 100644 --- a/SharpChat/Commands/WhisperCommand.cs +++ b/SharpChat/Commands/WhisperCommand.cs @@ -42,7 +42,7 @@ namespace SharpChat.Commands { DateTime = dateTime, ChannelName = whisperChan, Sender = ctx.User, - Text = $"{whisperUser.DisplayName} {whisperStr}", + Text = $"{whisperUser.LegacyName} {whisperStr}", Flags = ChatMessageFlags.Private, })); } diff --git a/SharpChat/Commands/WhoCommand.cs b/SharpChat/Commands/WhoCommand.cs index 6eb7b60..76ab685 100644 --- a/SharpChat/Commands/WhoCommand.cs +++ b/SharpChat/Commands/WhoCommand.cs @@ -20,7 +20,7 @@ namespace SharpChat.Commands { whoChanSB.Append(@" style=""font-weight: bold;"""); whoChanSB.Append('>'); - whoChanSB.Append(whoUser.DisplayName); + whoChanSB.Append(whoUser.LegacyName); whoChanSB.Append(", "); } @@ -48,7 +48,7 @@ namespace SharpChat.Commands { whoChanSB.Append(@" style=""font-weight: bold;"""); whoChanSB.Append('>'); - whoChanSB.Append(whoUser.DisplayName); + whoChanSB.Append(whoUser.LegacyName); whoChanSB.Append(", "); } diff --git a/SharpChat/EventStorage/MariaDBEventStorage.cs b/SharpChat/EventStorage/MariaDBEventStorage.cs index 5046034..35b75bc 100644 --- a/SharpChat/EventStorage/MariaDBEventStorage.cs +++ b/SharpChat/EventStorage/MariaDBEventStorage.cs @@ -32,10 +32,10 @@ namespace SharpChat.EventStorage { new MySqlParameter("flags", (byte)evt.Flags), new MySqlParameter("data", JsonSerializer.SerializeToUtf8Bytes(evt, evt.GetType())), new MySqlParameter("sender", evt.Sender?.UserId < 1 ? null : (long?)evt.Sender.UserId), - new MySqlParameter("sender_name", evt.Sender?.Username), + new MySqlParameter("sender_name", evt.Sender?.UserName), new MySqlParameter("sender_colour", evt.Sender?.Colour.ToMisuzu()), new MySqlParameter("sender_rank", evt.Sender?.Rank), - new MySqlParameter("sender_nick", evt.Sender?.Nickname), + new MySqlParameter("sender_nick", evt.Sender?.NickName), new MySqlParameter("sender_perms", evt.Sender?.Permissions) ); } @@ -72,14 +72,14 @@ namespace SharpChat.EventStorage { evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")); if(!reader.IsDBNull(reader.GetOrdinal("event_sender"))) { - evt.Sender = new ChatUser { - UserId = reader.GetInt64("event_sender"), - Username = reader.GetString("event_sender_name"), - Colour = ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), - Rank = reader.GetInt32("event_sender_rank"), - Nickname = reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick"), - Permissions = (ChatUserPermissions)reader.GetInt32("event_sender_perms") - }; + evt.Sender = new ChatUser( + reader.GetInt64("event_sender"), + reader.GetString("event_sender_name"), + ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), + reader.GetInt32("event_sender_rank"), + (ChatUserPermissions)reader.GetInt32("event_sender_perms"), + reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick") + ); } return evt; diff --git a/SharpChat/Misuzu/MisuzuAuthInfo.cs b/SharpChat/Misuzu/MisuzuAuthInfo.cs index 75a9f8b..ad4a3a2 100644 --- a/SharpChat/Misuzu/MisuzuAuthInfo.cs +++ b/SharpChat/Misuzu/MisuzuAuthInfo.cs @@ -13,11 +13,13 @@ namespace SharpChat.Misuzu { public long UserId { get; set; } [JsonPropertyName("username")] - public string Username { get; set; } + public string UserName { get; set; } [JsonPropertyName("colour_raw")] public int ColourRaw { get; set; } + public ChatColour Colour => ChatColour.FromMisuzu(ColourRaw); + [JsonPropertyName("hierarchy")] public int Rank { get; set; } diff --git a/SharpChat/Packet/ContextMessagePacket.cs b/SharpChat/Packet/ContextMessagePacket.cs index b9d1b2d..fbd7fcb 100644 --- a/SharpChat/Packet/ContextMessagePacket.cs +++ b/SharpChat/Packet/ContextMessagePacket.cs @@ -41,19 +41,19 @@ namespace SharpChat.Packet { case UserConnectEvent _: sb.Append(V1_CHATBOT); sb.Append("0\fjoin\f"); - sb.Append(Event.Sender.Username); + sb.Append(Event.Sender.UserName); break; case UserChannelJoinEvent _: sb.Append(V1_CHATBOT); sb.Append("0\fjchan\f"); - sb.Append(Event.Sender.Username); + sb.Append(Event.Sender.UserName); break; case UserChannelLeaveEvent _: sb.Append(V1_CHATBOT); sb.Append("0\flchan\f"); - sb.Append(Event.Sender.Username); + sb.Append(Event.Sender.UserName); break; case UserDisconnectEvent ude: @@ -77,7 +77,7 @@ namespace SharpChat.Packet { } sb.Append('\f'); - sb.Append(Event.Sender.Username); + sb.Append(Event.Sender.UserName); break; } diff --git a/SharpChat/Packet/FloodWarningPacket.cs b/SharpChat/Packet/FloodWarningPacket.cs deleted file mode 100644 index 208c612..0000000 --- a/SharpChat/Packet/FloodWarningPacket.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace SharpChat.Packet { - public class FloodWarningPacket : ServerPacket { - public override IEnumerable Pack() { - StringBuilder sb = new(); - - sb.Append('2'); - sb.Append('\t'); - sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds()); - sb.Append("\t-1\t0\fflwarn\t"); - sb.Append(SequenceId); - sb.Append("\t10010"); - - yield return sb.ToString(); - } - } -} diff --git a/SharpChat/Packet/LegacyCommandResponse.cs b/SharpChat/Packet/LegacyCommandResponse.cs index 879f2a4..5605ee4 100644 --- a/SharpChat/Packet/LegacyCommandResponse.cs +++ b/SharpChat/Packet/LegacyCommandResponse.cs @@ -104,5 +104,6 @@ namespace SharpChat.Packet { public const string KICK_NOT_ALLOWED = "kickna"; public const string USER_NOT_BANNED = "notban"; public const string USER_UNBANNED = "unban"; + public const string FLOOD_WARN = "flwarn"; } } diff --git a/SharpChat/Packet/UserDisconnectPacket.cs b/SharpChat/Packet/UserDisconnectPacket.cs index 6bd69e8..38c3acb 100644 --- a/SharpChat/Packet/UserDisconnectPacket.cs +++ b/SharpChat/Packet/UserDisconnectPacket.cs @@ -28,7 +28,7 @@ namespace SharpChat.Packet { sb.Append('\t'); sb.Append(User.UserId); sb.Append('\t'); - sb.Append(User.DisplayName); + sb.Append(User.LegacyName); sb.Append('\t'); switch(Reason) { diff --git a/SharpChat/Packet/UserUpdatePacket.cs b/SharpChat/Packet/UserUpdatePacket.cs index 9d4529e..292a260 100644 --- a/SharpChat/Packet/UserUpdatePacket.cs +++ b/SharpChat/Packet/UserUpdatePacket.cs @@ -24,7 +24,7 @@ namespace SharpChat.Packet { sb.Append("\t-1\t0\fnick\f"); sb.Append(PreviousName); sb.Append('\f'); - sb.Append(User.DisplayName); + sb.Append(User.LegacyName); sb.Append('\t'); sb.Append(SequenceId); sb.Append("\t10010"); diff --git a/SharpChat/PacketHandlers/AuthHandler.cs b/SharpChat/PacketHandlers/AuthHandler.cs index ca1c221..b412721 100644 --- a/SharpChat/PacketHandlers/AuthHandler.cs +++ b/SharpChat/PacketHandlers/AuthHandler.cs @@ -103,12 +103,22 @@ namespace SharpChat.PacketHandlers { ChatUser user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId); if(user == null) - user = new ChatUser(fai); - else { - user.ApplyAuth(fai); - if(user.CurrentChannel != null) - ctx.Chat.SendTo(user.CurrentChannel, new UserUpdatePacket(user)); - } + user = new ChatUser( + fai.UserId, + fai.UserName, + fai.Colour, + fai.Rank, + fai.Permissions + ); + else + ctx.Chat.UpdateUser( + user, + userName: fai.UserName, + status: ChatUserStatus.Online, + colour: fai.Colour, + rank: fai.Rank, + perms: fai.Permissions + ); // Enforce a maximum amount of connections per user if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) { @@ -119,7 +129,7 @@ namespace SharpChat.PacketHandlers { ctx.Connection.BumpPing(); ctx.Connection.User = user; - ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.Username}!")); + ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!")); if(File.Exists("welcome.txt")) { IEnumerable lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x)); diff --git a/SharpChat/PacketHandlers/SendMessageHandler.cs b/SharpChat/PacketHandlers/SendMessageHandler.cs index a572b31..86b4d58 100644 --- a/SharpChat/PacketHandlers/SendMessageHandler.cs +++ b/SharpChat/PacketHandlers/SendMessageHandler.cs @@ -45,17 +45,13 @@ namespace SharpChat.PacketHandlers { ctx.Chat.ContextAccess.Wait(); try { - ChatChannel channel = user.CurrentChannel; - - if(channel == null - || !ctx.Chat.IsInChannel(user, channel) + if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel channel) + && !ctx.Chat.IsInChannel(user, channel) /*|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser))*/) return; - if(user.Status != ChatUserStatus.Online) { - user.Status = ChatUserStatus.Online; - ctx.Chat.SendTo(channel, new UserUpdatePacket(user)); - } + if(user.Status != ChatUserStatus.Online) + ctx.Chat.UpdateUser(user, status: ChatUserStatus.Online); int maxMsgLength = MaxMessageLength; if(messageText.Length > maxMsgLength) @@ -64,7 +60,7 @@ namespace SharpChat.PacketHandlers { messageText = messageText.Trim(); #if DEBUG - Logger.Write($"<{ctx.Connection.Id} {user.Username}> {messageText}"); + Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}"); #endif IChatMessage message = null; diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs index 52df7ce..b9b56dd 100644 --- a/SharpChat/SockChatServer.cs +++ b/SharpChat/SockChatServer.cs @@ -17,6 +17,7 @@ namespace SharpChat { public const int DEFAULT_MSG_LENGTH_MAX = 5000; public const int DEFAULT_MAX_CONNECTIONS = 5; public const int DEFAULT_FLOOD_KICK_LENGTH = 30; + public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9; public IWebSocketServer Server { get; } public ChatContext Context { get; } @@ -27,6 +28,7 @@ namespace SharpChat { private readonly CachedValue MaxMessageLength; private readonly CachedValue MaxConnections; private readonly CachedValue FloodKickLength; + private readonly CachedValue FloodKickExemptRank; private readonly List GuestHandlers = new(); private readonly List AuthedHandlers = new(); @@ -45,25 +47,26 @@ namespace SharpChat { MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX); MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS); FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH); + FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK); Context = new ChatContext(evtStore); string[] channelNames = config.ReadValue("channels", new[] { "lounge" }); foreach(string channelName in channelNames) { - ChatChannel channelInfo = new(channelName); IConfig channelCfg = config.ScopeTo($"channels:{channelName}"); - string tmp; - tmp = channelCfg.SafeReadValue("name", string.Empty); - if(!string.IsNullOrWhiteSpace(tmp)) - channelInfo.Name = tmp; + string name = channelCfg.SafeReadValue("name", string.Empty); + if(string.IsNullOrWhiteSpace(name)) + name = channelName; - channelInfo.Password = channelCfg.SafeReadValue("password", string.Empty); - channelInfo.Rank = channelCfg.SafeReadValue("minRank", 0); + ChatChannel channelInfo = new( + name, + channelCfg.SafeReadValue("password", string.Empty), + rank: channelCfg.SafeReadValue("minRank", 0) + ); Context.Channels.Add(channelInfo); - DefaultChannel ??= channelInfo; } @@ -152,7 +155,7 @@ namespace SharpChat { Context.SafeUpdate(); // this doesn't affect non-authed connections????? - if(conn.User is not null && conn.User.HasFloodProtection) { + if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) { ChatUser banUser = null; string banAddr = string.Empty; TimeSpan banDuration = TimeSpan.MinValue; @@ -178,7 +181,7 @@ namespace SharpChat { if(banUser is not null) { if(banDuration == TimeSpan.MinValue) { - Context.SendTo(conn.User, new FloodWarningPacket()); + Context.SendTo(conn.User, new LegacyCommandResponse(LCR.FLOOD_WARN, false)); } else { Context.BanUser(conn.User, banDuration, UserDisconnectReason.Flood);