Simplify rate limiter, disabled silencing and merged BasicUser and ChatUser.
This commit is contained in:
parent
d268a419dc
commit
546e8a2c83
15 changed files with 98 additions and 115 deletions
|
@ -80,15 +80,6 @@ namespace SharpChat {
|
|||
return Users.Where(u => ids.Contains(u.UserId)).ToArray();
|
||||
}
|
||||
|
||||
public void DebugPrintChannelUsers() {
|
||||
lock(ChannelUsersAccess) {
|
||||
Logger.Write("DebugPrintChannelUsers()");
|
||||
foreach(ChannelUserAssoc cua in ChannelUsers)
|
||||
Logger.Write(cua);
|
||||
Logger.Write(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
||||
if(duration > TimeSpan.Zero)
|
||||
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
|
||||
|
@ -143,7 +134,6 @@ namespace SharpChat {
|
|||
|
||||
foreach(ChatChannel chan in channels) {
|
||||
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
|
||||
DebugPrintChannelUsers();
|
||||
|
||||
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
|
||||
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason));
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpChat {
|
||||
public enum ChatRateLimitState {
|
||||
None,
|
||||
Warning,
|
||||
Kick,
|
||||
}
|
||||
|
||||
public class ChatRateLimiter {
|
||||
private const int FLOOD_PROTECTION_AMOUNT = 30;
|
||||
private const int FLOOD_PROTECTION_THRESHOLD = 10;
|
||||
|
||||
private readonly Queue<DateTimeOffset> TimePoints = new();
|
||||
|
||||
public ChatRateLimitState State {
|
||||
get {
|
||||
if(TimePoints.Count == FLOOD_PROTECTION_AMOUNT) {
|
||||
if((TimePoints.Last() - TimePoints.First()).TotalSeconds <= FLOOD_PROTECTION_THRESHOLD)
|
||||
return ChatRateLimitState.Kick;
|
||||
|
||||
if((TimePoints.Last() - TimePoints.Skip(5).First()).TotalSeconds <= FLOOD_PROTECTION_THRESHOLD)
|
||||
return ChatRateLimitState.Warning;
|
||||
}
|
||||
|
||||
return ChatRateLimitState.None;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTimePoint(DateTimeOffset? dto = null) {
|
||||
if(!dto.HasValue)
|
||||
dto = DateTimeOffset.Now;
|
||||
|
||||
if(TimePoints.Count >= FLOOD_PROTECTION_AMOUNT)
|
||||
TimePoints.Dequeue();
|
||||
|
||||
TimePoints.Enqueue(dto.Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System;
|
|||
using System.Text;
|
||||
|
||||
namespace SharpChat {
|
||||
public class BasicUser : IEquatable<BasicUser> {
|
||||
public class ChatUser : IEquatable<ChatUser> {
|
||||
private const int RANK_NO_FLOOD = 9;
|
||||
|
||||
public long UserId { get; set; }
|
||||
|
@ -18,6 +18,11 @@ namespace SharpChat {
|
|||
public bool HasFloodProtection
|
||||
=> Rank < RANK_NO_FLOOD;
|
||||
|
||||
public readonly RateLimiter RateLimiter = new();
|
||||
|
||||
// This needs to be a session thing
|
||||
public ChatChannel CurrentChannel { get; set; }
|
||||
|
||||
public string DisplayName {
|
||||
get {
|
||||
StringBuilder sb = new();
|
||||
|
@ -36,6 +41,24 @@ namespace SharpChat {
|
|||
}
|
||||
}
|
||||
|
||||
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 bool Can(ChatUserPermissions perm, bool strict = false) {
|
||||
ChatUserPermissions perms = Permissions & perm;
|
||||
return strict ? perms == perm : perms > 0;
|
||||
|
@ -63,56 +86,23 @@ namespace SharpChat {
|
|||
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);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return UserId.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
return Equals(obj as BasicUser);
|
||||
return Equals(obj as ChatUser);
|
||||
}
|
||||
|
||||
public bool Equals(BasicUser other) {
|
||||
public bool Equals(ChatUser other) {
|
||||
return UserId == other?.UserId;
|
||||
}
|
||||
}
|
||||
|
||||
public class ChatUser : BasicUser {
|
||||
public DateTimeOffset SilencedUntil { get; set; }
|
||||
|
||||
public readonly ChatRateLimiter RateLimiter = new();
|
||||
|
||||
// This needs to be a session thing
|
||||
public ChatChannel CurrentChannel { get; set; }
|
||||
|
||||
public bool IsSilenced
|
||||
=> DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero;
|
||||
|
||||
public ChatUser() {}
|
||||
|
||||
public ChatUser(MisuzuAuthInfo auth) {
|
||||
UserId = auth.UserId;
|
||||
ApplyAuth(auth, true);
|
||||
}
|
||||
|
||||
public void ApplyAuth(MisuzuAuthInfo auth, bool invalidateRestrictions = false) {
|
||||
Username = auth.Username;
|
||||
|
||||
if(Status == ChatUserStatus.Offline)
|
||||
Status = ChatUserStatus.Online;
|
||||
|
||||
Colour = ChatColour.FromMisuzu(auth.ColourRaw);
|
||||
Rank = auth.Rank;
|
||||
Permissions = auth.Permissions;
|
||||
|
||||
if(invalidateRestrictions || !IsSilenced)
|
||||
SilencedUntil = auth.SilencedUntil;
|
||||
}
|
||||
|
||||
public bool NameEquals(string name) {
|
||||
return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| string.Equals(name, DisplayName, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public static string GetDMChannelName(ChatUser user1, ChatUser user2) {
|
||||
return user1.UserId < user2.UserId
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace SharpChat.Commands {
|
|||
return;
|
||||
}
|
||||
|
||||
if(silUser.IsSilenced) {
|
||||
if(RNG.Next() > 1 /*silUser.IsSilenced*/) {
|
||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_ALREADY));
|
||||
return;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace SharpChat.Commands {
|
|||
silenceUntil = DateTimeOffset.UtcNow.AddSeconds(silenceSeconds);
|
||||
}
|
||||
|
||||
silUser.SilencedUntil = silenceUntil;
|
||||
//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));
|
||||
}
|
||||
|
|
|
@ -28,12 +28,12 @@ namespace SharpChat.Commands {
|
|||
return;
|
||||
}
|
||||
|
||||
if(!unsilUser.IsSilenced) {
|
||||
if(RNG.Next() > 1 /*!unsilUser.IsSilenced*/) {
|
||||
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.NOT_SILENCED));
|
||||
return;
|
||||
}
|
||||
|
||||
unsilUser.SilencedUntil = DateTimeOffset.MinValue;
|
||||
//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));
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace SharpChat.EventStorage {
|
|||
evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created"));
|
||||
|
||||
if(!reader.IsDBNull(reader.GetOrdinal("event_sender"))) {
|
||||
evt.Sender = new BasicUser {
|
||||
evt.Sender = new ChatUser {
|
||||
UserId = reader.GetInt64("event_sender"),
|
||||
Username = reader.GetString("event_sender_name"),
|
||||
Colour = ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")),
|
||||
|
|
|
@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
|
|||
namespace SharpChat.Events {
|
||||
public class ChatMessage : IChatMessage {
|
||||
[JsonIgnore]
|
||||
public BasicUser Sender { get; set; }
|
||||
public ChatUser Sender { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string ChannelName { get; set; }
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace SharpChat.Events {
|
|||
|
||||
public interface IChatEvent {
|
||||
DateTimeOffset DateTime { get; set; }
|
||||
BasicUser Sender { get; set; }
|
||||
ChatUser Sender { get; set; }
|
||||
string ChannelName { get; set; }
|
||||
ChatMessageFlags Flags { get; set; }
|
||||
long SequenceId { get; set; }
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace SharpChat.Events {
|
|||
public DateTimeOffset DateTime { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BasicUser Sender { get; set; }
|
||||
public ChatUser Sender { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string ChannelName { get; set; }
|
||||
|
@ -19,7 +19,7 @@ namespace SharpChat.Events {
|
|||
public long SequenceId { get; set; }
|
||||
|
||||
public UserChannelJoinEvent() { }
|
||||
public UserChannelJoinEvent(DateTimeOffset joined, BasicUser user, ChatChannel channel) {
|
||||
public UserChannelJoinEvent(DateTimeOffset joined, ChatUser user, ChatChannel channel) {
|
||||
DateTime = joined;
|
||||
Sender = user;
|
||||
ChannelName = channel.Name;
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace SharpChat.Events {
|
|||
public DateTimeOffset DateTime { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BasicUser Sender { get; set; }
|
||||
public ChatUser Sender { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string ChannelName { get; set; }
|
||||
|
@ -19,7 +19,7 @@ namespace SharpChat.Events {
|
|||
public long SequenceId { get; set; }
|
||||
|
||||
public UserChannelLeaveEvent() { }
|
||||
public UserChannelLeaveEvent(DateTimeOffset parted, BasicUser user, ChatChannel channel) {
|
||||
public UserChannelLeaveEvent(DateTimeOffset parted, ChatUser user, ChatChannel channel) {
|
||||
DateTime = parted;
|
||||
Sender = user;
|
||||
ChannelName = channel.Name;
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace SharpChat.Events {
|
|||
public DateTimeOffset DateTime { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BasicUser Sender { get; set; }
|
||||
public ChatUser Sender { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string ChannelName { get; set; }
|
||||
|
@ -19,7 +19,7 @@ namespace SharpChat.Events {
|
|||
public long SequenceId { get; set; }
|
||||
|
||||
public UserConnectEvent() { }
|
||||
public UserConnectEvent(DateTimeOffset joined, BasicUser user, ChatChannel channel) {
|
||||
public UserConnectEvent(DateTimeOffset joined, ChatUser user, ChatChannel channel) {
|
||||
DateTime = joined;
|
||||
Sender = user;
|
||||
ChannelName = channel.Name;
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace SharpChat.Events {
|
|||
public DateTimeOffset DateTime { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BasicUser Sender { get; set; }
|
||||
public ChatUser Sender { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string ChannelName { get; set; }
|
||||
|
@ -24,7 +24,7 @@ namespace SharpChat.Events {
|
|||
public UserDisconnectReason Reason { get; set; }
|
||||
|
||||
public UserDisconnectEvent() { }
|
||||
public UserDisconnectEvent(DateTimeOffset parted, BasicUser user, ChatChannel channel, UserDisconnectReason reason) {
|
||||
public UserDisconnectEvent(DateTimeOffset parted, ChatUser user, ChatChannel channel, UserDisconnectReason reason) {
|
||||
DateTime = parted;
|
||||
Sender = user;
|
||||
ChannelName = channel.Name;
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace SharpChat.PacketHandlers {
|
|||
|
||||
if(channel == null
|
||||
|| !ctx.Chat.IsInChannel(user, channel)
|
||||
|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser)))
|
||||
/*|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser))*/)
|
||||
return;
|
||||
|
||||
if(user.Status != ChatUserStatus.Online) {
|
||||
|
|
45
SharpChat/RateLimiter.cs
Normal file
45
SharpChat/RateLimiter.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
|
||||
namespace SharpChat {
|
||||
public class RateLimiter {
|
||||
public const int DEFAULT_SIZE = 30;
|
||||
public const int DEFAULT_MINIMUM_DELAY = 10000;
|
||||
public const int DEFAULT_RISKY_OFFSET = 5;
|
||||
|
||||
private readonly int Size;
|
||||
private readonly int MinimumDelay;
|
||||
private readonly int RiskyOffset;
|
||||
private readonly long[] TimePoints;
|
||||
|
||||
public RateLimiter(
|
||||
int size = DEFAULT_SIZE,
|
||||
int minDelay = DEFAULT_MINIMUM_DELAY,
|
||||
int riskyOffset = DEFAULT_RISKY_OFFSET
|
||||
) {
|
||||
if(size < 2)
|
||||
throw new ArgumentException("Size is too small.", nameof(size));
|
||||
if(minDelay < 1000)
|
||||
throw new ArgumentException("Minimum delay is inhuman.", nameof(minDelay));
|
||||
if(riskyOffset != 0) {
|
||||
if(riskyOffset >= size)
|
||||
throw new ArgumentException("Risky offset may not be greater or equal to the size.", nameof(riskyOffset));
|
||||
else if(riskyOffset < 0)
|
||||
throw new ArgumentException("Risky offset may not be negative.", nameof(riskyOffset));
|
||||
}
|
||||
|
||||
Size = size;
|
||||
MinimumDelay = minDelay;
|
||||
RiskyOffset = riskyOffset;
|
||||
TimePoints = new long[Size];
|
||||
}
|
||||
|
||||
public bool IsRisky => TimePoints[RiskyOffset] != 0 && TimePoints[RiskyOffset + 1] != 0 && TimePoints[RiskyOffset] + MinimumDelay >= TimePoints[Size - 1];
|
||||
public bool IsExceeded => TimePoints[0] != 0 && TimePoints[1] != 0 && TimePoints[0] + MinimumDelay >= TimePoints[Size - 1];
|
||||
|
||||
public void Update() {
|
||||
for(int i = 1; i < Size; ++i)
|
||||
TimePoints[i - 1] = TimePoints[i];
|
||||
TimePoints[Size - 1] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -101,8 +101,8 @@ namespace SharpChat {
|
|||
new PardonUserCommand(msz),
|
||||
new PardonAddressCommand(msz),
|
||||
new BanListCommand(msz),
|
||||
new SilenceApplyCommand(),
|
||||
new SilenceRevokeCommand(),
|
||||
//new SilenceApplyCommand(),
|
||||
//new SilenceRevokeCommand(),
|
||||
new RemoteAddressCommand(),
|
||||
});
|
||||
|
||||
|
@ -182,9 +182,9 @@ namespace SharpChat {
|
|||
|
||||
// this doesn't affect non-authed connections?????
|
||||
if(conn.User is not null && conn.User.HasFloodProtection) {
|
||||
conn.User.RateLimiter.AddTimePoint();
|
||||
conn.User.RateLimiter.Update();
|
||||
|
||||
if(conn.User.RateLimiter.State == ChatRateLimitState.Kick) {
|
||||
if(conn.User.RateLimiter.IsExceeded) {
|
||||
Task.Run(async () => {
|
||||
TimeSpan duration = TimeSpan.FromSeconds(FloodKickLength);
|
||||
|
||||
|
@ -198,7 +198,7 @@ namespace SharpChat {
|
|||
Context.BanUser(conn.User, duration, UserDisconnectReason.Flood);
|
||||
}).Wait();
|
||||
return;
|
||||
} else if(conn.User.RateLimiter.State == ChatRateLimitState.Warning)
|
||||
} else if(conn.User.RateLimiter.IsRisky)
|
||||
Context.SendTo(conn.User, new FloodWarningPacket());
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue