No longer keep track of connections within the ChatUser class.

This commit is contained in:
flash 2023-02-16 23:33:48 +01:00
parent 06af94e94f
commit 13ae843c8d
28 changed files with 202 additions and 209 deletions

View File

@ -46,11 +46,6 @@ namespace SharpChat {
user.LeaveChannel(this);
}
public void Send(IServerPacket packet) {
foreach(ChatUser user in Users)
user.Send(packet);
}
public IEnumerable<ChatUser> GetUsers(IEnumerable<ChatUser> exclude = null) {
IEnumerable<ChatUser> users = Users.OrderByDescending(x => x.Rank);

View File

@ -17,7 +17,7 @@ namespace SharpChat {
public string Id { get; private set; }
public bool IsDisposed { get; private set; }
public DateTimeOffset LastPing { get; set; } = DateTimeOffset.MinValue;
public DateTimeOffset LastPing { get; set; } = DateTimeOffset.Now;
public ChatUser User { get; set; }
private int CloseCode { get; set; } = 1000;
@ -39,6 +39,10 @@ namespace SharpChat {
}
}
public bool IsAlive => !IsDisposed && !HasTimedOut;
public bool IsAuthed => IsAlive && User is not null;
public ChatConnection(IWebSocketConnection sock) {
Socket = sock;
Id = RNG.SecureRandomString(ID_LENGTH);

View File

@ -5,6 +5,7 @@ using SharpChat.Packet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace SharpChat {
public class ChatContext {
@ -25,19 +26,22 @@ namespace SharpChat {
}
public void Update() {
lock(UsersAccess)
foreach(ChatUser user in Users) {
IEnumerable<ChatConnection> timedOut = user.GetDeadConnections();
foreach(ChatConnection conn in timedOut) {
user.RemoveConnection(conn);
lock(ConnectionsAccess) {
foreach(ChatConnection conn in Connections)
if(!conn.IsDisposed && conn.HasTimedOut) {
conn.Dispose();
Logger.Write($"Nuked session {conn.Id} from {user.Username} (timeout)");
Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
}
if(!user.HasConnections)
UserLeave(null, user, UserDisconnectReason.TimeOut);
}
Connections.RemoveWhere(conn => conn.IsDisposed);
lock(UsersAccess)
foreach(ChatUser user in Users)
if(!Connections.Any(conn => conn.User == user)) {
UserLeave(null, user, UserDisconnectReason.TimeOut);
Logger.Write($"Timed out {user} (no more connections).");
}
}
}
public ChatConnection GetConnection(IWebSocketConnection sock) {
@ -46,18 +50,24 @@ namespace SharpChat {
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
if(duration > TimeSpan.Zero)
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
else
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Kicked));
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Kicked));
lock(ConnectionsAccess) {
foreach(ChatConnection conn in Connections)
if(conn.User == user)
conn.Dispose();
Connections.RemoveWhere(conn => conn.IsDisposed);
}
user.Close();
UserLeave(user.Channel, user, reason);
}
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
lock(EventsAccess) {
if(!chan.HasUser(user)) {
chan.Send(new UserConnectPacket(DateTimeOffset.Now, user));
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user));
Events.AddEvent(new UserConnectEvent(DateTimeOffset.Now, user, chan));
}
@ -94,28 +104,27 @@ namespace SharpChat {
lock(EventsAccess) {
chan.UserLeave(user);
chan.Send(new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason));
}
}
public void SwitchChannel(ChatUser user, ChatChannel chan, string password) {
if(user.CurrentChannel == chan) {
//user.Send(true, "samechan", chan.Name);
user.ForceChannel();
ForceChannel(user);
return;
}
if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.Owner != user) {
if(chan.Rank > user.Rank) {
user.Send(new LegacyCommandResponse(LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
user.ForceChannel();
SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
ForceChannel(user);
return;
}
if(chan.Password != password) {
user.Send(new LegacyCommandResponse(LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name));
user.ForceChannel();
SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name));
ForceChannel(user);
return;
}
}
@ -131,18 +140,18 @@ namespace SharpChat {
ChatChannel oldChan = user.CurrentChannel;
lock(EventsAccess) {
oldChan.Send(new UserChannelLeavePacket(user));
SendTo(oldChan, new UserChannelLeavePacket(user));
Events.AddEvent(new UserChannelLeaveEvent(DateTimeOffset.Now, user, oldChan));
chan.Send(new UserChannelJoinPacket(user));
SendTo(chan, new UserChannelJoinPacket(user));
Events.AddEvent(new UserChannelJoinEvent(DateTimeOffset.Now, user, chan));
user.Send(new ContextClearPacket(chan, ContextClearMode.MessagesUsers));
user.Send(new ContextUsersPacket(chan.GetUsers(new[] { user })));
SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers));
SendTo(user, new ContextUsersPacket(chan.GetUsers(new[] { user })));
foreach(IChatEvent msg in Events.GetTargetEventLog(chan.Name))
user.Send(new ContextMessagePacket(msg));
SendTo(user, new ContextMessagePacket(msg));
user.ForceChannel(chan);
ForceChannel(user, chan);
oldChan.UserLeave(user);
chan.UserJoin(user);
}
@ -153,9 +162,50 @@ namespace SharpChat {
}
public void Send(IServerPacket packet) {
lock(UsersAccess)
foreach(ChatUser user in Users)
user.Send(packet);
if(packet == null)
throw new ArgumentNullException(nameof(packet));
lock(ConnectionsAccess)
foreach(ChatConnection conn in Connections)
if(conn.IsAuthed)
conn.Send(packet);
}
public void SendTo(ChatUser user, IServerPacket packet) {
if(user == null)
throw new ArgumentNullException(nameof(user));
if(packet == null)
throw new ArgumentNullException(nameof(packet));
lock(ConnectionsAccess)
foreach(ChatConnection conn in Connections)
if(conn.IsAlive && conn.User == user)
conn.Send(packet);
}
public void SendTo(ChatChannel channel, IServerPacket packet) {
if(channel == null)
throw new ArgumentNullException(nameof(channel));
if(packet == null)
throw new ArgumentNullException(nameof(packet));
lock(ConnectionsAccess) {
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && channel.HasUser(c.User));
foreach(ChatConnection conn in conns)
conn.Send(packet);
}
}
public IPAddress[] GetRemoteAddresses(ChatUser user) {
lock(ConnectionsAccess)
return Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct().ToArray();
}
public void ForceChannel(ChatUser user, ChatChannel chan = null) {
if(user == null)
throw new ArgumentNullException(nameof(user));
SendTo(user, new UserChannelForceJoinPacket(chan ?? user.CurrentChannel));
}
public void UpdateChannel(ChatChannel channel, string name = null, bool? temporary = null, int? hierarchy = null, string password = null) {
@ -187,10 +237,10 @@ namespace SharpChat {
// 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
lock(UsersAccess)
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) {
user.Send(new ChannelUpdatePacket(prevName, channel));
SendTo(user, new ChannelUpdatePacket(prevName, channel));
if(nameUpdated)
user.ForceChannel();
ForceChannel(user);
}
}
@ -213,7 +263,7 @@ namespace SharpChat {
// Broadcast deletion of channel
lock(UsersAccess)
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank))
user.Send(new ChannelDeletePacket(channel));
SendTo(user, new ChannelDeletePacket(channel));
}
}
}

View File

@ -1,9 +1,7 @@
using SharpChat.Misuzu;
using SharpChat.Packet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace SharpChat {
@ -83,7 +81,6 @@ namespace SharpChat {
public class ChatUser : BasicUser, IPacketTarget {
public DateTimeOffset SilencedUntil { get; set; }
private readonly List<ChatConnection> Connections = new();
private readonly List<ChatChannel> Channels = new();
public readonly ChatRateLimiter RateLimiter = new();
@ -98,12 +95,6 @@ namespace SharpChat {
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<IPAddress> RemoteAddresses => Connections.Select(c => c.RemoteAddress);
public ChatUser() {}
public ChatUser(MisuzuAuthInfo auth) {
@ -125,26 +116,6 @@ namespace SharpChat {
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);
}
@ -165,27 +136,6 @@ namespace SharpChat {
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<ChatConnection> 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)

View File

@ -22,7 +22,7 @@ namespace SharpChat.Commands {
ctx.User.Status = ChatUserStatus.Away;
ctx.User.StatusMessage = statusText;
ctx.Channel.Send(new UserUpdatePacket(ctx.User));
ctx.Chat.SendTo(ctx.Channel, new UserUpdatePacket(ctx.User));
}
}
}

View File

@ -18,12 +18,12 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
Task.Run(async () => {
ctx.User.Send(new BanListPacket(
ctx.Chat.SendTo(ctx.User, new BanListPacket(
await Misuzu.GetBanListAsync()
));
}).Wait();

View File

@ -9,11 +9,11 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.Broadcast)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
ctx.Chat.Send(new LegacyCommandResponse(LCR.BROADCAST, false, string.Join(' ', ctx.Args)));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.BROADCAST, false, string.Join(' ', ctx.Args)));
}
}
}

View File

@ -9,7 +9,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(ctx.User.Can(ChatUserPermissions.CreateChannel)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
@ -17,7 +17,7 @@ namespace SharpChat.Commands {
bool createChanHasHierarchy;
if(!ctx.Args.Any() || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -27,20 +27,20 @@ namespace SharpChat.Commands {
createChanHierarchy = 0;
if(createChanHierarchy > ctx.User.Rank) {
ctx.User.Send(new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY));
return;
}
string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
if(!ChatChannel.CheckName(createChanName)) {
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_NAME_INVALID));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NAME_INVALID));
return;
}
lock(ctx.Chat.ChannelsAccess) {
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
return;
}
@ -54,11 +54,11 @@ namespace SharpChat.Commands {
ctx.Chat.Channels.Add(createChan);
lock(ctx.Chat.UsersAccess) {
foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
ccu.Send(new ChannelCreatePacket(ctx.Channel));
ctx.Chat.SendTo(ccu, new ChannelCreatePacket(ctx.Channel));
}
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_CREATED, false, createChan.Name));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_CREATED, false, createChan.Name));
}
}
}

View File

@ -12,7 +12,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.Args.Any() || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -22,18 +22,18 @@ namespace SharpChat.Commands {
delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
if(delChan == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, delChanName));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, delChanName));
return;
}
if(!ctx.User.Can(ChatUserPermissions.DeleteChannel) && delChan.Owner != ctx.User) {
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
return;
}
lock(ctx.Chat.ChannelsAccess)
ctx.Chat.RemoveChannel(delChan);
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_DELETED, false, delChan.Name));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETED, false, delChan.Name));
}
}
}

View File

@ -15,14 +15,14 @@ namespace SharpChat.Commands {
bool deleteAnyMessage = ctx.User.Can(ChatUserPermissions.DeleteAnyMessage);
if(!deleteAnyMessage && !ctx.User.Can(ChatUserPermissions.DeleteOwnMessage)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string firstArg = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -30,7 +30,7 @@ namespace SharpChat.Commands {
IChatEvent delMsg = ctx.Chat.Events.GetEvent(delSeqId);
if(delMsg == null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
ctx.User.Send(new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR));
return;
}

View File

@ -14,8 +14,8 @@ namespace SharpChat.Commands {
joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
if(joinChan == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
ctx.User.ForceChannel();
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
ctx.Chat.ForceChannel(ctx.User);
return;
}

View File

@ -21,7 +21,7 @@ namespace SharpChat.Commands {
bool isBanning = ctx.NameEquals("ban");
if(!ctx.User.Can(isBanning ? ChatUserPermissions.BanUser : ChatUserPermissions.KickUser)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
@ -32,19 +32,19 @@ namespace SharpChat.Commands {
lock(ctx.Chat.UsersAccess)
if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, banUser == null ? "User" : banUserTarget));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, banUser == null ? "User" : banUserTarget));
return;
}
if(banUser == ctx.User || banUser.Rank >= ctx.User.Rank) {
ctx.User.Send(new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName));
return;
}
TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero;
if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) {
if(durationSeconds < 0) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -60,18 +60,19 @@ namespace SharpChat.Commands {
string banReason = string.Join(' ', ctx.Args.Skip(banReasonIndex));
Task.Run(async () => {
string userId = banUser.UserId.ToString();
string userIp = ctx.Chat.GetRemoteAddresses(banUser).FirstOrDefault()?.ToString() ?? string.Empty;
// obviously it makes no sense to only check for one ip address but that's current misuzu limitations
MisuzuBanInfo fbi = await Misuzu.CheckBanAsync(
banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString()
);
MisuzuBanInfo fbi = await Misuzu.CheckBanAsync(userId, userIp);
if(fbi.IsBanned && !fbi.HasExpired) {
ctx.User.Send(new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName));
return;
}
await Misuzu.CreateBanAsync(
banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString(),
userId, userIp,
ctx.User.UserId.ToString(), ctx.Connection.RemoteAddress.ToString(),
duration, banReason
);

View File

@ -11,7 +11,7 @@ namespace SharpChat.Commands {
bool setOthersNick = ctx.User.Can(ChatUserPermissions.SetOthersNickname);
if(!setOthersNick && !ctx.User.Can(ChatUserPermissions.SetOwnNickname)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
ChatUser targetUser = null;
@ -26,7 +26,7 @@ namespace SharpChat.Commands {
targetUser ??= ctx.User;
if(ctx.Args.Length < offset) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -44,13 +44,13 @@ namespace SharpChat.Commands {
lock(ctx.Chat.UsersAccess)
if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) {
ctx.User.Send(new LegacyCommandResponse(LCR.NAME_IN_USE, true, 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.Channel.Send(new UserUpdatePacket(targetUser, previousName));
ctx.Chat.SendTo(ctx.Channel, new UserUpdatePacket(targetUser, previousName));
}
}
}

View File

@ -20,13 +20,13 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string unbanAddrTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress unbanAddr)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -36,15 +36,15 @@ namespace SharpChat.Commands {
MisuzuBanInfo banInfo = await Misuzu.CheckBanAsync(ipAddr: unbanAddrTarget);
if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
return;
}
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.RemoteAddress);
if(wasBanned)
ctx.User.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanAddrTarget));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanAddrTarget));
else
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
}).Wait();
}
}

View File

@ -19,14 +19,14 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
bool unbanUserTargetIsName = true;
string unbanUserTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -46,15 +46,15 @@ namespace SharpChat.Commands {
MisuzuBanInfo banInfo = await Misuzu.CheckBanAsync(unbanUserTarget, userIdIsName: unbanUserTargetIsName);
if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
return;
}
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.UserId);
if(wasBanned)
ctx.User.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanUserTarget));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanUserTarget));
else
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
}).Wait();
}
}

View File

@ -9,7 +9,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.SetChannelPassword) || ctx.Channel.Owner != ctx.User) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
@ -20,7 +20,7 @@ namespace SharpChat.Commands {
lock(ctx.Chat.ChannelsAccess)
ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_PASSWORD_CHANGED, false));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_PASSWORD_CHANGED, false));
}
}
}

View File

@ -11,18 +11,18 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.SetChannelHierarchy) || ctx.Channel.Owner != ctx.User) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
if(!ctx.Args.Any() || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) {
ctx.User.Send(new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY));
return;
}
lock(ctx.Chat.ChannelsAccess)
ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_HIERARCHY_CHANGED, false));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_HIERARCHY_CHANGED, false));
}
}
}

View File

@ -11,7 +11,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.SeeIPAddress)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, "/ip"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, "/ip"));
return;
}
@ -20,12 +20,12 @@ namespace SharpChat.Commands {
lock(ctx.Chat.UsersAccess)
if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
return;
}
foreach(IPAddress ip in ipUser.RemoteAddresses.Distinct().ToArray())
ctx.User.Send(new LegacyCommandResponse(LCR.IP_ADDRESS, false, ipUser.Username, ip));
foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser))
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.IP_ADDRESS, false, ipUser.Username, ip));
}
}
}

View File

@ -19,7 +19,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(ctx.User.UserId != 1) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
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.SilenceUser)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
@ -19,22 +19,22 @@ namespace SharpChat.Commands {
lock(ctx.Chat.UsersAccess)
if(string.IsNullOrWhiteSpace(silUserStr) || (silUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(silUserStr))) == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, silUserStr ?? "User"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, silUserStr ?? "User"));
return;
}
if(silUser == ctx.User) {
ctx.User.Send(new LegacyCommandResponse(LCR.SILENCE_SELF));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_SELF));
return;
}
if(silUser.Rank >= ctx.User.Rank) {
ctx.User.Send(new LegacyCommandResponse(LCR.SILENCE_HIERARCHY));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_HIERARCHY));
return;
}
if(silUser.IsSilenced) {
ctx.User.Send(new LegacyCommandResponse(LCR.SILENCE_ALREADY));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_ALREADY));
return;
}
@ -42,7 +42,7 @@ namespace SharpChat.Commands {
if(ctx.Args.Length > 1) {
if(!double.TryParse(ctx.Args.ElementAt(1), out double silenceSeconds)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -50,8 +50,8 @@ namespace SharpChat.Commands {
}
silUser.SilencedUntil = silenceUntil;
silUser.Send(new LegacyCommandResponse(LCR.SILENCED, false));
ctx.User.Send(new LegacyCommandResponse(LCR.TARGET_SILENCED, false, silUser.DisplayName));
ctx.Chat.SendTo(silUser, new LegacyCommandResponse(LCR.SILENCED, false));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_SILENCED, false, silUser.DisplayName));
}
}
}

View File

@ -10,7 +10,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.SilenceUser)) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
@ -19,23 +19,23 @@ namespace SharpChat.Commands {
lock(ctx.Chat.UsersAccess)
if(string.IsNullOrWhiteSpace(unsilUserStr) || (unsilUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unsilUserStr))) == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, unsilUserStr ?? "User"));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, unsilUserStr ?? "User"));
return;
}
if(unsilUser.Rank >= ctx.User.Rank) {
ctx.User.Send(new LegacyCommandResponse(LCR.UNSILENCE_HIERARCHY));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.UNSILENCE_HIERARCHY));
return;
}
if(!unsilUser.IsSilenced) {
ctx.User.Send(new LegacyCommandResponse(LCR.NOT_SILENCED));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.NOT_SILENCED));
return;
}
unsilUser.SilencedUntil = DateTimeOffset.MinValue;
unsilUser.Send(new LegacyCommandResponse(LCR.UNSILENCED, false));
ctx.User.Send(new LegacyCommandResponse(LCR.TARGET_UNSILENCED, false, unsilUser.DisplayName));
ctx.Chat.SendTo(unsilUser, new LegacyCommandResponse(LCR.UNSILENCED, false));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_UNSILENCED, false, unsilUser.DisplayName));
}
}
}

View File

@ -12,7 +12,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) {
if(ctx.Args.Length < 2) {
ctx.User.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
@ -22,7 +22,7 @@ namespace SharpChat.Commands {
whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
if(whisperUser == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, whisperUserStr));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, whisperUserStr));
return;
}
if(whisperUser == ctx.User)
@ -30,7 +30,7 @@ namespace SharpChat.Commands {
string whisperStr = string.Join(' ', ctx.Args.Skip(1));
whisperUser.Send(new ChatMessageAddPacket(new ChatMessage {
ctx.Chat.SendTo(whisperUser, new ChatMessageAddPacket(new ChatMessage {
DateTime = DateTimeOffset.Now,
Target = whisperUser,
TargetName = whisperUser.TargetName,
@ -38,7 +38,7 @@ namespace SharpChat.Commands {
Text = whisperStr,
Flags = ChatMessageFlags.Private,
}));
ctx.User.Send(new ChatMessageAddPacket(new ChatMessage {
ctx.Chat.SendTo(ctx.User, new ChatMessageAddPacket(new ChatMessage {
DateTime = DateTimeOffset.Now,
Target = whisperUser,
TargetName = whisperUser.TargetName,

View File

@ -28,19 +28,19 @@ namespace SharpChat.Commands {
if(whoChanSB.Length > 2)
whoChanSB.Length -= 2;
ctx.User.Send(new LegacyCommandResponse(LCR.USERS_LISTING_SERVER, false, whoChanSB));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_SERVER, false, whoChanSB));
} else {
ChatChannel whoChan;
lock(ctx.Chat.ChannelsAccess)
whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
if(whoChan == null) {
ctx.User.Send(new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
return;
}
if(whoChan.Rank > ctx.User.Rank || (whoChan.HasPassword && !ctx.User.Can(ChatUserPermissions.JoinAnyChannel))) {
ctx.User.Send(new LegacyCommandResponse(LCR.USERS_LISTING_ERROR, true, whoChanStr));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_ERROR, true, whoChanStr));
return;
}
@ -58,7 +58,7 @@ namespace SharpChat.Commands {
if(whoChanSB.Length > 2)
whoChanSB.Length -= 2;
ctx.User.Send(new LegacyCommandResponse(LCR.USERS_LISTING_CHANNEL, false, whoChan.Name, whoChanSB));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_CHANNEL, false, whoChan.Name, whoChanSB));
}
}
}

View File

@ -1,6 +1,5 @@
namespace SharpChat {
public interface IPacketTarget {
string TargetName { get; }
void Send(IServerPacket packet);
}
}

View File

@ -99,28 +99,27 @@ namespace SharpChat.PacketHandlers {
}
lock(ctx.Chat.UsersAccess) {
ChatUser aUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId);
ChatUser user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId);
if(aUser == null)
aUser = new ChatUser(fai);
if(user == null)
user = new ChatUser(fai);
else {
aUser.ApplyAuth(fai);
aUser.Channel?.Send(new UserUpdatePacket(aUser));
user.ApplyAuth(fai);
if(user.Channel != null)
ctx.Chat.SendTo(user.Channel, new UserUpdatePacket(user));
}
// Enforce a maximum amount of connections per user
if(aUser.ConnectionCount >= MaxConnections) {
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
ctx.Connection.Dispose();
return;
}
lock(ctx.Chat.ConnectionsAccess)
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
ctx.Connection.Dispose();
return;
}
// Bumping the ping to prevent upgrading
ctx.Connection.BumpPing();
aUser.AddConnection(ctx.Connection);
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, $"Welcome to Flashii Chat, {aUser.Username}!"));
ctx.Connection.User = user;
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));
@ -130,7 +129,7 @@ namespace SharpChat.PacketHandlers {
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));
}
ctx.Chat.HandleJoin(aUser, DefaultChannel, ctx.Connection, MaxMessageLength);
ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
}
}).Wait();
}

View File

@ -1,6 +1,7 @@
using SharpChat.Misuzu;
using SharpChat.Packet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -32,11 +33,16 @@ namespace SharpChat.PacketHandlers {
lock(BumpAccess) {
if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
(string, string)[] bumpList;
lock(ctx.Chat.UsersAccess)
bumpList = ctx.Chat.Users
.Where(u => u.HasConnections && u.Status == ChatUserStatus.Online)
.Select(u => (u.UserId.ToString(), u.RemoteAddresses.FirstOrDefault()?.ToString() ?? string.Empty))
lock(ctx.Chat.UsersAccess) {
IEnumerable<ChatUser> filtered = ctx.Chat.Users.Where(u => u.Status == ChatUserStatus.Online);
lock(ctx.Chat.ConnectionsAccess)
filtered = filtered.Where(u => ctx.Chat.Connections.Any(c => c.User == u));
bumpList = filtered
.Select(u => (u.UserId.ToString(), ctx.Chat.GetRemoteAddresses(u).FirstOrDefault()?.ToString() ?? string.Empty))
.ToArray();
}
if(bumpList.Any())
Task.Run(async () => {

View File

@ -51,7 +51,7 @@ namespace SharpChat.PacketHandlers {
if(user.Status != ChatUserStatus.Online) {
user.Status = ChatUserStatus.Online;
channel.Send(new UserUpdatePacket(user));
ctx.Chat.SendTo(channel, new UserUpdatePacket(user));
}
int maxMsgLength = MaxMessageLength;
@ -97,7 +97,7 @@ namespace SharpChat.PacketHandlers {
lock(ctx.Chat.EventsAccess) {
ctx.Chat.Events.AddEvent(message);
channel.Send(new ChatMessageAddPacket(message));
ctx.Chat.SendTo(channel, new ChatMessageAddPacket(message));
}
}
}

View File

@ -143,29 +143,18 @@ namespace SharpChat {
private void OnClose(IWebSocketConnection sock) {
Logger.Write($"Connection closed from {sock.ConnectionInfo.ClientIpAddress}:{sock.ConnectionInfo.ClientPort}");
ChatConnection conn;
lock(Context.ConnectionsAccess)
conn = Context.GetConnection(sock);
lock(Context.ConnectionsAccess) {
ChatConnection conn = Context.GetConnection(sock);
if(conn == null)
return;
// Remove connection from user
if(conn?.User != null) {
// RemoveConnection sets conn.User to null so we must grab a local copy.
ChatUser user = conn.User;
user.RemoveConnection(conn);
if(!user.HasConnections)
Context.UserLeave(null, user);
}
// Update context
Context.Update();
// Remove connection from server
lock(Context.ConnectionsAccess)
Context.Connections.Remove(conn);
conn?.Dispose();
if(conn.User != null && !Context.Connections.Any(c => c.User == conn.User))
Context.UserLeave(null, conn.User);
}
Context.Update();
}
private void OnError(IWebSocketConnection sock, Exception ex) {
@ -210,7 +199,7 @@ namespace SharpChat {
}).Wait();
return;
} else if(conn.User.RateLimiter.State == ChatRateLimitState.Warning)
conn.User.Send(new FloodWarningPacket());
Context.SendTo(conn.User, new FloodWarningPacket());
}
ChatPacketHandlerContext context = new(msg, Context, conn);