Improved user updating but also other things that were already local.

This commit is contained in:
flash 2023-02-22 01:28:53 +01:00
parent 8de3ba8dbb
commit 82973f7a33
27 changed files with 264 additions and 167 deletions

View file

@ -4,19 +4,35 @@ using System.Text;
namespace SharpChat { namespace SharpChat {
public class ChatChannel { public class ChatChannel {
public string Name { get; set; } public string Name { get; }
public string Password { get; set; } = string.Empty; public string Password { get; set; }
public bool IsTemporary { get; set; } = false; public bool IsTemporary { get; set; }
public int Rank { get; set; } = 0; public int Rank { get; set; }
public ChatUser Owner { get; set; } = null; public long OwnerId { get; set; }
public bool HasPassword public bool HasPassword
=> !string.IsNullOrWhiteSpace(Password); => !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; Name = name;
Password = password ?? string.Empty;
IsTemporary = isTemporary;
Rank = rank;
OwnerId = ownerId;
} }
public string Pack() { public string Pack() {
@ -35,6 +51,12 @@ namespace SharpChat {
return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase); return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
} }
public bool IsOwner(ChatUser user) {
return OwnerId > 0
&& user != null
&& OwnerId == user.UserId;
}
public override int GetHashCode() { public override int GetHashCode() {
return Name.GetHashCode(); return Name.GetHashCode();
} }

View file

@ -1,4 +1,6 @@
namespace SharpChat { using System.Diagnostics.CodeAnalysis;
namespace SharpChat {
public struct ChatColour { public struct ChatColour {
public byte Red { get; } public byte Red { get; }
public byte Green { get; } public byte Green { get; }
@ -21,6 +23,21 @@
Inherits = false; 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() { public override string ToString() {
return Inherits return Inherits
? "inherit" ? "inherit"
@ -50,5 +67,13 @@
? None ? None
: FromRawRGB(raw); : 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);
}
} }
} }

View file

@ -19,6 +19,7 @@ namespace SharpChat {
public IEventStorage Events { get; } public IEventStorage Events { get; }
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new(); public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new();
public Dictionary<long, RateLimiter> UserRateLimiters { get; } = new(); public Dictionary<long, RateLimiter> UserRateLimiters { get; } = new();
public Dictionary<long, ChatChannel> UserLastChannel { get; } = new();
public ChatContext(IEventStorage evtStore) { public ChatContext(IEventStorage evtStore) {
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore)); Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
@ -71,6 +72,65 @@ namespace SharpChat {
return Users.Where(u => ids.Contains(u.UserId)).ToArray(); 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) { public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
if(duration > TimeSpan.Zero) if(duration > TimeSpan.Zero)
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration)); SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
@ -102,12 +162,13 @@ namespace SharpChat {
Users.Add(user); Users.Add(user);
ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name)); ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
user.CurrentChannel = chan; UserLastChannel[user.UserId] = chan;
} }
public void HandleDisconnect(ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) { public void HandleDisconnect(ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) {
user.Status = ChatUserStatus.Offline; UpdateUser(user, status: ChatUserStatus.Offline);
Users.Remove(user); Users.Remove(user);
UserLastChannel.Remove(user.UserId);
ChatChannel[] channels = GetUserChannels(user); ChatChannel[] channels = GetUserChannels(user);
@ -117,25 +178,25 @@ namespace SharpChat {
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason)); SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason)); Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason));
if(chan.IsTemporary && chan.Owner == user) if(chan.IsTemporary && chan.IsOwner(user))
RemoveChannel(chan); RemoveChannel(chan);
} }
} }
public void SwitchChannel(ChatUser user, ChatChannel chan, string password) { 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); ForceChannel(user);
return; return;
} }
if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.Owner != user) { if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
if(chan.Rank > user.Rank) { if(chan.Rank > user.Rank) {
SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name)); SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
ForceChannel(user); ForceChannel(user);
return; return;
} }
if(chan.Password != password) { if(!string.IsNullOrEmpty(chan.Password) && chan.Password != password) {
SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name)); SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name));
ForceChannel(user); ForceChannel(user);
return; return;
@ -149,7 +210,7 @@ namespace SharpChat {
if(!Channels.Contains(chan)) if(!Channels.Contains(chan))
return; return;
ChatChannel oldChan = user.CurrentChannel; ChatChannel oldChan = UserLastChannel[user.UserId];
SendTo(oldChan, new UserChannelLeavePacket(user)); SendTo(oldChan, new UserChannelLeavePacket(user));
Events.AddEvent(new UserChannelLeaveEvent(DateTimeOffset.Now, user, oldChan)); Events.AddEvent(new UserChannelLeaveEvent(DateTimeOffset.Now, user, oldChan));
@ -166,9 +227,9 @@ namespace SharpChat {
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, oldChan.Name)); ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, oldChan.Name));
ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.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); RemoveChannel(oldChan);
} }
@ -204,6 +265,18 @@ namespace SharpChat {
conn.Send(packet); 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<ChatChannel> chans = Channels.Where(c => IsInChannel(user, c));
IEnumerable<ChatConnection> 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) { public IPAddress[] GetRemoteAddresses(ChatUser user) {
return Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct().ToArray(); return Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct().ToArray();
} }
@ -212,26 +285,18 @@ namespace SharpChat {
if(user == null) if(user == null)
throw new ArgumentNullException(nameof(user)); 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) if(channel == null)
throw new ArgumentNullException(nameof(channel)); throw new ArgumentNullException(nameof(channel));
if(!Channels.Contains(channel)) if(!Channels.Contains(channel))
throw new ArgumentException("Provided channel is not registered with this manager.", nameof(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) if(temporary.HasValue)
channel.IsTemporary = temporary.Value; channel.IsTemporary = temporary.Value;
@ -241,12 +306,9 @@ namespace SharpChat {
if(password != null) if(password != null)
channel.Password = password; 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)) { foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) {
SendTo(user, new ChannelUpdatePacket(prevName, channel)); SendTo(user, new ChannelUpdatePacket(channel.Name, channel));
if(nameUpdated)
ForceChannel(user);
} }
} }

View file

@ -1,5 +1,4 @@
using SharpChat.Misuzu; using System;
using System;
using System.Text; using System.Text;
namespace SharpChat { namespace SharpChat {
@ -8,57 +7,51 @@ namespace SharpChat {
public const int DEFAULT_MINIMUM_DELAY = 10000; public const int DEFAULT_MINIMUM_DELAY = 10000;
public const int DEFAULT_RISKY_OFFSET = 5; public const int DEFAULT_RISKY_OFFSET = 5;
private const int RANK_NO_FLOOD = 9; public long UserId { get; }
public string UserName { get; set; }
public long UserId { get; set; }
public string Username { get; set; }
public ChatColour Colour { get; set; } public ChatColour Colour { get; set; }
public int Rank { get; set; } public int Rank { get; set; }
public string Nickname { get; set; }
public ChatUserPermissions Permissions { get; set; } public ChatUserPermissions Permissions { get; set; }
public ChatUserStatus Status { get; set; } = ChatUserStatus.Online; public string NickName { get; set; }
public string StatusMessage { get; set; } public ChatUserStatus Status { get; set; }
public string StatusText { get; set; }
public bool HasFloodProtection public string LegacyName {
=> Rank < RANK_NO_FLOOD;
// This needs to be a session thing
public ChatChannel CurrentChannel { get; set; }
public string DisplayName {
get { get {
StringBuilder sb = new(); StringBuilder sb = new();
if(Status == ChatUserStatus.Away) if(Status == ChatUserStatus.Away)
sb.AppendFormat("&lt;{0}&gt;_", StatusMessage[..Math.Min(StatusMessage.Length, 5)].ToUpperInvariant()); sb.AppendFormat("&lt;{0}&gt;_", StatusText[..Math.Min(StatusText.Length, 5)].ToUpperInvariant());
if(string.IsNullOrWhiteSpace(Nickname)) if(string.IsNullOrWhiteSpace(NickName))
sb.Append(Username); sb.Append(UserName);
else { else {
sb.Append('~'); sb.Append('~');
sb.Append(Nickname); sb.Append(NickName);
} }
return sb.ToString(); return sb.ToString();
} }
} }
public ChatUser() { } public ChatUser(
long userId,
public ChatUser(MisuzuAuthInfo auth) { string userName,
UserId = auth.UserId; ChatColour colour,
ApplyAuth(auth); int rank,
} ChatUserPermissions perms,
string nickName = null,
public void ApplyAuth(MisuzuAuthInfo auth) { ChatUserStatus status = ChatUserStatus.Online,
Username = auth.Username; string statusText = null
) {
if(Status == ChatUserStatus.Offline) UserId = userId;
Status = ChatUserStatus.Online; UserName = userName ?? throw new ArgumentNullException(nameof(userName));
Colour = colour;
Colour = ChatColour.FromMisuzu(auth.ColourRaw); Rank = rank;
Rank = auth.Rank; Permissions = perms;
Permissions = auth.Permissions; NickName = nickName ?? string.Empty;
Status = status;
StatusText = statusText ?? string.Empty;
} }
public bool Can(ChatUserPermissions perm, bool strict = false) { public bool Can(ChatUserPermissions perm, bool strict = false) {
@ -71,27 +64,29 @@ namespace SharpChat {
sb.Append(UserId); sb.Append(UserId);
sb.Append('\t'); sb.Append('\t');
sb.Append(DisplayName); sb.Append(LegacyName);
sb.Append('\t'); sb.Append('\t');
sb.Append(Colour); sb.Append(Colour);
sb.Append('\t'); sb.Append('\t');
sb.Append(Rank); sb.Append(Rank);
sb.Append(' '); sb.Append(' ');
sb.Append(Can(ChatUserPermissions.KickUser) ? '1' : '0'); 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(Can(ChatUserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(Can(ChatUserPermissions.CreateChannel | ChatUserPermissions.SetChannelPermanent, true) ? 2 : ( sb.Append(Can(ChatUserPermissions.CreateChannel | ChatUserPermissions.SetChannelPermanent, true) ? '2' : (
Can(ChatUserPermissions.CreateChannel) ? 1 : 0 Can(ChatUserPermissions.CreateChannel) ? '1' : '0'
)); ));
return sb.ToString(); return sb.ToString();
} }
public bool NameEquals(string name) { public bool NameEquals(string name) {
return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase) return string.Equals(name, UserName, StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase) || string.Equals(name, NickName, StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(name, DisplayName, StringComparison.InvariantCultureIgnoreCase); || string.Equals(name, LegacyName, StringComparison.InvariantCultureIgnoreCase);
} }
public override int GetHashCode() { public override int GetHashCode() {

View file

@ -21,5 +21,6 @@ namespace SharpChat {
EditOwnMessage = 0x00002000, EditOwnMessage = 0x00002000,
EditAnyMessage = 0x00004000, EditAnyMessage = 0x00004000,
SeeIPAddress = 0x00008000, SeeIPAddress = 0x00008000,
ViewLogs = 0x00040000,
} }
} }

View file

@ -20,9 +20,11 @@ namespace SharpChat.Commands {
statusText = statusText[..MAX_LENGTH].Trim(); statusText = statusText[..MAX_LENGTH].Trim();
} }
ctx.User.Status = ChatUserStatus.Away; ctx.Chat.UpdateUser(
ctx.User.StatusMessage = statusText; ctx.User,
ctx.Chat.SendTo(ctx.Channel, new UserUpdatePacket(ctx.User)); status: ChatUserStatus.Away,
statusText: statusText
);
} }
} }
} }

View file

@ -43,12 +43,11 @@ namespace SharpChat.Commands {
return; return;
} }
ChatChannel createChan = new() { ChatChannel createChan = new(
Name = createChanName, ctx.User, createChanName,
IsTemporary = !ctx.User.Can(ChatUserPermissions.SetChannelPermanent), isTemporary: !ctx.User.Can(ChatUserPermissions.SetChannelPermanent),
Rank = createChanHierarchy, rank: createChanHierarchy
Owner = ctx.User, );
};
ctx.Chat.Channels.Add(createChan); ctx.Chat.Channels.Add(createChan);
foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank)) foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))

View file

@ -24,7 +24,7 @@ namespace SharpChat.Commands {
return; 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)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
return; return;
} }

View file

@ -36,7 +36,7 @@ namespace SharpChat.Commands {
} }
if(banUser == ctx.User || banUser.Rank >= ctx.User.Rank) { 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; return;
} }
@ -66,7 +66,7 @@ namespace SharpChat.Commands {
MisuzuBanInfo fbi = await Misuzu.CheckBanAsync(userId, userIp); MisuzuBanInfo fbi = await Misuzu.CheckBanAsync(userId, userIp);
if(fbi.IsBanned && !fbi.HasExpired) { 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; return;
} }

View file

@ -34,21 +34,20 @@ namespace SharpChat.Commands {
.Replace("\f", string.Empty).Replace("\t", string.Empty) .Replace("\f", string.Empty).Replace("\t", string.Empty)
.Replace(' ', '_').Trim(); .Replace(' ', '_').Trim();
if(nickStr == targetUser.Username) if(nickStr == targetUser.UserName)
nickStr = null; nickStr = string.Empty;
else if(nickStr.Length > 15) else if(nickStr.Length > 15)
nickStr = nickStr[..15]; nickStr = nickStr[..15];
else if(string.IsNullOrEmpty(nickStr)) else if(string.IsNullOrEmpty(nickStr))
nickStr = null; nickStr = string.Empty;
if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) { 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)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.NAME_IN_USE, true, nickStr));
return; return;
} }
string previousName = targetUser == ctx.User ? (targetUser.Nickname ?? targetUser.Username) : null; string previousName = targetUser == ctx.User ? (targetUser.NickName ?? targetUser.UserName) : null;
targetUser.Nickname = nickStr; ctx.Chat.UpdateUser(targetUser, nickName: nickStr, silent: previousName == null);
ctx.Chat.SendTo(ctx.Channel, new UserUpdatePacket(targetUser, previousName));
} }
} }
} }

View file

@ -8,7 +8,7 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { 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}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -10,7 +10,7 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { 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}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -24,7 +24,7 @@ namespace SharpChat.Commands {
} }
foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser)) 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));
} }
} }
} }

View file

@ -50,7 +50,7 @@ namespace SharpChat.Commands {
//silUser.SilencedUntil = silenceUntil; //silUser.SilencedUntil = silenceUntil;
ctx.Chat.SendTo(silUser, new LegacyCommandResponse(LCR.SILENCED, false)); 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));
} }
} }
} }

View file

@ -34,7 +34,7 @@ namespace SharpChat.Commands {
//unsilUser.SilencedUntil = DateTimeOffset.MinValue; //unsilUser.SilencedUntil = DateTimeOffset.MinValue;
ctx.Chat.SendTo(unsilUser, new LegacyCommandResponse(LCR.UNSILENCED, false)); 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));
} }
} }
} }

View file

@ -42,7 +42,7 @@ namespace SharpChat.Commands {
DateTime = dateTime, DateTime = dateTime,
ChannelName = whisperChan, ChannelName = whisperChan,
Sender = ctx.User, Sender = ctx.User,
Text = $"{whisperUser.DisplayName} {whisperStr}", Text = $"{whisperUser.LegacyName} {whisperStr}",
Flags = ChatMessageFlags.Private, Flags = ChatMessageFlags.Private,
})); }));
} }

View file

@ -20,7 +20,7 @@ namespace SharpChat.Commands {
whoChanSB.Append(@" style=""font-weight: bold;"""); whoChanSB.Append(@" style=""font-weight: bold;""");
whoChanSB.Append('>'); whoChanSB.Append('>');
whoChanSB.Append(whoUser.DisplayName); whoChanSB.Append(whoUser.LegacyName);
whoChanSB.Append("</a>, "); whoChanSB.Append("</a>, ");
} }
@ -48,7 +48,7 @@ namespace SharpChat.Commands {
whoChanSB.Append(@" style=""font-weight: bold;"""); whoChanSB.Append(@" style=""font-weight: bold;""");
whoChanSB.Append('>'); whoChanSB.Append('>');
whoChanSB.Append(whoUser.DisplayName); whoChanSB.Append(whoUser.LegacyName);
whoChanSB.Append("</a>, "); whoChanSB.Append("</a>, ");
} }

View file

@ -32,10 +32,10 @@ namespace SharpChat.EventStorage {
new MySqlParameter("flags", (byte)evt.Flags), new MySqlParameter("flags", (byte)evt.Flags),
new MySqlParameter("data", JsonSerializer.SerializeToUtf8Bytes(evt, evt.GetType())), new MySqlParameter("data", JsonSerializer.SerializeToUtf8Bytes(evt, evt.GetType())),
new MySqlParameter("sender", evt.Sender?.UserId < 1 ? null : (long?)evt.Sender.UserId), 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_colour", evt.Sender?.Colour.ToMisuzu()),
new MySqlParameter("sender_rank", evt.Sender?.Rank), 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) new MySqlParameter("sender_perms", evt.Sender?.Permissions)
); );
} }
@ -72,14 +72,14 @@ namespace SharpChat.EventStorage {
evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")); evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created"));
if(!reader.IsDBNull(reader.GetOrdinal("event_sender"))) { if(!reader.IsDBNull(reader.GetOrdinal("event_sender"))) {
evt.Sender = new ChatUser { evt.Sender = new ChatUser(
UserId = reader.GetInt64("event_sender"), reader.GetInt64("event_sender"),
Username = reader.GetString("event_sender_name"), reader.GetString("event_sender_name"),
Colour = ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")),
Rank = reader.GetInt32("event_sender_rank"), reader.GetInt32("event_sender_rank"),
Nickname = reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick"), (ChatUserPermissions)reader.GetInt32("event_sender_perms"),
Permissions = (ChatUserPermissions)reader.GetInt32("event_sender_perms") reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick")
}; );
} }
return evt; return evt;

View file

@ -13,11 +13,13 @@ namespace SharpChat.Misuzu {
public long UserId { get; set; } public long UserId { get; set; }
[JsonPropertyName("username")] [JsonPropertyName("username")]
public string Username { get; set; } public string UserName { get; set; }
[JsonPropertyName("colour_raw")] [JsonPropertyName("colour_raw")]
public int ColourRaw { get; set; } public int ColourRaw { get; set; }
public ChatColour Colour => ChatColour.FromMisuzu(ColourRaw);
[JsonPropertyName("hierarchy")] [JsonPropertyName("hierarchy")]
public int Rank { get; set; } public int Rank { get; set; }

View file

@ -41,19 +41,19 @@ namespace SharpChat.Packet {
case UserConnectEvent _: case UserConnectEvent _:
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\fjoin\f"); sb.Append("0\fjoin\f");
sb.Append(Event.Sender.Username); sb.Append(Event.Sender.UserName);
break; break;
case UserChannelJoinEvent _: case UserChannelJoinEvent _:
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\fjchan\f"); sb.Append("0\fjchan\f");
sb.Append(Event.Sender.Username); sb.Append(Event.Sender.UserName);
break; break;
case UserChannelLeaveEvent _: case UserChannelLeaveEvent _:
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\flchan\f"); sb.Append("0\flchan\f");
sb.Append(Event.Sender.Username); sb.Append(Event.Sender.UserName);
break; break;
case UserDisconnectEvent ude: case UserDisconnectEvent ude:
@ -77,7 +77,7 @@ namespace SharpChat.Packet {
} }
sb.Append('\f'); sb.Append('\f');
sb.Append(Event.Sender.Username); sb.Append(Event.Sender.UserName);
break; break;
} }

View file

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet {
public class FloodWarningPacket : ServerPacket {
public override IEnumerable<string> 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();
}
}
}

View file

@ -104,5 +104,6 @@ namespace SharpChat.Packet {
public const string KICK_NOT_ALLOWED = "kickna"; public const string KICK_NOT_ALLOWED = "kickna";
public const string USER_NOT_BANNED = "notban"; public const string USER_NOT_BANNED = "notban";
public const string USER_UNBANNED = "unban"; public const string USER_UNBANNED = "unban";
public const string FLOOD_WARN = "flwarn";
} }
} }

View file

@ -28,7 +28,7 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append(User.UserId); sb.Append(User.UserId);
sb.Append('\t'); sb.Append('\t');
sb.Append(User.DisplayName); sb.Append(User.LegacyName);
sb.Append('\t'); sb.Append('\t');
switch(Reason) { switch(Reason) {

View file

@ -24,7 +24,7 @@ namespace SharpChat.Packet {
sb.Append("\t-1\t0\fnick\f"); sb.Append("\t-1\t0\fnick\f");
sb.Append(PreviousName); sb.Append(PreviousName);
sb.Append('\f'); sb.Append('\f');
sb.Append(User.DisplayName); sb.Append(User.LegacyName);
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(SequenceId);
sb.Append("\t10010"); sb.Append("\t10010");

View file

@ -103,12 +103,22 @@ namespace SharpChat.PacketHandlers {
ChatUser user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId); ChatUser user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId);
if(user == null) if(user == null)
user = new ChatUser(fai); user = new ChatUser(
else { fai.UserId,
user.ApplyAuth(fai); fai.UserName,
if(user.CurrentChannel != null) fai.Colour,
ctx.Chat.SendTo(user.CurrentChannel, new UserUpdatePacket(user)); 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 // Enforce a maximum amount of connections per user
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) { if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
@ -119,7 +129,7 @@ namespace SharpChat.PacketHandlers {
ctx.Connection.BumpPing(); ctx.Connection.BumpPing();
ctx.Connection.User = user; 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")) { if(File.Exists("welcome.txt")) {
IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x)); IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));

View file

@ -45,17 +45,13 @@ namespace SharpChat.PacketHandlers {
ctx.Chat.ContextAccess.Wait(); ctx.Chat.ContextAccess.Wait();
try { try {
ChatChannel channel = user.CurrentChannel; if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel channel)
&& !ctx.Chat.IsInChannel(user, channel)
if(channel == null
|| !ctx.Chat.IsInChannel(user, channel)
/*|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser))*/) /*|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser))*/)
return; return;
if(user.Status != ChatUserStatus.Online) { if(user.Status != ChatUserStatus.Online)
user.Status = ChatUserStatus.Online; ctx.Chat.UpdateUser(user, status: ChatUserStatus.Online);
ctx.Chat.SendTo(channel, new UserUpdatePacket(user));
}
int maxMsgLength = MaxMessageLength; int maxMsgLength = MaxMessageLength;
if(messageText.Length > maxMsgLength) if(messageText.Length > maxMsgLength)
@ -64,7 +60,7 @@ namespace SharpChat.PacketHandlers {
messageText = messageText.Trim(); messageText = messageText.Trim();
#if DEBUG #if DEBUG
Logger.Write($"<{ctx.Connection.Id} {user.Username}> {messageText}"); Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
#endif #endif
IChatMessage message = null; IChatMessage message = null;

View file

@ -17,6 +17,7 @@ namespace SharpChat {
public const int DEFAULT_MSG_LENGTH_MAX = 5000; public const int DEFAULT_MSG_LENGTH_MAX = 5000;
public const int DEFAULT_MAX_CONNECTIONS = 5; public const int DEFAULT_MAX_CONNECTIONS = 5;
public const int DEFAULT_FLOOD_KICK_LENGTH = 30; public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
public IWebSocketServer Server { get; } public IWebSocketServer Server { get; }
public ChatContext Context { get; } public ChatContext Context { get; }
@ -27,6 +28,7 @@ namespace SharpChat {
private readonly CachedValue<int> MaxMessageLength; private readonly CachedValue<int> MaxMessageLength;
private readonly CachedValue<int> MaxConnections; private readonly CachedValue<int> MaxConnections;
private readonly CachedValue<int> FloodKickLength; private readonly CachedValue<int> FloodKickLength;
private readonly CachedValue<int> FloodKickExemptRank;
private readonly List<IChatPacketHandler> GuestHandlers = new(); private readonly List<IChatPacketHandler> GuestHandlers = new();
private readonly List<IChatPacketHandler> AuthedHandlers = new(); private readonly List<IChatPacketHandler> AuthedHandlers = new();
@ -45,25 +47,26 @@ namespace SharpChat {
MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX); MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS); MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH); FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
Context = new ChatContext(evtStore); Context = new ChatContext(evtStore);
string[] channelNames = config.ReadValue("channels", new[] { "lounge" }); string[] channelNames = config.ReadValue("channels", new[] { "lounge" });
foreach(string channelName in channelNames) { foreach(string channelName in channelNames) {
ChatChannel channelInfo = new(channelName);
IConfig channelCfg = config.ScopeTo($"channels:{channelName}"); IConfig channelCfg = config.ScopeTo($"channels:{channelName}");
string tmp; string name = channelCfg.SafeReadValue("name", string.Empty);
tmp = channelCfg.SafeReadValue("name", string.Empty); if(string.IsNullOrWhiteSpace(name))
if(!string.IsNullOrWhiteSpace(tmp)) name = channelName;
channelInfo.Name = tmp;
channelInfo.Password = channelCfg.SafeReadValue("password", string.Empty); ChatChannel channelInfo = new(
channelInfo.Rank = channelCfg.SafeReadValue("minRank", 0); name,
channelCfg.SafeReadValue("password", string.Empty),
rank: channelCfg.SafeReadValue("minRank", 0)
);
Context.Channels.Add(channelInfo); Context.Channels.Add(channelInfo);
DefaultChannel ??= channelInfo; DefaultChannel ??= channelInfo;
} }
@ -152,7 +155,7 @@ namespace SharpChat {
Context.SafeUpdate(); Context.SafeUpdate();
// this doesn't affect non-authed connections????? // 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; ChatUser banUser = null;
string banAddr = string.Empty; string banAddr = string.Empty;
TimeSpan banDuration = TimeSpan.MinValue; TimeSpan banDuration = TimeSpan.MinValue;
@ -178,7 +181,7 @@ namespace SharpChat {
if(banUser is not null) { if(banUser is not null) {
if(banDuration == TimeSpan.MinValue) { if(banDuration == TimeSpan.MinValue) {
Context.SendTo(conn.User, new FloodWarningPacket()); Context.SendTo(conn.User, new LegacyCommandResponse(LCR.FLOOD_WARN, false));
} else { } else {
Context.BanUser(conn.User, banDuration, UserDisconnectReason.Flood); Context.BanUser(conn.User, banDuration, UserDisconnectReason.Flood);