Updated to .NET 9.0

This commit is contained in:
flash 2025-04-25 15:49:46 +00:00
parent b026bad176
commit 1c23ffbbe8
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
61 changed files with 344 additions and 753 deletions

View file

@ -3,38 +3,22 @@ using System.Linq;
using System.Text; using System.Text;
namespace SharpChat { namespace SharpChat {
public class ChatChannel { public class ChatChannel(
public string Name { get; } string name,
public string Password { get; set; } string password = "",
public bool IsTemporary { get; set; } bool isTemporary = false,
public int Rank { get; set; } int rank = 0,
public long OwnerId { get; set; } long ownerId = 0
) {
public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
public string Password { get; set; } = password ?? string.Empty;
public bool IsTemporary { get; set; } = isTemporary;
public int Rank { get; set; } = rank;
public long OwnerId { get; set; } = ownerId;
public bool HasPassword public bool HasPassword
=> !string.IsNullOrWhiteSpace(Password); => !string.IsNullOrWhiteSpace(Password);
public ChatChannel(
ChatUser owner,
string name,
string password = null,
bool isTemporary = false,
int rank = 0
) : this(name, password, isTemporary, rank, owner?.UserId ?? 0) {}
public ChatChannel(
string name,
string password = null,
bool isTemporary = false,
int rank = 0,
long ownerId = 0
) {
Name = name;
Password = password ?? string.Empty;
IsTemporary = isTemporary;
Rank = rank;
OwnerId = ownerId;
}
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace SharpChat { namespace SharpChat {
public struct ChatColour { public readonly struct ChatColour {
public byte Red { get; } public byte Red { get; }
public byte Green { get; } public byte Green { get; }
public byte Blue { get; } public byte Blue { get; }

View file

@ -17,8 +17,7 @@ namespace SharpChat {
ChatConnection connection, ChatConnection connection,
ChatChannel channel ChatChannel channel
) { ) {
if(text == null) ArgumentNullException.ThrowIfNull(text);
throw new ArgumentNullException(nameof(text));
Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Chat = chat ?? throw new ArgumentNullException(nameof(chat));
User = user ?? throw new ArgumentNullException(nameof(user)); User = user ?? throw new ArgumentNullException(nameof(user));
@ -27,7 +26,7 @@ namespace SharpChat {
string[] parts = text[1..].Split(' '); string[] parts = text[1..].Split(' ');
Name = parts.First().Replace(".", string.Empty); Name = parts.First().Replace(".", string.Empty);
Args = parts.Skip(1).ToArray(); Args = [.. parts.Skip(1)];
} }
public ChatCommandContext( public ChatCommandContext(

View file

@ -37,8 +37,8 @@ namespace SharpChat {
throw new Exception("Unable to parse remote address?????"); throw new Exception("Unable to parse remote address?????");
if(IPAddress.IsLoopback(addr) if(IPAddress.IsLoopback(addr)
&& sock.ConnectionInfo.Headers.ContainsKey("X-Real-IP") && sock.ConnectionInfo.Headers.TryGetValue("X-Real-IP", out string addrStr)
&& IPAddress.TryParse(sock.ConnectionInfo.Headers["X-Real-IP"], out IPAddress realAddr)) && IPAddress.TryParse(addrStr, out IPAddress realAddr))
addr = realAddr; addr = realAddr;
RemoteAddress = addr; RemoteAddress = addr;

View file

@ -8,22 +8,18 @@ using System.Net;
using System.Threading; using System.Threading;
namespace SharpChat { namespace SharpChat {
public class ChatContext { public class ChatContext(IEventStorage evtStore) {
public record ChannelUserAssoc(long UserId, string ChannelName); public record ChannelUserAssoc(long UserId, string ChannelName);
public readonly SemaphoreSlim ContextAccess = new(1, 1); public readonly SemaphoreSlim ContextAccess = new(1, 1);
public HashSet<ChatChannel> Channels { get; } = new(); public HashSet<ChatChannel> Channels { get; } = [];
public HashSet<ChatConnection> Connections { get; } = new(); public HashSet<ChatConnection> Connections { get; } = [];
public HashSet<ChatUser> Users { get; } = new(); public HashSet<ChatUser> Users { get; } = [];
public IEventStorage Events { get; } public IEventStorage Events { get; } = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new(); public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
public Dictionary<long, RateLimiter> UserRateLimiters { get; } = new(); public Dictionary<long, RateLimiter> UserRateLimiters { get; } = [];
public Dictionary<long, ChatChannel> UserLastChannel { get; } = new(); public Dictionary<long, ChatChannel> UserLastChannel { get; } = [];
public ChatContext(IEventStorage evtStore) {
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
}
public void DispatchEvent(IChatEvent eventInfo) { public void DispatchEvent(IChatEvent eventInfo) {
if(eventInfo is MessageCreateEvent mce) { if(eventInfo is MessageCreateEvent mce) {
@ -34,7 +30,7 @@ namespace SharpChat {
// e.g. nook sees @Arysil and Arysil sees @nook // e.g. nook sees @Arysil and Arysil sees @nook
// this entire routine is garbage, channels should probably in the db // this entire routine is garbage, channels should probably in the db
if(!mce.ChannelName.StartsWith("@")) if(!mce.ChannelName.StartsWith('@'))
return; return;
IEnumerable<long> uids = mce.ChannelName[1..].Split('-', 3).Select(u => long.TryParse(u, out long up) ? up : -1); IEnumerable<long> uids = mce.ChannelName[1..].Split('-', 3).Select(u => long.TryParse(u, out long up) ? up : -1);
@ -110,21 +106,21 @@ namespace SharpChat {
} }
public string[] GetUserChannelNames(ChatUser user) { public string[] GetUserChannelNames(ChatUser user) {
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)];
} }
public ChatChannel[] GetUserChannels(ChatUser user) { public ChatChannel[] GetUserChannels(ChatUser user) {
string[] names = GetUserChannelNames(user); string[] names = GetUserChannelNames(user);
return Channels.Where(c => names.Any(n => c.NameEquals(n))).ToArray(); return [.. Channels.Where(c => names.Any(n => c.NameEquals(n)))];
} }
public long[] GetChannelUserIds(ChatChannel channel) { public long[] GetChannelUserIds(ChatChannel channel) {
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)];
} }
public ChatUser[] GetChannelUsers(ChatChannel channel) { public ChatUser[] GetChannelUsers(ChatChannel channel) {
long[] ids = GetChannelUserIds(channel); long[] ids = GetChannelUserIds(channel);
return Users.Where(u => ids.Contains(u.UserId)).ToArray(); return [.. Users.Where(u => ids.Contains(u.UserId))];
} }
public void UpdateUser( public void UpdateUser(
@ -139,11 +135,10 @@ namespace SharpChat {
bool? isSuper = null, bool? isSuper = null,
bool silent = false bool silent = false
) { ) {
if(user == null) ArgumentNullException.ThrowIfNull(user);
throw new ArgumentNullException(nameof(user));
bool hasChanged = false; bool hasChanged = false;
string previousName = null; string previousName = string.Empty;
if(userName != null && !user.UserName.Equals(userName)) { if(userName != null && !user.UserName.Equals(userName)) {
user.UserName = userName; user.UserName = userName;
@ -210,11 +205,11 @@ namespace SharpChat {
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) { public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
if(!IsInChannel(user, chan)) { if(!IsInChannel(user, chan)) {
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user)); SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user));
Events.AddEvent("user:connect", user, chan, flags: StoredEventFlags.Log); Events.AddEvent(SharpId.Next(), "user:connect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
} }
conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength)); conn.Send(new AuthSuccessPacket(user, chan, maxMsgLength));
conn.Send(new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank))); conn.Send(new ContextUsersPacket(GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)));
foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name)) foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
conn.Send(new ContextMessagePacket(msg)); conn.Send(new ContextMessagePacket(msg));
@ -238,7 +233,7 @@ namespace SharpChat {
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name)); ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason)); SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
Events.AddEvent("user:disconnect", user, chan, new { reason = (int)reason }, StoredEventFlags.Log); Events.AddEvent(SharpId.Next(), "user:disconnect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, new { reason = (int)reason }, StoredEventFlags.Log);
if(chan.IsTemporary && chan.IsOwner(user)) if(chan.IsTemporary && chan.IsOwner(user))
RemoveChannel(chan); RemoveChannel(chan);
@ -275,12 +270,12 @@ namespace SharpChat {
ChatChannel oldChan = UserLastChannel[user.UserId]; ChatChannel oldChan = UserLastChannel[user.UserId];
SendTo(oldChan, new UserChannelLeavePacket(user)); SendTo(oldChan, new UserChannelLeavePacket(user));
Events.AddEvent("chan:leave", user, oldChan, flags: StoredEventFlags.Log); Events.AddEvent(SharpId.Next(), "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
SendTo(chan, new UserChannelJoinPacket(user)); SendTo(chan, new UserChannelJoinPacket(user));
Events.AddEvent("chan:join", user, oldChan, flags: StoredEventFlags.Log); Events.AddEvent(SharpId.Next(), "chan:join", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers)); SendTo(user, new ContextClearPacket(ContextClearMode.MessagesUsers));
SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank))); SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)));
foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name)) foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
SendTo(user, new ContextMessagePacket(msg)); SendTo(user, new ContextMessagePacket(msg));
@ -296,8 +291,7 @@ namespace SharpChat {
} }
public void Send(IServerPacket packet) { public void Send(IServerPacket packet) {
if(packet == null) ArgumentNullException.ThrowIfNull(packet);
throw new ArgumentNullException(nameof(packet));
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(conn.IsAuthed) if(conn.IsAuthed)
@ -305,10 +299,8 @@ namespace SharpChat {
} }
public void SendTo(ChatUser user, IServerPacket packet) { public void SendTo(ChatUser user, IServerPacket packet) {
if(user == null) ArgumentNullException.ThrowIfNull(user);
throw new ArgumentNullException(nameof(user)); ArgumentNullException.ThrowIfNull(packet);
if(packet == null)
throw new ArgumentNullException(nameof(packet));
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(conn.IsAlive && conn.User == user) if(conn.IsAlive && conn.User == user)
@ -316,10 +308,8 @@ namespace SharpChat {
} }
public void SendTo(ChatChannel channel, IServerPacket packet) { public void SendTo(ChatChannel channel, IServerPacket packet) {
if(channel == null) ArgumentNullException.ThrowIfNull(channel);
throw new ArgumentNullException(nameof(channel)); ArgumentNullException.ThrowIfNull(packet);
if(packet == null)
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
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel)); IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel));
@ -328,10 +318,8 @@ namespace SharpChat {
} }
public void SendToUserChannels(ChatUser user, IServerPacket packet) { public void SendToUserChannels(ChatUser user, IServerPacket packet) {
if(user == null) ArgumentNullException.ThrowIfNull(user);
throw new ArgumentNullException(nameof(user)); ArgumentNullException.ThrowIfNull(packet);
if(packet == null)
throw new ArgumentNullException(nameof(packet));
IEnumerable<ChatChannel> chans = Channels.Where(c => IsInChannel(user, c)); IEnumerable<ChatChannel> chans = Channels.Where(c => IsInChannel(user, c));
IEnumerable<ChatConnection> conns = Connections.Where(conn => conn.IsAuthed && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName)))); IEnumerable<ChatConnection> conns = Connections.Where(conn => conn.IsAuthed && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
@ -340,12 +328,11 @@ namespace SharpChat {
} }
public IPAddress[] GetRemoteAddresses(ChatUser user) { public IPAddress[] GetRemoteAddresses(ChatUser user) {
return Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct().ToArray(); return [.. Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct()];
} }
public void ForceChannel(ChatUser user, ChatChannel chan = null) { public void ForceChannel(ChatUser user, ChatChannel chan = null) {
if(user == null) ArgumentNullException.ThrowIfNull(user);
throw new ArgumentNullException(nameof(user));
if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan)) if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
throw new ArgumentException("no channel???"); throw new ArgumentException("no channel???");
@ -354,8 +341,7 @@ namespace SharpChat {
} }
public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string password = null) { public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string password = null) {
if(channel == null) ArgumentNullException.ThrowIfNull(channel);
throw new ArgumentNullException(nameof(channel));
if(!Channels.Contains(channel)) if(!Channels.Contains(channel))
throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel)); throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
@ -375,7 +361,7 @@ namespace SharpChat {
} }
public void RemoveChannel(ChatChannel channel) { public void RemoveChannel(ChatChannel channel) {
if(channel == null || !Channels.Any()) if(channel == null || Channels.Count < 1)
return; return;
ChatChannel defaultChannel = Channels.FirstOrDefault(); ChatChannel defaultChannel = Channels.FirstOrDefault();

View file

@ -1,20 +1,14 @@
using System; using System;
namespace SharpChat { namespace SharpChat {
public class ChatPacketHandlerContext { public class ChatPacketHandlerContext(
public string Text { get; } string text,
public ChatContext Chat { get; } ChatContext chat,
public ChatConnection Connection { get; } ChatConnection connection
) {
public ChatPacketHandlerContext( public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text));
string text, public ChatContext Chat { get; } = chat ?? throw new ArgumentNullException(nameof(chat));
ChatContext chat, public ChatConnection Connection { get; } = connection ?? throw new ArgumentNullException(nameof(connection));
ChatConnection connection
) {
Text = text ?? throw new ArgumentNullException(nameof(text));
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
}
public bool CheckPacketId(string packetId) { public bool CheckPacketId(string packetId) {
return Text == packetId || Text.StartsWith(packetId + '\t'); return Text == packetId || Text.StartsWith(packetId + '\t');

View file

@ -4,20 +4,29 @@ using System.Text;
using SharpChat.Commands; using SharpChat.Commands;
namespace SharpChat { namespace SharpChat {
public class ChatUser : IEquatable<ChatUser> { public class ChatUser(
long userId,
string userName,
ChatColour colour,
int rank,
ChatUserPermissions perms,
string nickName = "",
ChatUserStatus status = ChatUserStatus.Online,
string statusText = ""
) {
public const int DEFAULT_SIZE = 30; public const int DEFAULT_SIZE = 30;
public const int DEFAULT_MINIMUM_DELAY = 10000; public const int DEFAULT_MINIMUM_DELAY = 10000;
public const int DEFAULT_RISKY_OFFSET = 5; public const int DEFAULT_RISKY_OFFSET = 5;
public long UserId { get; } public long UserId { get; } = userId;
public string UserName { get; set; } public string UserName { get; set; } = userName ?? throw new ArgumentNullException(nameof(userName));
public ChatColour Colour { get; set; } public ChatColour Colour { get; set; } = colour;
public int Rank { get; set; } public int Rank { get; set; } = rank;
public ChatUserPermissions Permissions { get; set; } public ChatUserPermissions Permissions { get; set; } = perms;
public bool IsSuper { get; set; } public bool IsSuper { get; set; }
public string NickName { get; set; } public string NickName { get; set; } = nickName ?? string.Empty;
public ChatUserStatus Status { get; set; } public ChatUserStatus Status { get; set; } = status;
public string StatusText { get; set; } public string StatusText { get; set; } = statusText ?? string.Empty;
public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}"; public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}";
@ -27,7 +36,7 @@ namespace SharpChat {
if(Status == ChatUserStatus.Away) { if(Status == ChatUserStatus.Away) {
string statusText = StatusText.Trim(); string statusText = StatusText.Trim();
StringInfo sti = new StringInfo(statusText); StringInfo sti = new(statusText);
if(Encoding.UTF8.GetByteCount(statusText) > AFKCommand.MAX_BYTES if(Encoding.UTF8.GetByteCount(statusText) > AFKCommand.MAX_BYTES
|| sti.LengthInTextElements > AFKCommand.MAX_GRAPHEMES) || sti.LengthInTextElements > AFKCommand.MAX_GRAPHEMES)
statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, AFKCommand.MAX_GRAPHEMES)).Trim(); statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, AFKCommand.MAX_GRAPHEMES)).Trim();
@ -41,27 +50,6 @@ namespace SharpChat {
} }
} }
public ChatUser(
long userId,
string userName,
ChatColour colour,
int rank,
ChatUserPermissions perms,
string nickName = null,
ChatUserStatus status = ChatUserStatus.Online,
string statusText = null,
bool isSuper = false
) {
UserId = userId;
UserName = userName ?? throw new ArgumentNullException(nameof(userName));
Colour = colour;
Rank = rank;
Permissions = perms;
NickName = nickName ?? string.Empty;
Status = status;
StatusText = statusText ?? string.Empty;
}
public bool Can(ChatUserPermissions perm, bool strict = false) { public bool Can(ChatUserPermissions perm, bool strict = false) {
ChatUserPermissions perms = Permissions & perm; ChatUserPermissions perms = Permissions & perm;
return strict ? perms == perm : perms > 0; return strict ? perms == perm : perms > 0;
@ -102,14 +90,6 @@ namespace SharpChat {
return UserId.GetHashCode(); return UserId.GetHashCode();
} }
public override bool Equals(object obj) {
return Equals(obj as ChatUser);
}
public bool Equals(ChatUser other) {
return UserId == other?.UserId;
}
public static string GetDMChannelName(ChatUser user1, ChatUser user2) { public static string GetDMChannelName(ChatUser user1, ChatUser user2) {
return user1.UserId < user2.UserId return user1.UserId < user2.UserId
? $"@{user1.UserId}-{user2.UserId}" ? $"@{user1.UserId}-{user2.UserId}"

View file

@ -21,7 +21,7 @@ namespace SharpChat.Commands {
else { else {
statusText = statusText.Trim(); statusText = statusText.Trim();
StringInfo sti = new StringInfo(statusText); StringInfo sti = new(statusText);
if(Encoding.UTF8.GetByteCount(statusText) > MAX_BYTES if(Encoding.UTF8.GetByteCount(statusText) > MAX_BYTES
|| sti.LengthInTextElements > MAX_GRAPHEMES) || sti.LengthInTextElements > MAX_GRAPHEMES)
statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, MAX_GRAPHEMES)).Trim(); statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, MAX_GRAPHEMES)).Trim();

View file

@ -10,7 +10,7 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
if(!ctx.Args.Any()) if(ctx.Args.Length < 1)
return; return;
string actionStr = string.Join(' ', ctx.Args); string actionStr = string.Join(' ', ctx.Args);
@ -19,8 +19,13 @@ namespace SharpChat.Commands {
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), SharpId.Next(),
ctx.Channel, ctx.Channel.Name,
ctx.User, ctx.User.UserId,
ctx.User.UserName,
ctx.User.Colour,
ctx.User.Rank,
ctx.User.NickName,
ctx.User.Permissions,
DateTimeOffset.Now, DateTimeOffset.Now,
actionStr, actionStr,
false, true, false false, true, false

View file

@ -4,12 +4,8 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class BanListCommand : IChatCommand { public class BanListCommand(MisuzuClient msz) : IChatCommand {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public BanListCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
}
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("bans") return ctx.NameEquals("bans")

View file

@ -18,7 +18,12 @@ namespace SharpChat.Commands {
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), SharpId.Next(),
string.Empty, string.Empty,
ctx.User, ctx.User.UserId,
ctx.User.UserName,
ctx.User.Colour,
ctx.User.Rank,
ctx.User.NickName,
ctx.User.Permissions,
DateTimeOffset.Now, DateTimeOffset.Now,
string.Join(' ', ctx.Args), string.Join(' ', ctx.Args),
false, false, true false, false, true

View file

@ -16,7 +16,7 @@ namespace SharpChat.Commands {
string firstArg = ctx.Args.First(); string firstArg = ctx.Args.First();
bool createChanHasHierarchy; bool createChanHasHierarchy;
if(!ctx.Args.Any() || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) { if(ctx.Args.Length < 1 || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -44,9 +44,10 @@ namespace SharpChat.Commands {
} }
ChatChannel createChan = new( ChatChannel createChan = new(
ctx.User, createChanName, createChanName,
isTemporary: !ctx.User.Can(ChatUserPermissions.SetChannelPermanent), isTemporary: !ctx.User.Can(ChatUserPermissions.SetChannelPermanent),
rank: createChanHierarchy rank: createChanHierarchy,
ownerId: ctx.User.UserId
); );
ctx.Chat.Channels.Add(createChan); ctx.Chat.Channels.Add(createChan);

View file

@ -11,7 +11,7 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
if(!ctx.Args.Any() || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) { if(ctx.Args.Length < 1 || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return; return;
} }

View file

@ -5,12 +5,8 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class KickBanCommand : IChatCommand { public class KickBanCommand(MisuzuClient msz) : IChatCommand {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public KickBanCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
}
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("kick") return ctx.NameEquals("kick")

View file

@ -45,7 +45,7 @@ namespace SharpChat.Commands {
else if(string.IsNullOrEmpty(nickStr)) else if(string.IsNullOrEmpty(nickStr))
nickStr = string.Empty; nickStr = string.Empty;
else { else {
StringInfo nsi = new StringInfo(nickStr); StringInfo nsi = new(nickStr);
if(Encoding.UTF8.GetByteCount(nickStr) > MAX_BYTES if(Encoding.UTF8.GetByteCount(nickStr) > MAX_BYTES
|| nsi.LengthInTextElements > MAX_GRAPHEMES) || nsi.LengthInTextElements > MAX_GRAPHEMES)
nickStr = nsi.SubstringByTextElements(0, Math.Min(nsi.LengthInTextElements, MAX_GRAPHEMES)).Trim(); nickStr = nsi.SubstringByTextElements(0, Math.Min(nsi.LengthInTextElements, MAX_GRAPHEMES)).Trim();

View file

@ -6,12 +6,8 @@ using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class PardonAddressCommand : IChatCommand { public class PardonAddressCommand(MisuzuClient msz) : IChatCommand {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public PardonAddressCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
}
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("pardonip") return ctx.NameEquals("pardonip")

View file

@ -5,12 +5,8 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class PardonUserCommand : IChatCommand { public class PardonUserCommand(MisuzuClient msz) : IChatCommand {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public PardonUserCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
}
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("pardon") return ctx.NameEquals("pardon")

View file

@ -15,7 +15,7 @@ namespace SharpChat.Commands {
return; return;
} }
if(!ctx.Args.Any() || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) { if(ctx.Args.Length < 1 || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY));
return; return;
} }

View file

@ -3,14 +3,9 @@ using System;
using System.Threading; using System.Threading;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class ShutdownRestartCommand : IChatCommand { public class ShutdownRestartCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : IChatCommand {
private readonly ManualResetEvent WaitHandle; private readonly ManualResetEvent WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle));
private readonly Func<bool> ShutdownCheck; private readonly Func<bool> ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck));
public ShutdownRestartCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) {
WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle));
ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck));
}
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("shutdown") return ctx.NameEquals("shutdown")

View file

@ -30,7 +30,12 @@ namespace SharpChat.Commands {
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), SharpId.Next(),
ChatUser.GetDMChannelName(ctx.User, whisperUser), ChatUser.GetDMChannelName(ctx.User, whisperUser),
ctx.User, ctx.User.UserId,
ctx.User.UserName,
ctx.User.Colour,
ctx.User.Rank,
ctx.User.NickName,
ctx.User.Permissions,
DateTimeOffset.Now, DateTimeOffset.Now,
string.Join(' ', ctx.Args.Skip(1)), string.Join(' ', ctx.Args.Skip(1)),
true, false, false true, false, false

View file

@ -1,11 +1,9 @@
using System; using System;
namespace SharpChat.Config { namespace SharpChat.Config {
public class CachedValue<T> { public class CachedValue<T>(IConfig config, string name, TimeSpan lifetime, T fallback) {
private IConfig Config { get; } private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
private string Name { get; } private string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
private TimeSpan Lifetime { get; }
private T Fallback { get; }
private object ConfigAccess { get; } = new(); private object ConfigAccess { get; } = new();
private object CurrentValue { get; set; } private object CurrentValue { get; set; }
@ -15,9 +13,9 @@ namespace SharpChat.Config {
get { get {
lock(ConfigAccess) { // this lock doesn't really make sense since it doesn't affect other config calls lock(ConfigAccess) { // this lock doesn't really make sense since it doesn't affect other config calls
DateTimeOffset now = DateTimeOffset.Now; DateTimeOffset now = DateTimeOffset.Now;
if((now - LastRead) >= Lifetime) { if((now - LastRead) >= lifetime) {
LastRead = now; LastRead = now;
CurrentValue = Config.ReadValue(Name, Fallback); CurrentValue = Config.ReadValue(Name, fallback);
Logger.Debug($"Read {Name} ({CurrentValue})"); Logger.Debug($"Read {Name} ({CurrentValue})");
} }
} }
@ -27,15 +25,6 @@ namespace SharpChat.Config {
public static implicit operator T(CachedValue<T> val) => val.Value; public static implicit operator T(CachedValue<T> val) => val.Value;
public CachedValue(IConfig config, string name, TimeSpan lifetime, T fallback) {
Config = config ?? throw new ArgumentNullException(nameof(config));
Name = name ?? throw new ArgumentNullException(nameof(name));
Lifetime = lifetime;
Fallback = fallback;
if(string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name cannot be empty.", nameof(name));
}
public void Refresh() { public void Refresh() {
LastRead = DateTimeOffset.MinValue; LastRead = DateTimeOffset.MinValue;
} }

View file

@ -6,11 +6,6 @@ namespace SharpChat.Config {
public ConfigException(string message, Exception ex) : base(message, ex) { } public ConfigException(string message, Exception ex) : base(message, ex) { }
} }
public class ConfigLockException : ConfigException { public class ConfigLockException() : ConfigException("Unable to acquire lock for reading configuration.") {}
public ConfigLockException() : base("Unable to acquire lock for reading configuration.") { } public class ConfigTypeException(Exception ex) : ConfigException("Given type does not match the value in the configuration.", ex) {}
}
public class ConfigTypeException : ConfigException {
public ConfigTypeException(Exception ex) : base("Given type does not match the value in the configuration.", ex) { }
}
} }

View file

@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpChat.Config { namespace SharpChat.Config {
public interface IConfig : IDisposable { public interface IConfig : IDisposable {

View file

@ -1,18 +1,9 @@
using System; using System;
namespace SharpChat.Config { namespace SharpChat.Config {
public class ScopedConfig : IConfig { public class ScopedConfig(IConfig config, string prefix) : IConfig {
private IConfig Config { get; } private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
private string Prefix { get; } private string Prefix { get; } = prefix ?? throw new ArgumentNullException(nameof(prefix));
public ScopedConfig(IConfig config, string prefix) {
Config = config ?? throw new ArgumentNullException(nameof(config));
Prefix = prefix ?? throw new ArgumentNullException(nameof(prefix));
if(string.IsNullOrWhiteSpace(prefix))
throw new ArgumentException("Prefix must exist.", nameof(prefix));
if(Prefix[^1] != ':')
Prefix += ':';
}
private string GetName(string name) { private string GetName(string name) {
return Prefix + name; return Prefix + name;

View file

@ -13,9 +13,6 @@ namespace SharpChat.Config {
private static readonly TimeSpan CACHE_LIFETIME = TimeSpan.FromMinutes(15); private static readonly TimeSpan CACHE_LIFETIME = TimeSpan.FromMinutes(15);
public StreamConfig(string fileName)
: this(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)) { }
public StreamConfig(Stream stream) { public StreamConfig(Stream stream) {
Stream = stream ?? throw new ArgumentNullException(nameof(stream)); Stream = stream ?? throw new ArgumentNullException(nameof(stream));
if(!Stream.CanRead) if(!Stream.CanRead)
@ -26,6 +23,10 @@ namespace SharpChat.Config {
Lock = new Mutex(); Lock = new Mutex();
} }
public static StreamConfig FromPath(string fileName) {
return new StreamConfig(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite));
}
public string ReadValue(string name, string fallback = null) { public string ReadValue(string name, string fallback = null) {
if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong
throw new ConfigLockException(); throw new ConfigLockException();
@ -39,7 +40,7 @@ namespace SharpChat.Config {
continue; continue;
line = line.TrimStart(); line = line.TrimStart();
if(line.StartsWith(";") || line.StartsWith("#")) if(line.StartsWith(';') || line.StartsWith('#'))
continue; continue;
string[] parts = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); string[] parts = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
@ -85,6 +86,11 @@ namespace SharpChat.Config {
} }
public IConfig ScopeTo(string prefix) { public IConfig ScopeTo(string prefix) {
if(string.IsNullOrWhiteSpace(prefix))
throw new ArgumentException("Prefix must exist.", nameof(prefix));
if(prefix[^1] != ':')
prefix += ':';
return new ScopedConfig(this, prefix); return new ScopedConfig(this, prefix);
} }

View file

@ -4,31 +4,18 @@ namespace SharpChat.EventStorage
{ {
public interface IEventStorage { public interface IEventStorage {
void AddEvent( void AddEvent(
long id, string type, long id,
object data = null, string type,
StoredEventFlags flags = StoredEventFlags.None
);
void AddEvent(
long id, string type,
string channelName, string channelName,
long senderId,
string senderName,
ChatColour senderColour,
int senderRank,
string senderNick,
ChatUserPermissions senderPerms,
object data = null, object data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
); );
void AddEvent(
long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
);
void AddEvent(
long id, string type,
string channelName,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
);
long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None);
void RemoveEvent(StoredEventInfo evt); void RemoveEvent(StoredEventInfo evt);
StoredEventInfo GetEvent(long seqId); StoredEventInfo GetEvent(long seqId);
IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0); IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0);

View file

@ -1,45 +1,13 @@
using MySqlConnector; using MySqlConnector;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Channels;
namespace SharpChat.EventStorage namespace SharpChat.EventStorage
{ {
public partial class MariaDBEventStorage : IEventStorage { public partial class MariaDBEventStorage(string connString) : IEventStorage {
private string ConnectionString { get; } private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString));
public MariaDBEventStorage(string connString) {
ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString));
}
public void AddEvent(
long id, string type,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
string channelName,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags);
}
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
@ -48,8 +16,7 @@ namespace SharpChat.EventStorage
object data = null, object data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
if(type == null) ArgumentNullException.ThrowIfNull(type);
throw new ArgumentNullException(nameof(type));
RunCommand( RunCommand(
"INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`" "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`"
@ -71,8 +38,7 @@ namespace SharpChat.EventStorage
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) { public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null) ArgumentNullException.ThrowIfNull(type);
throw new ArgumentNullException(nameof(type));
long id = SharpId.Next(); long id = SharpId.Next();
@ -137,7 +103,7 @@ namespace SharpChat.EventStorage
} }
public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) { public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
List<StoredEventInfo> events = new(); List<StoredEventInfo> events = [];
try { try {
using MySqlDataReader reader = RunQuery( using MySqlDataReader reader = RunQuery(
@ -170,8 +136,7 @@ namespace SharpChat.EventStorage
} }
public void RemoveEvent(StoredEventInfo evt) { public void RemoveEvent(StoredEventInfo evt) {
if(evt == null) ArgumentNullException.ThrowIfNull(evt);
throw new ArgumentNullException(nameof(evt));
RunCommand( RunCommand(
"UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL", "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL",
new MySqlParameter("id", evt.Id) new MySqlParameter("id", evt.Id)

View file

@ -1,10 +1,8 @@
using System; using System;
namespace SharpChat.EventStorage namespace SharpChat.EventStorage {
{
[Flags] [Flags]
public enum StoredEventFlags public enum StoredEventFlags {
{
None = 0, None = 0,
Action = 1, Action = 1,
Broadcast = 1 << 1, Broadcast = 1 << 1,

View file

@ -2,34 +2,23 @@
using System.Text.Json; using System.Text.Json;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage {
public class StoredEventInfo { public class StoredEventInfo(
public long Id { get; set; } long id,
public string Type { get; set; } string type,
public ChatUser Sender { get; set; } ChatUser sender,
public DateTimeOffset Created { get; set; } DateTimeOffset created,
public DateTimeOffset? Deleted { get; set; } DateTimeOffset? deleted,
public string ChannelName { get; set; } string channelName,
public StoredEventFlags Flags { get; set; } JsonDocument data,
public JsonDocument Data { get; set; } StoredEventFlags flags
) {
public StoredEventInfo( public long Id { get; set; } = id;
long id, public string Type { get; set; } = type;
string type, public ChatUser Sender { get; set; } = sender;
ChatUser sender, public DateTimeOffset Created { get; set; } = created;
DateTimeOffset created, public DateTimeOffset? Deleted { get; set; } = deleted;
DateTimeOffset? deleted, public string ChannelName { get; set; } = channelName;
string channelName, public StoredEventFlags Flags { get; set; } = flags;
JsonDocument data, public JsonDocument Data { get; set; } = data;
StoredEventFlags flags
) {
Id = id;
Type = type;
Sender = sender;
Created = created;
Deleted = deleted;
ChannelName = channelName;
Data = data;
Flags = flags;
}
} }
} }

View file

@ -5,33 +5,7 @@ using System.Text.Json;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage {
public class VirtualEventStorage : IEventStorage { public class VirtualEventStorage : IEventStorage {
private readonly Dictionary<long, StoredEventInfo> Events = new(); private readonly Dictionary<long, StoredEventInfo> Events = [];
public void AddEvent(
long id, string type,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
string channelName,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags);
}
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
@ -40,8 +14,7 @@ namespace SharpChat.EventStorage {
object data = null, object data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
if(type == null) ArgumentNullException.ThrowIfNull(type);
throw new ArgumentNullException(nameof(type));
// VES is meant as an emergency fallback but this is something else // VES is meant as an emergency fallback but this is something else
JsonDocument hack = JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data)); JsonDocument hack = JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data));
@ -55,35 +28,12 @@ namespace SharpChat.EventStorage {
), DateTimeOffset.Now, null, channelName, hack, flags)); ), DateTimeOffset.Now, null, channelName, hack, flags));
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null)
throw new ArgumentNullException(nameof(type));
long id = SharpId.Next();
AddEvent(
id, type,
channel?.Name,
user?.UserId ?? 0,
user?.UserName,
user?.Colour ?? ChatColour.None,
user?.Rank ?? 0,
user?.NickName,
user?.Permissions ?? 0,
data,
flags
);
return id;
}
public StoredEventInfo GetEvent(long seqId) { public StoredEventInfo GetEvent(long seqId) {
return Events.TryGetValue(seqId, out StoredEventInfo evt) ? evt : null; return Events.TryGetValue(seqId, out StoredEventInfo evt) ? evt : null;
} }
public void RemoveEvent(StoredEventInfo evt) { public void RemoveEvent(StoredEventInfo evt) {
if(evt == null) ArgumentNullException.ThrowIfNull(evt);
throw new ArgumentNullException(nameof(evt));
Events.Remove(evt.Id); Events.Remove(evt.Id);
} }
@ -96,7 +46,7 @@ namespace SharpChat.EventStorage {
start = 0; start = 0;
} }
return subset.Skip(start).Take(amount).ToArray(); return [.. subset.Skip(start).Take(amount)];
} }
} }
} }

View file

@ -1,10 +1,3 @@
using System; namespace SharpChat.Events {
using System.Collections.Generic; public interface IChatEvent {}
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpChat.Events {
public interface IChatEvent {
}
} }

View file

@ -1,95 +1,34 @@
using System; using System;
namespace SharpChat.Events { namespace SharpChat.Events {
public class MessageCreateEvent : IChatEvent { public class MessageCreateEvent(
public long MessageId { get; } long msgId,
public string ChannelName { get; } string channelName,
public long SenderId { get; } long senderId,
public string SenderName { get; } string senderName,
public ChatColour SenderColour { get; } ChatColour senderColour,
public int SenderRank { get; } int senderRank,
public string SenderNickName { get; } string senderNickName,
public ChatUserPermissions SenderPerms { get; } ChatUserPermissions senderPerms,
public DateTimeOffset MessageCreated { get; } DateTimeOffset msgCreated,
string msgText,
bool isPrivate,
bool isAction,
bool isBroadcast
) : IChatEvent {
public long MessageId { get; } = msgId;
public string ChannelName { get; } = channelName;
public long SenderId { get; } = senderId;
public string SenderName { get; } = senderName;
public ChatColour SenderColour { get; } = senderColour;
public int SenderRank { get; } = senderRank;
public string SenderNickName { get; } = senderNickName;
public ChatUserPermissions SenderPerms { get; } = senderPerms;
public DateTimeOffset MessageCreated { get; } = msgCreated;
public string MessageChannel { get; } public string MessageChannel { get; }
public string MessageText { get; } public string MessageText { get; } = msgText;
public bool IsPrivate { get; } public bool IsPrivate { get; } = isPrivate;
public bool IsAction { get; } public bool IsAction { get; } = isAction;
public bool IsBroadcast { get; } public bool IsBroadcast { get; } = isBroadcast;
public MessageCreateEvent(
long msgId,
string channelName,
long senderId,
string senderName,
ChatColour senderColour,
int senderRank,
string senderNickName,
ChatUserPermissions senderPerms,
DateTimeOffset msgCreated,
string msgText,
bool isPrivate,
bool isAction,
bool isBroadcast
) {
MessageId = msgId;
ChannelName = channelName;
SenderId = senderId;
SenderName = senderName;
SenderColour = senderColour;
SenderRank = senderRank;
SenderNickName = senderNickName;
SenderPerms = senderPerms;
MessageCreated = msgCreated;
MessageText = msgText;
IsPrivate = isPrivate;
IsAction = isAction;
IsBroadcast = isBroadcast;
}
public MessageCreateEvent(
long msgId,
string channelName,
ChatUser sender,
DateTimeOffset msgCreated,
string msgText,
bool isPrivate,
bool isAction,
bool isBroadcast
) : this(
msgId,
channelName,
sender?.UserId ?? -1,
sender?.UserName ?? null,
sender?.Colour ?? ChatColour.None,
sender?.Rank ?? 0,
sender?.NickName ?? null,
sender?.Permissions ?? 0,
msgCreated,
msgText,
isPrivate,
isAction,
isBroadcast
) { }
public MessageCreateEvent(
long msgId,
ChatChannel channel,
ChatUser sender,
DateTimeOffset msgCreated,
string msgText,
bool isPrivate,
bool isAction,
bool isBroadcast
) : this(
msgId,
channel?.Name ?? null,
sender,
msgCreated,
msgText,
isPrivate,
isAction,
isBroadcast
) { }
} }
} }

View file

@ -6,13 +6,9 @@ namespace SharpChat {
IEnumerable<string> Pack(); IEnumerable<string> Pack();
} }
public abstract class ServerPacket : IServerPacket { public abstract class ServerPacket(long sequenceId = 0) : IServerPacket {
public long SequenceId { get; } // Allow sequence id to be manually set for potential message repeats
public long SequenceId { get; } = sequenceId > 0 ? sequenceId : SharpId.Next();
public ServerPacket(long sequenceId = 0) {
// Allow sequence id to be manually set for potential message repeats
SequenceId = sequenceId > 0 ? sequenceId : SharpId.Next();
}
public abstract IEnumerable<string> Pack(); public abstract IEnumerable<string> Pack();
} }

View file

@ -25,8 +25,5 @@ namespace SharpChat.Misuzu {
[JsonPropertyName("perms")] [JsonPropertyName("perms")]
public ChatUserPermissions Permissions { get; set; } public ChatUserPermissions Permissions { get; set; }
[JsonPropertyName("super")]
public bool IsSuper { get; set; }
} }
} }

View file

@ -34,8 +34,7 @@ namespace SharpChat.Misuzu {
private CachedValue<string> SecretKey { get; } private CachedValue<string> SecretKey { get; }
public MisuzuClient(HttpClient httpClient, IConfig config) { public MisuzuClient(HttpClient httpClient, IConfig config) {
if(config == null) ArgumentNullException.ThrowIfNull(config);
throw new ArgumentNullException(nameof(config));
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
BaseURL = config.ReadCached("url", DEFAULT_BASE_URL); BaseURL = config.ReadCached("url", DEFAULT_BASE_URL);
@ -77,8 +76,7 @@ namespace SharpChat.Misuzu {
} }
public async Task BumpUsersOnlineAsync(IEnumerable<(string userId, string ipAddr)> list) { public async Task BumpUsersOnlineAsync(IEnumerable<(string userId, string ipAddr)> list) {
if(list == null) ArgumentNullException.ThrowIfNull(list);
throw new ArgumentNullException(nameof(list));
if(!list.Any()) if(!list.Any())
return; return;
@ -160,8 +158,7 @@ namespace SharpChat.Misuzu {
} }
public async Task<bool> RevokeBanAsync(MisuzuBanInfo banInfo, BanRevokeKind kind) { public async Task<bool> RevokeBanAsync(MisuzuBanInfo banInfo, BanRevokeKind kind) {
if(banInfo == null) ArgumentNullException.ThrowIfNull(banInfo);
throw new ArgumentNullException(nameof(banInfo));
string type = kind switch { string type = kind switch {
BanRevokeKind.UserId => "user", BanRevokeKind.UserId => "user",

View file

@ -3,23 +3,13 @@ using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class AuthSuccessPacket : ServerPacket { public class AuthSuccessPacket(
public ChatUser User { get; private set; } ChatUser user,
public ChatChannel Channel { get; private set; } ChatChannel channel,
public ChatConnection Connection { get; private set; } int maxMsgLength
public int MaxMessageLength { get; private set; } ) : ServerPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public AuthSuccessPacket( public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel));
ChatUser user,
ChatChannel channel,
ChatConnection connection,
int maxMsgLength
) {
User = user ?? throw new ArgumentNullException(nameof(user));
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
MaxMessageLength = maxMsgLength;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -30,9 +20,9 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append(Channel.Name); sb.Append(Channel.Name);
sb.Append('\t'); sb.Append('\t');
sb.Append(MaxMessageLength); sb.Append(maxMsgLength);
return new[] { sb.ToString() }; yield return sb.ToString();
} }
} }
} }

View file

@ -5,12 +5,8 @@ using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class BanListPacket : ServerPacket { public class BanListPacket(IEnumerable<MisuzuBanInfo> bans) : ServerPacket {
public IEnumerable<MisuzuBanInfo> Bans { get; private set; } public IEnumerable<MisuzuBanInfo> Bans { get; private set; } = bans ?? throw new ArgumentNullException(nameof(bans));
public BanListPacket(IEnumerable<MisuzuBanInfo> bans) {
Bans = bans ?? throw new ArgumentNullException(nameof(bans));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -32,7 +28,7 @@ namespace SharpChat.Packet {
sb.Append(SequenceId); sb.Append(SequenceId);
sb.Append("\t10010"); sb.Append("\t10010");
return new[] { sb.ToString() }; yield return sb.ToString();
} }
} }
} }

View file

@ -1,13 +1,10 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ChannelCreatePacket : ServerPacket { public class ChannelCreatePacket(ChatChannel channel) : ServerPacket {
public ChatChannel Channel { get; private set; } public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel));
public ChannelCreatePacket(ChatChannel channel) {
Channel = channel;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -3,12 +3,8 @@ using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ChannelDeletePacket : ServerPacket { public class ChannelDeletePacket(ChatChannel channel) : ServerPacket {
public ChatChannel Channel { get; private set; } public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel));
public ChannelDeletePacket(ChatChannel channel) {
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -1,15 +1,11 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ChannelUpdatePacket : ServerPacket { public class ChannelUpdatePacket(string previousName, ChatChannel channel) : ServerPacket {
public string PreviousName { get; private set; } public string PreviousName { get; private set; } = previousName ?? throw new ArgumentNullException(nameof(previousName));
public ChatChannel Channel { get; private set; } public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel));
public ChannelUpdatePacket(string previousName, ChatChannel channel) {
PreviousName = previousName;
Channel = channel;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -1,46 +1,17 @@
using SharpChat.EventStorage; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ChatMessageAddPacket : ServerPacket { public class ChatMessageAddPacket(
public DateTimeOffset Created { get; } long msgId,
public long UserId { get; } DateTimeOffset created,
public string Text { get; } long userId,
public bool IsAction { get; } string text,
public bool IsPrivate { get; } bool isAction,
bool isPrivate
public ChatMessageAddPacket( ) : ServerPacket(msgId) {
long msgId, public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text));
DateTimeOffset created,
long userId,
string text,
bool isAction,
bool isPrivate
) : base(msgId) {
Created = created;
UserId = userId < 0 ? -1 : userId;
Text = text;
IsAction = isAction;
IsPrivate = isPrivate;
}
public static ChatMessageAddPacket FromStoredEvent(StoredEventInfo sei) {
if(sei == null)
throw new ArgumentNullException(nameof(sei));
if(sei.Type is not "msg:add" and not "SharpChat.Events.ChatMessage")
throw new ArgumentException("Wrong event type.", nameof(sei));
return new ChatMessageAddPacket(
sei.Id,
sei.Created,
sei.Sender?.UserId ?? -1,
string.Empty, // todo: this
(sei.Flags & StoredEventFlags.Action) > 0,
(sei.Flags & StoredEventFlags.Private) > 0
);
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -48,13 +19,13 @@ namespace SharpChat.Packet {
sb.Append('2'); sb.Append('2');
sb.Append('\t'); sb.Append('\t');
sb.Append(Created.ToUnixTimeSeconds()); sb.Append(created.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
sb.Append(UserId); sb.Append(userId);
sb.Append('\t'); sb.Append('\t');
if(IsAction) if(isAction)
sb.Append("<i>"); sb.Append("<i>");
sb.Append( sb.Append(
@ -64,16 +35,16 @@ namespace SharpChat.Packet {
.Replace("\t", " ") .Replace("\t", " ")
); );
if(IsAction) if(isAction)
sb.Append("</i>"); sb.Append("</i>");
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(SequenceId);
sb.AppendFormat( sb.AppendFormat(
"\t1{0}0{1}{2}", "\t1{0}0{1}{2}",
IsAction ? '1' : '0', isAction ? '1' : '0',
IsAction ? '0' : '1', isAction ? '0' : '1',
IsPrivate ? '1' : '0' isPrivate ? '1' : '0'
); );
yield return sb.ToString(); yield return sb.ToString();

View file

@ -2,19 +2,13 @@
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ChatMessageDeletePacket : ServerPacket { public class ChatMessageDeletePacket(long eventId) : ServerPacket {
public long EventId { get; private set; }
public ChatMessageDeletePacket(long eventId) {
EventId = eventId;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('6'); sb.Append('6');
sb.Append('\t'); sb.Append('\t');
sb.Append(EventId); sb.Append(eventId);
yield return sb.ToString(); yield return sb.ToString();
} }

View file

@ -4,12 +4,8 @@ using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ContextChannelsPacket : ServerPacket { public class ContextChannelsPacket(IEnumerable<ChatChannel> channels) : ServerPacket {
public IEnumerable<ChatChannel> Channels { get; private set; } public IEnumerable<ChatChannel> Channels { get; private set; } = channels?.Where(c => c != null) ?? throw new ArgumentNullException(nameof(channels));
public ContextChannelsPacket(IEnumerable<ChatChannel> channels) {
Channels = channels?.Where(c => c != null) ?? throw new ArgumentNullException(nameof(channels));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -10,24 +10,13 @@ namespace SharpChat.Packet {
MessagesUsersChannels = 4, MessagesUsersChannels = 4,
} }
public class ContextClearPacket : ServerPacket { public class ContextClearPacket(ContextClearMode mode) : ServerPacket {
public ChatChannel Channel { get; private set; }
public ContextClearMode Mode { get; private set; }
public bool IsGlobal
=> Channel == null;
public ContextClearPacket(ChatChannel channel, ContextClearMode mode) {
Channel = channel;
Mode = mode;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('8'); sb.Append('8');
sb.Append('\t'); sb.Append('\t');
sb.Append((int)Mode); sb.Append((int)mode);
yield return sb.ToString(); yield return sb.ToString();
} }

View file

@ -5,14 +5,8 @@ using System.Text;
namespace SharpChat.Packet namespace SharpChat.Packet
{ {
public class ContextMessagePacket : ServerPacket { public class ContextMessagePacket(StoredEventInfo evt, bool notify = false) : ServerPacket {
public StoredEventInfo Event { get; private set; } public StoredEventInfo Event { get; private set; } = evt ?? throw new ArgumentNullException(nameof(evt));
public bool Notify { get; private set; }
public ContextMessagePacket(StoredEventInfo evt, bool notify = false) {
Event = evt ?? throw new ArgumentNullException(nameof(evt));
Notify = notify;
}
private const string V1_CHATBOT = "-1\tChatBot\tinherit\t\t"; private const string V1_CHATBOT = "-1\tChatBot\tinherit\t\t";
@ -106,7 +100,7 @@ namespace SharpChat.Packet
sb.Append('\t'); sb.Append('\t');
sb.Append(Event.Id < 1 ? SequenceId : Event.Id); sb.Append(Event.Id < 1 ? SequenceId : Event.Id);
sb.Append('\t'); sb.Append('\t');
sb.Append(Notify ? '1' : '0'); sb.Append(notify ? '1' : '0');
sb.AppendFormat( sb.AppendFormat(
"\t1{0}0{1}{2}", "\t1{0}0{1}{2}",
isAction ? '1' : '0', isAction ? '1' : '0',

View file

@ -4,12 +4,8 @@ using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ContextUsersPacket : ServerPacket { public class ContextUsersPacket(IEnumerable<ChatUser> users) : ServerPacket {
public IEnumerable<ChatUser> Users { get; private set; } public IEnumerable<ChatUser> Users { get; private set; } = users?.Where(u => u != null) ?? throw new ArgumentNullException(nameof(users));
public ContextUsersPacket(IEnumerable<ChatUser> users) {
Users = users?.Where(u => u != null) ?? throw new ArgumentNullException(nameof(users));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -1,23 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class LegacyCommandResponse : ServerPacket { public class LegacyCommandResponse(
public bool IsError { get; private set; } string stringId,
public string StringId { get; private set; } bool isError = true,
public IEnumerable<object> Arguments { get; private set; } params object[] args
) : ServerPacket {
public LegacyCommandResponse( public string StringId { get; private set; } = stringId ?? throw new ArgumentNullException(nameof(stringId));
string stringId,
bool isError = true,
params object[] args
) {
IsError = isError;
StringId = stringId;
Arguments = args;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -36,12 +27,12 @@ namespace SharpChat.Packet {
sb.Append("\t-1\t"); sb.Append("\t-1\t");
} }
sb.Append(IsError ? '1' : '0'); sb.Append(isError ? '1' : '0');
sb.Append('\f'); sb.Append('\f');
sb.Append(StringId == LCR.WELCOME ? LCR.BROADCAST : StringId); sb.Append(StringId == LCR.WELCOME ? LCR.BROADCAST : StringId);
if(Arguments?.Any() == true) if(args.Length > 0)
foreach(object arg in Arguments) { foreach(object arg in args) {
sb.Append('\f'); sb.Append('\f');
sb.Append(arg); sb.Append(arg);
} }

View file

@ -1,6 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class PongPacket : ServerPacket { public class PongPacket : ServerPacket {

View file

@ -3,12 +3,8 @@ using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserChannelForceJoinPacket : ServerPacket { public class UserChannelForceJoinPacket(ChatChannel channel) : ServerPacket {
public ChatChannel Channel { get; private set; } public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel));
public UserChannelForceJoinPacket(ChatChannel channel) {
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -3,12 +3,8 @@ using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserChannelJoinPacket : ServerPacket { public class UserChannelJoinPacket(ChatUser user) : ServerPacket {
public ChatUser User { get; private set; } public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public UserChannelJoinPacket(ChatUser user) {
User = user ?? throw new ArgumentNullException(nameof(user));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -21,7 +17,7 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(SequenceId);
return new[] { sb.ToString() }; yield return sb.ToString();
} }
} }
} }

View file

@ -3,12 +3,8 @@ using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserChannelLeavePacket : ServerPacket { public class UserChannelLeavePacket(ChatUser user) : ServerPacket {
public ChatUser User { get; private set; } public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public UserChannelLeavePacket(ChatUser user) {
User = user ?? throw new ArgumentNullException(nameof(user));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();

View file

@ -3,21 +3,15 @@ using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserConnectPacket : ServerPacket { public class UserConnectPacket(DateTimeOffset joined, ChatUser user) : ServerPacket {
public DateTimeOffset Joined { get; private set; } public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public ChatUser User { get; private set; }
public UserConnectPacket(DateTimeOffset joined, ChatUser user) {
Joined = joined;
User = user ?? throw new ArgumentNullException(nameof(user));
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('1'); sb.Append('1');
sb.Append('\t'); sb.Append('\t');
sb.Append(Joined.ToUnixTimeSeconds()); sb.Append(joined.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
sb.Append(User.Pack()); sb.Append(User.Pack());
sb.Append('\t'); sb.Append('\t');

View file

@ -10,16 +10,8 @@ namespace SharpChat.Packet {
Flood, Flood,
} }
public class UserDisconnectPacket : ServerPacket { public class UserDisconnectPacket(DateTimeOffset disconnected, ChatUser user, UserDisconnectReason reason) : ServerPacket {
public DateTimeOffset Disconnected { get; private set; } public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public ChatUser User { get; private set; }
public UserDisconnectReason Reason { get; private set; }
public UserDisconnectPacket(DateTimeOffset disconnected, ChatUser user, UserDisconnectReason reason) {
Disconnected = disconnected;
User = user ?? throw new ArgumentNullException(nameof(user));
Reason = reason;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -31,7 +23,7 @@ namespace SharpChat.Packet {
sb.Append(User.LegacyNameWithStatus); sb.Append(User.LegacyNameWithStatus);
sb.Append('\t'); sb.Append('\t');
switch(Reason) { switch(reason) {
case UserDisconnectReason.Leave: case UserDisconnectReason.Leave:
default: default:
sb.Append("leave"); sb.Append("leave");
@ -48,11 +40,11 @@ namespace SharpChat.Packet {
} }
sb.Append('\t'); sb.Append('\t');
sb.Append(Disconnected.ToUnixTimeSeconds()); sb.Append(disconnected.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(SequenceId);
return new[] { sb.ToString() }; yield return sb.ToString();
} }
} }
} }

View file

@ -3,26 +3,20 @@ using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserUpdatePacket : ServerPacket { public class UserUpdatePacket(ChatUser user, string previousName = "") : ServerPacket {
public ChatUser User { get; private set; } public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public string PreviousName { get; private set; }
public UserUpdatePacket(ChatUser user, string previousName = null) {
User = user ?? throw new ArgumentNullException(nameof(user));
PreviousName = previousName;
}
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
bool isSilent = string.IsNullOrEmpty(PreviousName); bool isSilent = string.IsNullOrEmpty(previousName);
if(!isSilent) { if(!isSilent) {
sb.Append('2'); sb.Append('2');
sb.Append('\t'); sb.Append('\t');
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds()); sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
sb.Append("\t-1\t0\fnick\f"); sb.Append("\t-1\t0\fnick\f");
sb.Append(PreviousName); sb.Append(previousName);
sb.Append('\f'); sb.Append('\f');
sb.Append(User.LegacyNameWithStatus); sb.Append(User.LegacyNameWithStatus);
sb.Append('\t'); sb.Append('\t');

View file

@ -8,23 +8,16 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SharpChat.PacketHandlers { namespace SharpChat.PacketHandlers {
public class AuthHandler : IChatPacketHandler { public class AuthHandler(
private readonly MisuzuClient Misuzu; MisuzuClient msz,
private readonly ChatChannel DefaultChannel; ChatChannel defaultChannel,
private readonly CachedValue<int> MaxMessageLength; CachedValue<int> maxMsgLength,
private readonly CachedValue<int> MaxConnections; CachedValue<int> maxConns
) : IChatPacketHandler {
public AuthHandler( private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
MisuzuClient msz, private readonly ChatChannel DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
ChatChannel defaultChannel, private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
CachedValue<int> maxMsgLength, private readonly CachedValue<int> MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns));
CachedValue<int> maxConns
) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns));
}
public bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(ChatPacketHandlerContext ctx) {
return ctx.CheckPacketId("1"); return ctx.CheckPacketId("1");
@ -108,8 +101,7 @@ namespace SharpChat.PacketHandlers {
fai.UserName, fai.UserName,
fai.Colour, fai.Colour,
fai.Rank, fai.Rank,
fai.Permissions, fai.Permissions
isSuper: fai.IsSuper
); );
else else
ctx.Chat.UpdateUser( ctx.Chat.UpdateUser(
@ -117,8 +109,7 @@ namespace SharpChat.PacketHandlers {
userName: fai.UserName, userName: fai.UserName,
colour: fai.Colour, colour: fai.Colour,
rank: fai.Rank, rank: fai.Rank,
perms: fai.Permissions, perms: fai.Permissions
isSuper: fai.IsSuper
); );
// Enforce a maximum amount of connections per user // Enforce a maximum amount of connections per user

View file

@ -5,16 +5,12 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SharpChat.PacketHandlers { namespace SharpChat.PacketHandlers {
public class PingHandler : IChatPacketHandler { public class PingHandler(MisuzuClient msz) : IChatPacketHandler {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
private readonly TimeSpan BumpInterval = TimeSpan.FromMinutes(1); private readonly TimeSpan BumpInterval = TimeSpan.FromMinutes(1);
private DateTimeOffset LastBump = DateTimeOffset.MinValue; private DateTimeOffset LastBump = DateTimeOffset.MinValue;
public PingHandler(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
}
public bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(ChatPacketHandlerContext ctx) {
return ctx.CheckPacketId("0"); return ctx.CheckPacketId("0");
} }
@ -31,12 +27,11 @@ namespace SharpChat.PacketHandlers {
ctx.Chat.ContextAccess.Wait(); ctx.Chat.ContextAccess.Wait();
try { try {
if(LastBump < DateTimeOffset.UtcNow - BumpInterval) { if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
(string, string)[] bumpList = ctx.Chat.Users (string, string)[] bumpList = [.. ctx.Chat.Users
.Where(u => u.Status == ChatUserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u)) .Where(u => u.Status == ChatUserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u))
.Select(u => (u.UserId.ToString(), ctx.Chat.GetRemoteAddresses(u).FirstOrDefault()?.ToString() ?? string.Empty)) .Select(u => (u.UserId.ToString(), ctx.Chat.GetRemoteAddresses(u).FirstOrDefault()?.ToString() ?? string.Empty))];
.ToArray();
if(bumpList.Any()) if(bumpList.Length > 0)
Task.Run(async () => { Task.Run(async () => {
await Misuzu.BumpUsersOnlineAsync(bumpList); await Misuzu.BumpUsersOnlineAsync(bumpList);
}).Wait(); }).Wait();

View file

@ -1,24 +1,16 @@
using SharpChat.Commands; using SharpChat.Config;
using SharpChat.Config;
using SharpChat.Events; using SharpChat.Events;
using SharpChat.EventStorage;
using SharpChat.Packet;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.PacketHandlers namespace SharpChat.PacketHandlers {
{ public class SendMessageHandler(CachedValue<int> maxMsgLength) : IChatPacketHandler {
public class SendMessageHandler : IChatPacketHandler { private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
private readonly CachedValue<int> MaxMessageLength;
private List<IChatCommand> Commands { get; } = new(); private List<IChatCommand> Commands { get; } = [];
public SendMessageHandler(CachedValue<int> maxMsgLength) {
MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
}
public void AddCommand(IChatCommand command) { public void AddCommand(IChatCommand command) {
Commands.Add(command ?? throw new ArgumentNullException(nameof(command))); Commands.Add(command ?? throw new ArgumentNullException(nameof(command)));
@ -57,7 +49,7 @@ namespace SharpChat.PacketHandlers
ctx.Chat.UpdateUser(user, status: ChatUserStatus.Online); ctx.Chat.UpdateUser(user, status: ChatUserStatus.Online);
int maxMsgLength = MaxMessageLength; int maxMsgLength = MaxMessageLength;
StringInfo messageTextInfo = new StringInfo(messageText); StringInfo messageTextInfo = new(messageText);
if(Encoding.UTF8.GetByteCount(messageText) > (maxMsgLength * 10) if(Encoding.UTF8.GetByteCount(messageText) > (maxMsgLength * 10)
|| messageTextInfo.LengthInTextElements > maxMsgLength) || messageTextInfo.LengthInTextElements > maxMsgLength)
messageText = messageTextInfo.SubstringByTextElements(0, Math.Min(messageTextInfo.LengthInTextElements, maxMsgLength)); messageText = messageTextInfo.SubstringByTextElements(0, Math.Min(messageTextInfo.LengthInTextElements, maxMsgLength));
@ -68,7 +60,7 @@ namespace SharpChat.PacketHandlers
Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}"); Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
#endif #endif
if(messageText.StartsWith("/")) { if(messageText.StartsWith('/')) {
ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel); ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
IChatCommand command = null; IChatCommand command = null;
@ -87,8 +79,13 @@ namespace SharpChat.PacketHandlers
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), SharpId.Next(),
channel, channel.Name,
user, user.UserId,
user.UserName,
user.Colour,
user.Rank,
user.NickName,
user.Permissions,
DateTimeOffset.Now, DateTimeOffset.Now,
messageText, messageText,
false, false, false false, false, false

View file

@ -45,7 +45,7 @@ namespace SharpChat {
if(!File.Exists(configFile) && configFile == CONFIG) if(!File.Exists(configFile) && configFile == CONFIG)
ConvertConfiguration(); ConvertConfiguration();
using IConfig config = new StreamConfig(configFile); using StreamConfig config = StreamConfig.FromPath(configFile);
if(hasCancelled) return; if(hasCancelled) return;
@ -124,7 +124,7 @@ namespace SharpChat {
const string mdb_config = @"mariadb.txt"; const string mdb_config = @"mariadb.txt";
string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : Array.Empty<string>(); string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : [];
sw.WriteLine(); sw.WriteLine();
sw.WriteLine("# MariaDB configuration"); sw.WriteLine("# MariaDB configuration");

View file

@ -2,12 +2,20 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Fleck" Version="1.2.0" /> <PackageReference Include="Fleck" Version="1.2.0" />
<PackageReference Include="MySqlConnector" Version="2.2.5" /> <PackageReference Include="MySqlConnector" Version="2.4.0" />
</ItemGroup> </ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View file

@ -37,7 +37,7 @@ namespace SharpChat {
} }
ListenerSocket = new SocketWrapper(socket); ListenerSocket = new SocketWrapper(socket);
SupportedSubProtocols = Array.Empty<string>(); SupportedSubProtocols = [];
} }
public ISocket ListenerSocket { get; set; } public ISocket ListenerSocket { get; set; }

View file

@ -30,12 +30,14 @@ namespace SharpChat {
private readonly CachedValue<int> FloodKickLength; private readonly CachedValue<int> FloodKickLength;
private readonly CachedValue<int> FloodKickExemptRank; private readonly CachedValue<int> FloodKickExemptRank;
private readonly List<IChatPacketHandler> GuestHandlers = new(); private readonly List<IChatPacketHandler> GuestHandlers = [];
private readonly List<IChatPacketHandler> AuthedHandlers = new(); private readonly List<IChatPacketHandler> AuthedHandlers = [];
private readonly SendMessageHandler SendMessageHandler; private readonly SendMessageHandler SendMessageHandler;
private bool IsShuttingDown = false; private bool IsShuttingDown = false;
private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
private ChatChannel DefaultChannel { get; set; } private ChatChannel DefaultChannel { get; set; }
public SockChatServer(HttpClient httpClient, MisuzuClient msz, IEventStorage evtStore, IConfig config) { public SockChatServer(HttpClient httpClient, MisuzuClient msz, IEventStorage evtStore, IConfig config) {
@ -51,7 +53,7 @@ namespace SharpChat {
Context = new ChatContext(evtStore); Context = new ChatContext(evtStore);
string[] channelNames = config.ReadValue("channels", new[] { "lounge" }); string[] channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
foreach(string channelName in channelNames) { foreach(string channelName in channelNames) {
IConfig channelCfg = config.ScopeTo($"channels:{channelName}"); IConfig channelCfg = config.ScopeTo($"channels:{channelName}");
@ -72,12 +74,12 @@ namespace SharpChat {
GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections)); GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections));
AuthedHandlers.AddRange(new IChatPacketHandler[] { AuthedHandlers.AddRange([
new PingHandler(Misuzu), new PingHandler(Misuzu),
SendMessageHandler = new SendMessageHandler(MaxMessageLength), SendMessageHandler = new SendMessageHandler(MaxMessageLength),
}); ]);
SendMessageHandler.AddCommands(new IChatCommand[] { SendMessageHandler.AddCommands([
new AFKCommand(), new AFKCommand(),
new NickCommand(), new NickCommand(),
new WhisperCommand(), new WhisperCommand(),
@ -95,7 +97,7 @@ namespace SharpChat {
new PardonAddressCommand(msz), new PardonAddressCommand(msz),
new BanListCommand(msz), new BanListCommand(msz),
new RemoteAddressCommand(), new RemoteAddressCommand(),
}); ]);
ushort port = config.SafeReadValue("port", DEFAULT_PORT); ushort port = config.SafeReadValue("port", DEFAULT_PORT);
Server = new SharpChatWebSocketServer($"ws://0.0.0.0:{port}"); Server = new SharpChatWebSocketServer($"ws://0.0.0.0:{port}");