using SharpChat.Misuzu; using SharpChat.Packet; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; namespace SharpChat { public class BasicUser : IEquatable { private const int RANK_NO_FLOOD = 9; public long UserId { get; set; } 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 bool HasFloodProtection => Rank < RANK_NO_FLOOD; public string DisplayName { get { StringBuilder sb = new(); if(Status == ChatUserStatus.Away) sb.AppendFormat("<{0}>_", StatusMessage[..Math.Min(StatusMessage.Length, 5)].ToUpperInvariant()); if(string.IsNullOrWhiteSpace(Nickname)) sb.Append(Username); else { sb.Append('~'); sb.Append(Nickname); } return sb.ToString(); } } public bool Can(ChatUserPermissions perm, bool strict = false) { ChatUserPermissions perms = Permissions & perm; return strict ? perms == perm : perms > 0; } public string Pack() { StringBuilder sb = new(); sb.Append(UserId); sb.Append('\t'); sb.Append(DisplayName); 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(Can(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(' '); sb.Append(Can(ChatUserPermissions.CreateChannel | ChatUserPermissions.SetChannelPermanent, true) ? 2 : ( Can(ChatUserPermissions.CreateChannel) ? 1 : 0 )); return sb.ToString(); } public override int GetHashCode() { return UserId.GetHashCode(); } public override bool Equals(object obj) { return Equals(obj as BasicUser); } public bool Equals(BasicUser other) { return UserId == other?.UserId; } } public class ChatUser : BasicUser, IPacketTarget { public DateTimeOffset SilencedUntil { get; set; } private readonly List Connections = new(); private readonly List Channels = new(); public readonly ChatRateLimiter RateLimiter = new(); public string TargetName => "@log"; public ChatChannel Channel => Channels.FirstOrDefault(); // This needs to be a session thing public ChatChannel CurrentChannel { get; private set; } public bool IsSilenced => DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero; public bool HasConnections => Connections.Where(c => !c.HasTimedOut && !c.IsDisposed).Any(); public int ConnectionCount => Connections.Where(c => !c.HasTimedOut && !c.IsDisposed).Count(); public IEnumerable RemoteAddresses => Connections.Select(c => c.RemoteAddress); public ChatUser() {} public ChatUser(MisuzuAuthInfo auth) { UserId = auth.UserId; ApplyAuth(auth, true); } public void ApplyAuth(MisuzuAuthInfo auth, bool invalidateRestrictions = false) { Username = auth.Username; if(Status == ChatUserStatus.Offline) Status = ChatUserStatus.Online; Colour = ChatColour.FromMisuzu(auth.ColourRaw); Rank = auth.Rank; Permissions = auth.Permissions; if(invalidateRestrictions || !IsSilenced) SilencedUntil = auth.SilencedUntil; } public void Send(IServerPacket packet) { foreach(ChatConnection conn in Connections) conn.Send(packet); } public void Close() { foreach(ChatConnection conn in Connections) conn.Dispose(); Connections.Clear(); } public void ForceChannel(ChatChannel chan = null) { Send(new UserChannelForceJoinPacket(chan ?? CurrentChannel)); } public void FocusChannel(ChatChannel chan) { if(InChannel(chan)) CurrentChannel = chan; } public bool InChannel(ChatChannel chan) { return Channels.Contains(chan); } public void JoinChannel(ChatChannel chan) { if(!InChannel(chan)) { Channels.Add(chan); CurrentChannel = chan; } } public void LeaveChannel(ChatChannel chan) { Channels.Remove(chan); CurrentChannel = Channels.FirstOrDefault(); } public IEnumerable GetChannels() { return Channels.ToList(); } public void AddConnection(ChatConnection conn) { if(conn == null) return; conn.User = this; Connections.Add(conn); } public void RemoveConnection(ChatConnection conn) { if(conn == null) return; if(!conn.IsDisposed) // this could be possible conn.User = null; Connections.Remove(conn); } public IEnumerable GetDeadConnections() { return Connections.Where(x => x.HasTimedOut || x.IsDisposed).ToList(); } public bool NameEquals(string name) { return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase) || string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase) || string.Equals(name, DisplayName, StringComparison.InvariantCultureIgnoreCase); } } }