Less haphazard locking (perhaps too?)
This commit is contained in:
parent
70df99fe9b
commit
86a46539f2
22 changed files with 257 additions and 306 deletions
|
@ -1,79 +1,74 @@
|
||||||
using Fleck;
|
using SharpChat.Events;
|
||||||
using SharpChat.Events;
|
|
||||||
using SharpChat.EventStorage;
|
using SharpChat.EventStorage;
|
||||||
using SharpChat.Packet;
|
using SharpChat.Packet;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public class ChatContext {
|
public class ChatContext {
|
||||||
public record ChannelUserAssoc(long UserId, string ChannelName);
|
public record ChannelUserAssoc(long UserId, string ChannelName);
|
||||||
|
|
||||||
|
public readonly SemaphoreSlim ContextAccess = new(1, 1);
|
||||||
|
|
||||||
public HashSet<ChatChannel> Channels { get; } = new();
|
public HashSet<ChatChannel> Channels { get; } = new();
|
||||||
public readonly object ChannelsAccess = new();
|
|
||||||
|
|
||||||
public HashSet<ChatConnection> Connections { get; } = new();
|
public HashSet<ChatConnection> Connections { get; } = new();
|
||||||
public readonly object ConnectionsAccess = new();
|
|
||||||
|
|
||||||
public HashSet<ChatUser> Users { get; } = new();
|
public HashSet<ChatUser> Users { get; } = new();
|
||||||
public readonly object UsersAccess = new();
|
|
||||||
|
|
||||||
public IEventStorage Events { get; }
|
public IEventStorage Events { get; }
|
||||||
public readonly object EventsAccess = new();
|
|
||||||
|
|
||||||
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new();
|
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new();
|
||||||
public readonly object ChannelUsersAccess = new();
|
public Dictionary<long, RateLimiter> UserRateLimiters { get; } = new();
|
||||||
|
|
||||||
public ChatContext(IEventStorage evtStore) {
|
public ChatContext(IEventStorage evtStore) {
|
||||||
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
|
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update() {
|
public void Update() {
|
||||||
lock(ConnectionsAccess) {
|
foreach(ChatConnection conn in Connections)
|
||||||
foreach(ChatConnection conn in Connections)
|
if(!conn.IsDisposed && conn.HasTimedOut) {
|
||||||
if(!conn.IsDisposed && conn.HasTimedOut) {
|
conn.Dispose();
|
||||||
conn.Dispose();
|
Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
|
||||||
Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Connections.RemoveWhere(conn => conn.IsDisposed);
|
Connections.RemoveWhere(conn => conn.IsDisposed);
|
||||||
|
|
||||||
lock(UsersAccess)
|
foreach(ChatUser user in Users)
|
||||||
foreach(ChatUser user in Users)
|
if(!Connections.Any(conn => conn.User == user)) {
|
||||||
if(!Connections.Any(conn => conn.User == user)) {
|
HandleDisconnect(user, UserDisconnectReason.TimeOut);
|
||||||
HandleDisconnect(user, UserDisconnectReason.TimeOut);
|
Logger.Write($"Timed out {user} (no more connections).");
|
||||||
Logger.Write($"Timed out {user} (no more connections).");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SafeUpdate() {
|
||||||
|
ContextAccess.Wait();
|
||||||
|
try {
|
||||||
|
Update();
|
||||||
|
} finally {
|
||||||
|
ContextAccess.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsInChannel(ChatUser user, ChatChannel channel) {
|
public bool IsInChannel(ChatUser user, ChatChannel channel) {
|
||||||
lock(ChannelUsersAccess)
|
return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name));
|
||||||
return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetUserChannelNames(ChatUser user) {
|
public string[] GetUserChannelNames(ChatUser user) {
|
||||||
lock(ChannelUsersAccess)
|
return ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName).ToArray();
|
||||||
return ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChatChannel[] GetUserChannels(ChatUser user) {
|
public ChatChannel[] GetUserChannels(ChatUser user) {
|
||||||
string[] names = GetUserChannelNames(user);
|
string[] names = GetUserChannelNames(user);
|
||||||
lock(ChannelsAccess)
|
return Channels.Where(c => names.Any(n => c.NameEquals(n))).ToArray();
|
||||||
return Channels.Where(c => names.Any(n => c.NameEquals(n))).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long[] GetChannelUserIds(ChatChannel channel) {
|
public long[] GetChannelUserIds(ChatChannel channel) {
|
||||||
lock(ChannelUsersAccess)
|
return ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId).ToArray();
|
||||||
return ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChatUser[] GetChannelUsers(ChatChannel channel) {
|
public ChatUser[] GetChannelUsers(ChatChannel channel) {
|
||||||
long[] ids = GetChannelUserIds(channel);
|
long[] ids = GetChannelUserIds(channel);
|
||||||
lock(UsersAccess)
|
return Users.Where(u => ids.Contains(u.UserId)).ToArray();
|
||||||
return Users.Where(u => ids.Contains(u.UserId)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
||||||
|
@ -82,63 +77,48 @@ namespace SharpChat {
|
||||||
else
|
else
|
||||||
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Kicked));
|
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Kicked));
|
||||||
|
|
||||||
lock(ConnectionsAccess) {
|
foreach(ChatConnection conn in Connections)
|
||||||
foreach(ChatConnection conn in Connections)
|
if(conn.User == user)
|
||||||
if(conn.User == user)
|
conn.Dispose();
|
||||||
conn.Dispose();
|
Connections.RemoveWhere(conn => conn.IsDisposed);
|
||||||
Connections.RemoveWhere(conn => conn.IsDisposed);
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleDisconnect(user, reason);
|
HandleDisconnect(user, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
|
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
|
||||||
lock(EventsAccess) {
|
if(!IsInChannel(user, chan)) {
|
||||||
if(!IsInChannel(user, chan)) {
|
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user));
|
||||||
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user));
|
Events.AddEvent(new UserConnectEvent(DateTimeOffset.Now, user, chan));
|
||||||
Events.AddEvent(new UserConnectEvent(DateTimeOffset.Now, user, chan));
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength));
|
|
||||||
conn.Send(new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
|
|
||||||
|
|
||||||
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
|
||||||
conn.Send(new ContextMessagePacket(msg));
|
|
||||||
|
|
||||||
lock(ChannelsAccess)
|
|
||||||
conn.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank)));
|
|
||||||
|
|
||||||
lock(UsersAccess)
|
|
||||||
Users.Add(user);
|
|
||||||
|
|
||||||
lock(ChannelUsersAccess) {
|
|
||||||
ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
|
|
||||||
user.CurrentChannel = chan;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength));
|
||||||
|
conn.Send(new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
|
||||||
|
|
||||||
|
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
||||||
|
conn.Send(new ContextMessagePacket(msg));
|
||||||
|
|
||||||
|
conn.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank)));
|
||||||
|
|
||||||
|
Users.Add(user);
|
||||||
|
|
||||||
|
ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
|
||||||
|
user.CurrentChannel = chan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleDisconnect(ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) {
|
public void HandleDisconnect(ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) {
|
||||||
user.Status = ChatUserStatus.Offline;
|
user.Status = ChatUserStatus.Offline;
|
||||||
|
Users.Remove(user);
|
||||||
|
|
||||||
lock(EventsAccess) {
|
ChatChannel[] channels = GetUserChannels(user);
|
||||||
lock(UsersAccess)
|
|
||||||
Users.Remove(user);
|
|
||||||
|
|
||||||
lock(ChannelUsersAccess) {
|
foreach(ChatChannel chan in channels) {
|
||||||
ChatChannel[] channels = GetUserChannels(user);
|
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
|
||||||
|
|
||||||
foreach(ChatChannel chan in channels) {
|
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
|
||||||
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
|
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason));
|
||||||
|
|
||||||
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
|
if(chan.IsTemporary && chan.Owner == user)
|
||||||
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason));
|
RemoveChannel(chan);
|
||||||
|
|
||||||
if(chan.IsTemporary && chan.Owner == user)
|
|
||||||
lock(ChannelsAccess)
|
|
||||||
RemoveChannel(chan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,46 +146,39 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ForceChannelSwitch(ChatUser user, ChatChannel chan) {
|
public void ForceChannelSwitch(ChatUser user, ChatChannel chan) {
|
||||||
lock(ChannelsAccess)
|
if(!Channels.Contains(chan))
|
||||||
if(!Channels.Contains(chan))
|
return;
|
||||||
return;
|
|
||||||
|
|
||||||
ChatChannel oldChan = user.CurrentChannel;
|
ChatChannel oldChan = user.CurrentChannel;
|
||||||
|
|
||||||
lock(EventsAccess) {
|
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));
|
SendTo(chan, new UserChannelJoinPacket(user));
|
||||||
SendTo(chan, new UserChannelJoinPacket(user));
|
Events.AddEvent(new UserChannelJoinEvent(DateTimeOffset.Now, user, chan));
|
||||||
Events.AddEvent(new UserChannelJoinEvent(DateTimeOffset.Now, user, chan));
|
|
||||||
|
|
||||||
SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers));
|
SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers));
|
||||||
SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
|
SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
|
||||||
|
|
||||||
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
||||||
SendTo(user, new ContextMessagePacket(msg));
|
SendTo(user, new ContextMessagePacket(msg));
|
||||||
|
|
||||||
ForceChannel(user, chan);
|
ForceChannel(user, chan);
|
||||||
|
|
||||||
lock(ChannelUsersAccess) {
|
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;
|
||||||
user.CurrentChannel = chan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(oldChan.IsTemporary && oldChan.Owner == user)
|
if(oldChan.IsTemporary && oldChan.Owner == user)
|
||||||
lock(ChannelsAccess)
|
RemoveChannel(oldChan);
|
||||||
RemoveChannel(oldChan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send(IServerPacket packet) {
|
public void Send(IServerPacket packet) {
|
||||||
if(packet == null)
|
if(packet == null)
|
||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
|
|
||||||
lock(ConnectionsAccess)
|
foreach(ChatConnection conn in Connections)
|
||||||
foreach(ChatConnection conn in Connections)
|
if(conn.IsAuthed)
|
||||||
if(conn.IsAuthed)
|
conn.Send(packet);
|
||||||
conn.Send(packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendTo(ChatUser user, IServerPacket packet) {
|
public void SendTo(ChatUser user, IServerPacket packet) {
|
||||||
|
@ -214,10 +187,9 @@ namespace SharpChat {
|
||||||
if(packet == null)
|
if(packet == null)
|
||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
|
|
||||||
lock(ConnectionsAccess)
|
foreach(ChatConnection conn in Connections)
|
||||||
foreach(ChatConnection conn in Connections)
|
if(conn.IsAlive && conn.User == user)
|
||||||
if(conn.IsAlive && conn.User == user)
|
conn.Send(packet);
|
||||||
conn.Send(packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendTo(ChatChannel channel, IServerPacket packet) {
|
public void SendTo(ChatChannel channel, IServerPacket packet) {
|
||||||
|
@ -227,16 +199,13 @@ namespace SharpChat {
|
||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
|
|
||||||
// might be faster to grab the users first and then cascade into that SendTo
|
// might be faster to grab the users first and then cascade into that SendTo
|
||||||
lock(ConnectionsAccess) {
|
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel));
|
||||||
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel));
|
foreach(ChatConnection conn in conns)
|
||||||
foreach(ChatConnection conn in conns)
|
conn.Send(packet);
|
||||||
conn.Send(packet);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPAddress[] GetRemoteAddresses(ChatUser user) {
|
public IPAddress[] GetRemoteAddresses(ChatUser user) {
|
||||||
lock(ConnectionsAccess)
|
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ForceChannel(ChatUser user, ChatChannel chan = null) {
|
public void ForceChannel(ChatUser user, ChatChannel chan = null) {
|
||||||
|
@ -273,13 +242,12 @@ namespace SharpChat {
|
||||||
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
|
// 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)) {
|
||||||
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) {
|
SendTo(user, new ChannelUpdatePacket(prevName, channel));
|
||||||
SendTo(user, new ChannelUpdatePacket(prevName, channel));
|
|
||||||
|
|
||||||
if(nameUpdated)
|
if(nameUpdated)
|
||||||
ForceChannel(user);
|
ForceChannel(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveChannel(ChatChannel channel) {
|
public void RemoveChannel(ChatChannel channel) {
|
||||||
|
@ -299,9 +267,8 @@ namespace SharpChat {
|
||||||
SwitchChannel(user, defaultChannel, string.Empty);
|
SwitchChannel(user, defaultChannel, string.Empty);
|
||||||
|
|
||||||
// Broadcast deletion of channel
|
// Broadcast deletion of channel
|
||||||
lock(UsersAccess)
|
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 ChannelDeletePacket(channel));
|
||||||
SendTo(user, new ChannelDeletePacket(channel));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,6 @@ namespace SharpChat {
|
||||||
public bool HasFloodProtection
|
public bool HasFloodProtection
|
||||||
=> Rank < RANK_NO_FLOOD;
|
=> Rank < RANK_NO_FLOOD;
|
||||||
|
|
||||||
public readonly RateLimiter RateLimiter = new(DEFAULT_SIZE, DEFAULT_MINIMUM_DELAY, DEFAULT_RISKY_OFFSET);
|
|
||||||
|
|
||||||
// This needs to be a session thing
|
// This needs to be a session thing
|
||||||
public ChatChannel CurrentChannel { get; set; }
|
public ChatChannel CurrentChannel { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -38,28 +38,24 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(ctx.Chat.ChannelsAccess) {
|
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
|
||||||
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatChannel createChan = new() {
|
|
||||||
Name = createChanName,
|
|
||||||
IsTemporary = !ctx.User.Can(ChatUserPermissions.SetChannelPermanent),
|
|
||||||
Rank = createChanHierarchy,
|
|
||||||
Owner = ctx.User,
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.Chat.Channels.Add(createChan);
|
|
||||||
lock(ctx.Chat.UsersAccess) {
|
|
||||||
foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
|
|
||||||
ctx.Chat.SendTo(ccu, new ChannelCreatePacket(ctx.Channel));
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
|
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_CREATED, false, createChan.Name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatChannel createChan = new() {
|
||||||
|
Name = createChanName,
|
||||||
|
IsTemporary = !ctx.User.Can(ChatUserPermissions.SetChannelPermanent),
|
||||||
|
Rank = createChanHierarchy,
|
||||||
|
Owner = ctx.User,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.Chat.Channels.Add(createChan);
|
||||||
|
foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
|
||||||
|
ctx.Chat.SendTo(ccu, new ChannelCreatePacket(ctx.Channel));
|
||||||
|
|
||||||
|
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
|
||||||
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_CREATED, false, createChan.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,7 @@ namespace SharpChat.Commands {
|
||||||
}
|
}
|
||||||
|
|
||||||
string delChanName = string.Join('_', ctx.Args);
|
string delChanName = string.Join('_', ctx.Args);
|
||||||
ChatChannel delChan;
|
ChatChannel delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
|
||||||
lock(ctx.Chat.ChannelsAccess)
|
|
||||||
delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
|
|
||||||
|
|
||||||
if(delChan == null) {
|
if(delChan == null) {
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, delChanName));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, delChanName));
|
||||||
|
@ -31,8 +29,7 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(ctx.Chat.ChannelsAccess)
|
ctx.Chat.RemoveChannel(delChan);
|
||||||
ctx.Chat.RemoveChannel(delChan);
|
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETED, false, delChan.Name));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETED, false, delChan.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,17 +26,15 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(ctx.Chat.EventsAccess) {
|
IChatEvent delMsg = ctx.Chat.Events.GetEvent(delSeqId);
|
||||||
IChatEvent delMsg = ctx.Chat.Events.GetEvent(delSeqId);
|
|
||||||
|
|
||||||
if(delMsg == null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
|
if(delMsg == null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Chat.Events.RemoveEvent(delMsg);
|
|
||||||
ctx.Chat.Send(new ChatMessageDeletePacket(delMsg.SequenceId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Chat.Events.RemoveEvent(delMsg);
|
||||||
|
ctx.Chat.Send(new ChatMessageDeletePacket(delMsg.SequenceId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,7 @@ namespace SharpChat.Commands {
|
||||||
|
|
||||||
public void Dispatch(ChatCommandContext ctx) {
|
public void Dispatch(ChatCommandContext ctx) {
|
||||||
string joinChanStr = ctx.Args.FirstOrDefault();
|
string joinChanStr = ctx.Args.FirstOrDefault();
|
||||||
ChatChannel joinChan;
|
ChatChannel joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
|
||||||
lock(ctx.Chat.ChannelsAccess)
|
|
||||||
joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
|
|
||||||
|
|
||||||
if(joinChan == null) {
|
if(joinChan == null) {
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
|
||||||
|
|
|
@ -30,11 +30,10 @@ namespace SharpChat.Commands {
|
||||||
int banReasonIndex = 1;
|
int banReasonIndex = 1;
|
||||||
ChatUser banUser = null;
|
ChatUser banUser = null;
|
||||||
|
|
||||||
lock(ctx.Chat.UsersAccess)
|
if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
|
||||||
if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
|
ctx.Chat.SendTo(ctx.User, 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;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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.DisplayName));
|
||||||
|
|
|
@ -18,8 +18,7 @@ namespace SharpChat.Commands {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {
|
if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {
|
||||||
lock(ctx.Chat.UsersAccess)
|
targetUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == targetUserId);
|
||||||
targetUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == targetUserId);
|
|
||||||
++offset;
|
++offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,11 +41,10 @@ namespace SharpChat.Commands {
|
||||||
else if(string.IsNullOrEmpty(nickStr))
|
else if(string.IsNullOrEmpty(nickStr))
|
||||||
nickStr = null;
|
nickStr = null;
|
||||||
|
|
||||||
lock(ctx.Chat.UsersAccess)
|
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;
|
targetUser.Nickname = nickStr;
|
||||||
|
|
|
@ -30,13 +30,10 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatUser unbanUser;
|
ChatUser unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
|
||||||
lock(ctx.Chat.UsersAccess)
|
|
||||||
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
|
|
||||||
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
|
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
|
||||||
unbanUserTargetIsName = false;
|
unbanUserTargetIsName = false;
|
||||||
lock(ctx.Chat.UsersAccess)
|
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId);
|
||||||
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unbanUser != null)
|
if(unbanUser != null)
|
||||||
|
|
|
@ -18,8 +18,7 @@ namespace SharpChat.Commands {
|
||||||
if(string.IsNullOrWhiteSpace(chanPass))
|
if(string.IsNullOrWhiteSpace(chanPass))
|
||||||
chanPass = string.Empty;
|
chanPass = string.Empty;
|
||||||
|
|
||||||
lock(ctx.Chat.ChannelsAccess)
|
ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
|
||||||
ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
|
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_PASSWORD_CHANGED, false));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_PASSWORD_CHANGED, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,7 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(ctx.Chat.ChannelsAccess)
|
ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
|
||||||
ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
|
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_HIERARCHY_CHANGED, false));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_HIERARCHY_CHANGED, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,10 @@ namespace SharpChat.Commands {
|
||||||
string ipUserStr = ctx.Args.FirstOrDefault();
|
string ipUserStr = ctx.Args.FirstOrDefault();
|
||||||
ChatUser ipUser;
|
ChatUser ipUser;
|
||||||
|
|
||||||
lock(ctx.Chat.UsersAccess)
|
if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
|
||||||
if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
||||||
|
|
|
@ -27,9 +27,8 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(ctx.NameEquals("restart"))
|
if(ctx.NameEquals("restart"))
|
||||||
lock(ctx.Chat.ConnectionsAccess)
|
foreach(ChatConnection conn in ctx.Chat.Connections)
|
||||||
foreach(ChatConnection conn in ctx.Chat.Connections)
|
conn.PrepareForRestart();
|
||||||
conn.PrepareForRestart();
|
|
||||||
|
|
||||||
ctx.Chat.Update();
|
ctx.Chat.Update();
|
||||||
WaitHandle?.Set();
|
WaitHandle?.Set();
|
||||||
|
|
|
@ -17,11 +17,10 @@ namespace SharpChat.Commands {
|
||||||
string silUserStr = ctx.Args.FirstOrDefault();
|
string silUserStr = ctx.Args.FirstOrDefault();
|
||||||
ChatUser silUser;
|
ChatUser silUser;
|
||||||
|
|
||||||
lock(ctx.Chat.UsersAccess)
|
if(string.IsNullOrWhiteSpace(silUserStr) || (silUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(silUserStr))) == null) {
|
||||||
if(string.IsNullOrWhiteSpace(silUserStr) || (silUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(silUserStr))) == null) {
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, silUserStr ?? "User"));
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, silUserStr ?? "User"));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(silUser == ctx.User) {
|
if(silUser == ctx.User) {
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_SELF));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_SELF));
|
||||||
|
|
|
@ -17,11 +17,10 @@ namespace SharpChat.Commands {
|
||||||
string unsilUserStr = ctx.Args.FirstOrDefault();
|
string unsilUserStr = ctx.Args.FirstOrDefault();
|
||||||
ChatUser unsilUser;
|
ChatUser unsilUser;
|
||||||
|
|
||||||
lock(ctx.Chat.UsersAccess)
|
if(string.IsNullOrWhiteSpace(unsilUserStr) || (unsilUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unsilUserStr))) == null) {
|
||||||
if(string.IsNullOrWhiteSpace(unsilUserStr) || (unsilUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unsilUserStr))) == null) {
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, unsilUserStr ?? "User"));
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, unsilUserStr ?? "User"));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(unsilUser.Rank >= ctx.User.Rank) {
|
if(unsilUser.Rank >= ctx.User.Rank) {
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.UNSILENCE_HIERARCHY));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.UNSILENCE_HIERARCHY));
|
||||||
|
|
|
@ -16,15 +16,14 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatUser whisperUser;
|
|
||||||
string whisperUserStr = ctx.Args.FirstOrDefault();
|
string whisperUserStr = ctx.Args.FirstOrDefault();
|
||||||
lock(ctx.Chat.UsersAccess)
|
ChatUser whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
|
||||||
whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
|
|
||||||
|
|
||||||
if(whisperUser == null) {
|
if(whisperUser == null) {
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, whisperUserStr));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, whisperUserStr));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(whisperUser == ctx.User)
|
if(whisperUser == ctx.User)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -13,26 +13,23 @@ namespace SharpChat.Commands {
|
||||||
string whoChanStr = ctx.Args.FirstOrDefault();
|
string whoChanStr = ctx.Args.FirstOrDefault();
|
||||||
|
|
||||||
if(string.IsNullOrEmpty(whoChanStr)) {
|
if(string.IsNullOrEmpty(whoChanStr)) {
|
||||||
lock(ctx.Chat.UsersAccess)
|
foreach(ChatUser whoUser in ctx.Chat.Users) {
|
||||||
foreach(ChatUser whoUser in ctx.Chat.Users) {
|
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
|
||||||
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
|
|
||||||
|
|
||||||
if(whoUser == ctx.User)
|
if(whoUser == ctx.User)
|
||||||
whoChanSB.Append(@" style=""font-weight: bold;""");
|
whoChanSB.Append(@" style=""font-weight: bold;""");
|
||||||
|
|
||||||
whoChanSB.Append('>');
|
whoChanSB.Append('>');
|
||||||
whoChanSB.Append(whoUser.DisplayName);
|
whoChanSB.Append(whoUser.DisplayName);
|
||||||
whoChanSB.Append("</a>, ");
|
whoChanSB.Append("</a>, ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(whoChanSB.Length > 2)
|
if(whoChanSB.Length > 2)
|
||||||
whoChanSB.Length -= 2;
|
whoChanSB.Length -= 2;
|
||||||
|
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_SERVER, false, whoChanSB));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_SERVER, false, whoChanSB));
|
||||||
} else {
|
} else {
|
||||||
ChatChannel whoChan;
|
ChatChannel whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
|
||||||
lock(ctx.Chat.ChannelsAccess)
|
|
||||||
whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
|
|
||||||
|
|
||||||
if(whoChan == null) {
|
if(whoChan == null) {
|
||||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
|
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
|
||||||
|
|
|
@ -98,39 +98,36 @@ namespace SharpChat.PacketHandlers {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(ctx.Chat.UsersAccess) {
|
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(fai);
|
||||||
else {
|
else {
|
||||||
user.ApplyAuth(fai);
|
user.ApplyAuth(fai);
|
||||||
if(user.CurrentChannel != null)
|
if(user.CurrentChannel != null)
|
||||||
ctx.Chat.SendTo(user.CurrentChannel, new UserUpdatePacket(user));
|
ctx.Chat.SendTo(user.CurrentChannel, new UserUpdatePacket(user));
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce a maximum amount of connections per user
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Connection.BumpPing();
|
|
||||||
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));
|
|
||||||
string line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
|
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(line))
|
|
||||||
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce a maximum amount of connections per user
|
||||||
|
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
|
||||||
|
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
|
||||||
|
ctx.Connection.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Connection.BumpPing();
|
||||||
|
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));
|
||||||
|
string line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
|
||||||
|
|
||||||
|
if(!string.IsNullOrWhiteSpace(line))
|
||||||
|
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
|
||||||
}).Wait();
|
}).Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,17 +32,10 @@ namespace SharpChat.PacketHandlers {
|
||||||
|
|
||||||
lock(BumpAccess) {
|
lock(BumpAccess) {
|
||||||
if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
|
if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
|
||||||
(string, string)[] bumpList;
|
(string, string)[] bumpList = ctx.Chat.Users
|
||||||
lock(ctx.Chat.UsersAccess) {
|
.Where(u => u.Status == ChatUserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u))
|
||||||
IEnumerable<ChatUser> filtered = ctx.Chat.Users.Where(u => u.Status == ChatUserStatus.Online);
|
.Select(u => (u.UserId.ToString(), ctx.Chat.GetRemoteAddresses(u).FirstOrDefault()?.ToString() ?? string.Empty))
|
||||||
|
.ToArray();
|
||||||
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())
|
if(bumpList.Any())
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
|
|
|
@ -94,10 +94,8 @@ namespace SharpChat.PacketHandlers {
|
||||||
Text = messageText,
|
Text = messageText,
|
||||||
};
|
};
|
||||||
|
|
||||||
lock(ctx.Chat.EventsAccess) {
|
ctx.Chat.Events.AddEvent(message);
|
||||||
ctx.Chat.Events.AddEvent(message);
|
ctx.Chat.SendTo(channel, new ChatMessageAddPacket(message));
|
||||||
ctx.Chat.SendTo(channel, new ChatMessageAddPacket(message));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace SharpChat {
|
||||||
private readonly int RiskyOffset;
|
private readonly int RiskyOffset;
|
||||||
private readonly long[] TimePoints;
|
private readonly long[] TimePoints;
|
||||||
|
|
||||||
public RateLimiter(int size, int minDelay, int riskyOffset) {
|
public RateLimiter(int size, int minDelay, int riskyOffset = 0) {
|
||||||
if(size < 2)
|
if(size < 2)
|
||||||
throw new ArgumentException("Size is too small.", nameof(size));
|
throw new ArgumentException("Size is too small.", nameof(size));
|
||||||
if(minDelay < 1000)
|
if(minDelay < 1000)
|
||||||
|
|
|
@ -10,7 +10,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public class SockChatServer : IDisposable {
|
public class SockChatServer : IDisposable {
|
||||||
|
@ -19,15 +18,6 @@ namespace SharpChat {
|
||||||
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 bool IsDisposed { get; private set; }
|
|
||||||
|
|
||||||
public static ChatUser Bot { get; } = new ChatUser {
|
|
||||||
UserId = -1,
|
|
||||||
Username = "ChatBot",
|
|
||||||
Rank = 0,
|
|
||||||
Colour = new ChatColour(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public IWebSocketServer Server { get; }
|
public IWebSocketServer Server { get; }
|
||||||
public ChatContext Context { get; }
|
public ChatContext Context { get; }
|
||||||
|
|
||||||
|
@ -115,14 +105,13 @@ namespace SharpChat {
|
||||||
SendMessageHandler.AddCommand(new ShutdownRestartCommand(waitHandle, () => !IsShuttingDown && (IsShuttingDown = true)));
|
SendMessageHandler.AddCommand(new ShutdownRestartCommand(waitHandle, () => !IsShuttingDown && (IsShuttingDown = true)));
|
||||||
|
|
||||||
Server.Start(sock => {
|
Server.Start(sock => {
|
||||||
if(IsShuttingDown || IsDisposed) {
|
if(IsShuttingDown) {
|
||||||
sock.Close(1013);
|
sock.Close(1013);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatConnection conn;
|
ChatConnection conn = new(sock);
|
||||||
lock(Context.ConnectionsAccess)
|
Context.Connections.Add(conn);
|
||||||
Context.Connections.Add(conn = new(sock));
|
|
||||||
|
|
||||||
sock.OnOpen = () => OnOpen(conn);
|
sock.OnOpen = () => OnOpen(conn);
|
||||||
sock.OnClose = () => OnClose(conn);
|
sock.OnClose = () => OnClose(conn);
|
||||||
|
@ -135,51 +124,78 @@ namespace SharpChat {
|
||||||
|
|
||||||
private void OnOpen(ChatConnection conn) {
|
private void OnOpen(ChatConnection conn) {
|
||||||
Logger.Write($"Connection opened from {conn.RemoteAddress}:{conn.RemotePort}");
|
Logger.Write($"Connection opened from {conn.RemoteAddress}:{conn.RemotePort}");
|
||||||
|
Context.SafeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
Context.Update();
|
private void OnError(ChatConnection conn, Exception ex) {
|
||||||
|
Logger.Write($"[{conn.Id} {conn.RemoteAddress}] {ex}");
|
||||||
|
Context.SafeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClose(ChatConnection conn) {
|
private void OnClose(ChatConnection conn) {
|
||||||
Logger.Write($"Connection closed from {conn.RemoteAddress}:{conn.RemotePort}");
|
Logger.Write($"Connection closed from {conn.RemoteAddress}:{conn.RemotePort}");
|
||||||
|
|
||||||
lock(Context.ConnectionsAccess) {
|
Context.ContextAccess.Wait();
|
||||||
|
try {
|
||||||
Context.Connections.Remove(conn);
|
Context.Connections.Remove(conn);
|
||||||
|
|
||||||
if(conn.User != null && !Context.Connections.Any(c => c.User == conn.User))
|
if(conn.User != null && !Context.Connections.Any(c => c.User == conn.User))
|
||||||
Context.HandleDisconnect(conn.User);
|
Context.HandleDisconnect(conn.User);
|
||||||
|
|
||||||
|
Context.Update();
|
||||||
|
} finally {
|
||||||
|
Context.ContextAccess.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnError(ChatConnection conn, Exception ex) {
|
|
||||||
Logger.Write($"[{conn.Id} {conn.RemoteAddress}] {ex}");
|
|
||||||
Context.Update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMessage(ChatConnection conn, string msg) {
|
private void OnMessage(ChatConnection conn, string msg) {
|
||||||
Context.Update();
|
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.HasFloodProtection) {
|
||||||
conn.User.RateLimiter.Update();
|
ChatUser banUser = null;
|
||||||
|
string banAddr = string.Empty;
|
||||||
|
TimeSpan banDuration = TimeSpan.MinValue;
|
||||||
|
|
||||||
if(conn.User.RateLimiter.IsExceeded) {
|
Context.ContextAccess.Wait();
|
||||||
Task.Run(async () => {
|
try {
|
||||||
TimeSpan duration = TimeSpan.FromSeconds(FloodKickLength);
|
if(!Context.UserRateLimiters.TryGetValue(conn.User.UserId, out RateLimiter rateLimiter))
|
||||||
|
Context.UserRateLimiters.Add(conn.User.UserId, rateLimiter = new RateLimiter(
|
||||||
|
ChatUser.DEFAULT_SIZE,
|
||||||
|
ChatUser.DEFAULT_MINIMUM_DELAY,
|
||||||
|
ChatUser.DEFAULT_RISKY_OFFSET
|
||||||
|
));
|
||||||
|
|
||||||
await Misuzu.CreateBanAsync(
|
rateLimiter.Update();
|
||||||
conn.User.UserId.ToString(), conn.RemoteAddress.ToString(),
|
|
||||||
string.Empty, "::1",
|
|
||||||
duration,
|
|
||||||
"Kicked from chat for flood protection."
|
|
||||||
);
|
|
||||||
|
|
||||||
Context.BanUser(conn.User, duration, UserDisconnectReason.Flood);
|
if(rateLimiter.IsExceeded) {
|
||||||
}).Wait();
|
banDuration = TimeSpan.FromSeconds(FloodKickLength);
|
||||||
return;
|
banUser = conn.User;
|
||||||
} else if(conn.User.RateLimiter.IsRisky)
|
banAddr = conn.RemoteAddress.ToString();
|
||||||
Context.SendTo(conn.User, new FloodWarningPacket());
|
} else if(rateLimiter.IsRisky) {
|
||||||
|
banUser = conn.User;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(banUser is not null) {
|
||||||
|
if(banDuration == TimeSpan.MinValue) {
|
||||||
|
Context.SendTo(conn.User, new FloodWarningPacket());
|
||||||
|
} else {
|
||||||
|
Context.BanUser(conn.User, banDuration, UserDisconnectReason.Flood);
|
||||||
|
|
||||||
|
if(banDuration > TimeSpan.Zero)
|
||||||
|
Misuzu.CreateBanAsync(
|
||||||
|
conn.User.UserId.ToString(), conn.RemoteAddress.ToString(),
|
||||||
|
string.Empty, "::1",
|
||||||
|
banDuration,
|
||||||
|
"Kicked from chat for flood protection."
|
||||||
|
).Wait();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Context.ContextAccess.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatPacketHandlerContext context = new(msg, Context, conn);
|
ChatPacketHandlerContext context = new(msg, Context, conn);
|
||||||
|
@ -187,9 +203,18 @@ namespace SharpChat {
|
||||||
? GuestHandlers.FirstOrDefault(h => h.IsMatch(context))
|
? GuestHandlers.FirstOrDefault(h => h.IsMatch(context))
|
||||||
: AuthedHandlers.FirstOrDefault(h => h.IsMatch(context));
|
: AuthedHandlers.FirstOrDefault(h => h.IsMatch(context));
|
||||||
|
|
||||||
handler?.Handle(context);
|
if(handler is not null) {
|
||||||
|
Context.ContextAccess.Wait();
|
||||||
|
try {
|
||||||
|
handler.Handle(context);
|
||||||
|
} finally {
|
||||||
|
Context.ContextAccess.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsDisposed;
|
||||||
|
|
||||||
~SockChatServer() {
|
~SockChatServer() {
|
||||||
DoDispose();
|
DoDispose();
|
||||||
}
|
}
|
||||||
|
@ -203,10 +228,10 @@ namespace SharpChat {
|
||||||
if(IsDisposed)
|
if(IsDisposed)
|
||||||
return;
|
return;
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
IsShuttingDown = true;
|
||||||
|
|
||||||
lock(Context.ConnectionsAccess)
|
foreach(ChatConnection conn in Context.Connections)
|
||||||
foreach(ChatConnection conn in Context.Connections)
|
conn.Dispose();
|
||||||
conn.Dispose();
|
|
||||||
|
|
||||||
Server?.Dispose();
|
Server?.Dispose();
|
||||||
HttpClient?.Dispose();
|
HttpClient?.Dispose();
|
||||||
|
|
Loading…
Reference in a new issue