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 {
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();
}

View File

@ -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);
}
}
}

View File

@ -19,6 +19,7 @@ namespace SharpChat {
public IEventStorage Events { get; }
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new();
public Dictionary<long, RateLimiter> UserRateLimiters { get; } = new();
public Dictionary<long, ChatChannel> 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<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) {
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));
}
}

View File

@ -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("&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))
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() {

View File

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

View File

@ -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
);
}
}
}

View File

@ -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))

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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,
}));
}

View File

@ -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("</a>, ");
}
@ -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("</a>, ");
}

View File

@ -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;

View File

@ -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; }

View File

@ -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;
}

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 USER_NOT_BANNED = "notban";
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(User.UserId);
sb.Append('\t');
sb.Append(User.DisplayName);
sb.Append(User.LegacyName);
sb.Append('\t');
switch(Reason) {

View File

@ -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");

View File

@ -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<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));

View File

@ -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;

View File

@ -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<int> MaxMessageLength;
private readonly CachedValue<int> MaxConnections;
private readonly CachedValue<int> FloodKickLength;
private readonly CachedValue<int> FloodKickExemptRank;
private readonly List<IChatPacketHandler> GuestHandlers = new();
private readonly List<IChatPacketHandler> 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);