Name adjustments and moved some things to the common lib.

This commit is contained in:
flash 2025-04-26 12:51:08 +00:00
parent b8ec381f3b
commit 0cc5d46ea9
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
50 changed files with 323 additions and 323 deletions

View file

@ -1,6 +1,6 @@
namespace SharpChat { namespace SharpChat {
public interface C2SPacketHandler { public interface C2SPacketHandler {
bool IsMatch(ChatPacketHandlerContext ctx); bool IsMatch(C2SPacketHandlerContext ctx);
void Handle(ChatPacketHandlerContext ctx); void Handle(C2SPacketHandlerContext ctx);
} }
} }

View file

@ -1,12 +1,12 @@
namespace SharpChat { namespace SharpChat {
public class ChatPacketHandlerContext( public class C2SPacketHandlerContext(
string text, string text,
ChatContext chat, Context chat,
ChatConnection connection Connection connection
) { ) {
public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text)); public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text));
public ChatContext Chat { get; } = chat ?? throw new ArgumentNullException(nameof(chat)); public Context Chat { get; } = chat ?? throw new ArgumentNullException(nameof(chat));
public ChatConnection Connection { get; } = connection ?? throw new ArgumentNullException(nameof(connection)); public Connection Connection { get; } = 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

@ -5,20 +5,20 @@ using SharpChat.S2CPackets;
namespace SharpChat.C2SPacketHandlers { namespace SharpChat.C2SPacketHandlers {
public class AuthC2SPacketHandler( public class AuthC2SPacketHandler(
MisuzuClient msz, MisuzuClient msz,
ChatChannel defaultChannel, Channel defaultChannel,
CachedValue<int> maxMsgLength, CachedValue<int> maxMsgLength,
CachedValue<int> maxConns CachedValue<int> maxConns
) : C2SPacketHandler { ) : C2SPacketHandler {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
private readonly ChatChannel DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel)); private readonly Channel DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength)); private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
private readonly CachedValue<int> MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns)); private readonly CachedValue<int> MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns));
public bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(C2SPacketHandlerContext ctx) {
return ctx.CheckPacketId("1"); return ctx.CheckPacketId("1");
} }
public void Handle(ChatPacketHandlerContext ctx) { public void Handle(C2SPacketHandlerContext ctx) {
string[] args = ctx.SplitText(3); string[] args = ctx.SplitText(3);
string? authMethod = args.ElementAtOrDefault(1); string? authMethod = args.ElementAtOrDefault(1);
@ -88,10 +88,10 @@ namespace SharpChat.C2SPacketHandlers {
await ctx.Chat.ContextAccess.WaitAsync(); await ctx.Chat.ContextAccess.WaitAsync();
try { try {
ChatUser? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId); User? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId);
if(user == null) if(user == null)
user = new ChatUser( user = new User(
fai.UserId, fai.UserId,
fai.UserName ?? $"({fai.UserId})", fai.UserName ?? $"({fai.UserId})",
fai.Colour, fai.Colour,

View file

@ -8,11 +8,11 @@ namespace SharpChat.C2SPacketHandlers {
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 bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(C2SPacketHandlerContext ctx) {
return ctx.CheckPacketId("0"); return ctx.CheckPacketId("0");
} }
public void Handle(ChatPacketHandlerContext ctx) { public void Handle(C2SPacketHandlerContext ctx) {
string[] parts = ctx.SplitText(2); string[] parts = ctx.SplitText(2);
if(!int.TryParse(parts.FirstOrDefault(), out int pTime)) if(!int.TryParse(parts.FirstOrDefault(), out int pTime))
@ -25,7 +25,7 @@ namespace SharpChat.C2SPacketHandlers {
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 == UserStatus.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))];
if(bumpList.Length > 0) if(bumpList.Length > 0)

View file

@ -11,27 +11,27 @@ namespace SharpChat.C2SPacketHandlers {
) : C2SPacketHandler { ) : C2SPacketHandler {
private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength)); private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
private List<ChatCommand> Commands { get; } = []; private List<ClientCommand> Commands { get; } = [];
public void AddCommand(ChatCommand command) { public void AddCommand(ClientCommand command) {
Commands.Add(command ?? throw new ArgumentNullException(nameof(command))); Commands.Add(command ?? throw new ArgumentNullException(nameof(command)));
} }
public void AddCommands(IEnumerable<ChatCommand> commands) { public void AddCommands(IEnumerable<ClientCommand> commands) {
Commands.AddRange(commands ?? throw new ArgumentNullException(nameof(commands))); Commands.AddRange(commands ?? throw new ArgumentNullException(nameof(commands)));
} }
public bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(C2SPacketHandlerContext ctx) {
return ctx.CheckPacketId("2"); return ctx.CheckPacketId("2");
} }
public void Handle(ChatPacketHandlerContext ctx) { public void Handle(C2SPacketHandlerContext ctx) {
string[] args = ctx.SplitText(3); string[] args = ctx.SplitText(3);
ChatUser? user = ctx.Connection.User; User? user = ctx.Connection.User;
string? messageText = args.ElementAtOrDefault(2); string? messageText = args.ElementAtOrDefault(2);
if(user == null || !user.Can(ChatUserPermissions.SendMessage) || string.IsNullOrWhiteSpace(messageText)) if(user == null || !user.Can(UserPermissions.SendMessage) || string.IsNullOrWhiteSpace(messageText))
return; return;
// Extra validation step, not necessary at all but enforces proper formatting in SCv1. // Extra validation step, not necessary at all but enforces proper formatting in SCv1.
@ -40,12 +40,12 @@ namespace SharpChat.C2SPacketHandlers {
ctx.Chat.ContextAccess.Wait(); ctx.Chat.ContextAccess.Wait();
try { try {
if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel? channel) if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out Channel? channel)
&& (channel is null || !ctx.Chat.IsInChannel(user, channel))) && (channel is null || !ctx.Chat.IsInChannel(user, channel)))
return; return;
if(user.Status != ChatUserStatus.Online) if(user.Status != UserStatus.Online)
ctx.Chat.UpdateUser(user, status: ChatUserStatus.Online); ctx.Chat.UpdateUser(user, status: UserStatus.Online);
int maxMsgLength = MaxMessageLength; int maxMsgLength = MaxMessageLength;
StringInfo messageTextInfo = new(messageText); StringInfo messageTextInfo = new(messageText);
@ -60,8 +60,8 @@ namespace SharpChat.C2SPacketHandlers {
#endif #endif
if(messageText.StartsWith('/')) { if(messageText.StartsWith('/')) {
ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel); ClientCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
foreach(ChatCommand cmd in Commands) foreach(ClientCommand cmd in Commands)
if(cmd.IsMatch(context)) { if(cmd.IsMatch(context)) {
cmd.Dispatch(context); cmd.Dispatch(context);
return; return;

View file

@ -1,5 +1,5 @@
namespace SharpChat { namespace SharpChat {
public class ChatChannel( public class Channel(
string name, string name,
string password = "", string password = "",
bool isTemporary = false, bool isTemporary = false,
@ -19,7 +19,7 @@
return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase); return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
} }
public bool IsOwner(ChatUser user) { public bool IsOwner(User user) {
return OwnerId > 0 return OwnerId > 0
&& user != null && user != null
&& OwnerId == user.UserId; && OwnerId == user.UserId;

View file

@ -1,6 +0,0 @@
namespace SharpChat {
public interface ChatCommand {
bool IsMatch(ChatCommandContext ctx);
void Dispatch(ChatCommandContext ctx);
}
}

View file

@ -0,0 +1,6 @@
namespace SharpChat {
public interface ClientCommand {
bool IsMatch(ClientCommandContext ctx);
void Dispatch(ClientCommandContext ctx);
}
}

View file

@ -1,18 +1,18 @@
namespace SharpChat { namespace SharpChat {
public class ChatCommandContext { public class ClientCommandContext {
public string Name { get; } public string Name { get; }
public string[] Args { get; } public string[] Args { get; }
public ChatContext Chat { get; } public Context Chat { get; }
public ChatUser User { get; } public User User { get; }
public ChatConnection Connection { get; } public Connection Connection { get; }
public ChatChannel Channel { get; } public Channel Channel { get; }
public ChatCommandContext( public ClientCommandContext(
string text, string text,
ChatContext chat, Context chat,
ChatUser user, User user,
ChatConnection connection, Connection connection,
ChatChannel channel Channel channel
) { ) {
ArgumentNullException.ThrowIfNull(text); ArgumentNullException.ThrowIfNull(text);
@ -26,13 +26,13 @@
Args = [.. parts.Skip(1)]; Args = [.. parts.Skip(1)];
} }
public ChatCommandContext( public ClientCommandContext(
string name, string name,
string[] args, string[] args,
ChatContext chat, Context chat,
ChatUser user, User user,
ChatConnection connection, Connection connection,
ChatChannel channel Channel channel
) { ) {
Name = name ?? throw new ArgumentNullException(nameof(name)); Name = name ?? throw new ArgumentNullException(nameof(name));
Args = args ?? throw new ArgumentNullException(nameof(args)); Args = args ?? throw new ArgumentNullException(nameof(args));

View file

@ -1,17 +1,17 @@
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class AFKCommand : ChatCommand { public class AFKClientCommand : ClientCommand {
private const string DEFAULT = "AFK"; private const string DEFAULT = "AFK";
public const int MAX_GRAPHEMES = 5; public const int MAX_GRAPHEMES = 5;
public const int MAX_BYTES = MAX_GRAPHEMES * 10; public const int MAX_BYTES = MAX_GRAPHEMES * 10;
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("afk"); return ctx.NameEquals("afk");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
string? statusText = ctx.Args.FirstOrDefault(); string? statusText = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(statusText)) if(string.IsNullOrWhiteSpace(statusText))
statusText = DEFAULT; statusText = DEFAULT;
@ -26,7 +26,7 @@ namespace SharpChat.Commands {
ctx.Chat.UpdateUser( ctx.Chat.UpdateUser(
ctx.User, ctx.User,
status: ChatUserStatus.Away, status: UserStatus.Away,
statusText: statusText statusText: statusText
); );
} }

View file

@ -1,13 +1,13 @@
using SharpChat.Events; using SharpChat.Events;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class ActionCommand : ChatCommand { public class ActionClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("action") return ctx.NameEquals("action")
|| ctx.NameEquals("me"); || ctx.NameEquals("me");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
if(ctx.Args.Length < 1) if(ctx.Args.Length < 1)
return; return;

View file

@ -1,19 +1,19 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class BanListCommand(MisuzuClient msz) : ChatCommand { public class BanListClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("bans") return ctx.NameEquals("bans")
|| ctx.NameEquals("banned"); || ctx.NameEquals("banned");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) { if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -1,17 +1,17 @@
using SharpChat.Events; using SharpChat.Events;
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class BroadcastCommand : ChatCommand { public class BroadcastClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("say") return ctx.NameEquals("say")
|| ctx.NameEquals("broadcast"); || ctx.NameEquals("broadcast");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.Broadcast)) { if(!ctx.User.Can(UserPermissions.Broadcast)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -1,15 +1,15 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class CreateChannelCommand : ChatCommand { public class CreateChannelClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("create"); return ctx.NameEquals("create");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.CreateChannel)) { if(!ctx.User.Can(UserPermissions.CreateChannel)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -34,7 +34,7 @@ namespace SharpChat.Commands {
string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0)); string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
if(!ChatChannel.CheckName(createChanName)) { if(!Channel.CheckName(createChanName)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NAME_INVALID)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NAME_INVALID));
return; return;
} }
@ -44,15 +44,15 @@ namespace SharpChat.Commands {
return; return;
} }
ChatChannel createChan = new( Channel createChan = new(
createChanName, createChanName,
isTemporary: !ctx.User.Can(ChatUserPermissions.SetChannelPermanent), isTemporary: !ctx.User.Can(UserPermissions.SetChannelPermanent),
rank: createChanHierarchy, rank: createChanHierarchy,
ownerId: ctx.User.UserId ownerId: ctx.User.UserId
); );
ctx.Chat.Channels.Add(createChan); ctx.Chat.Channels.Add(createChan);
foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank)) foreach(User ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(createChan.Name, createChan.HasPassword, createChan.IsTemporary)); ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(createChan.Name, createChan.HasPassword, createChan.IsTemporary));
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password); ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);

View file

@ -1,15 +1,15 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class DeleteChannelCommand : ChatCommand { public class DeleteChannelClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("delchan") || ( return ctx.NameEquals("delchan") || (
ctx.NameEquals("delete") ctx.NameEquals("delete")
&& ctx.Args.FirstOrDefault()?.All(char.IsDigit) == false && ctx.Args.FirstOrDefault()?.All(char.IsDigit) == false
); );
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(ctx.Args.Length < 1 || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) { if(ctx.Args.Length < 1 || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) {
@ -18,14 +18,14 @@ namespace SharpChat.Commands {
} }
string delChanName = string.Join('_', ctx.Args); string delChanName = string.Join('_', ctx.Args);
ChatChannel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName)); Channel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
if(delChan == null) { if(delChan == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
return; return;
} }
if(!ctx.User.Can(ChatUserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) { if(!ctx.User.Can(UserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETE_FAILED, true, delChan.Name)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
return; return;
} }

View file

@ -1,21 +1,21 @@
using SharpChat.EventStorage; using SharpChat.EventStorage;
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands namespace SharpChat.ClientCommands
{ {
public class DeleteMessageCommand : ChatCommand { public class DeleteMessageClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("delmsg") || ( return ctx.NameEquals("delmsg") || (
ctx.NameEquals("delete") ctx.NameEquals("delete")
&& ctx.Args.FirstOrDefault()?.All(char.IsDigit) == true && ctx.Args.FirstOrDefault()?.All(char.IsDigit) == true
); );
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
bool deleteAnyMessage = ctx.User.Can(ChatUserPermissions.DeleteAnyMessage); bool deleteAnyMessage = ctx.User.Can(UserPermissions.DeleteAnyMessage);
if(!deleteAnyMessage && !ctx.User.Can(ChatUserPermissions.DeleteOwnMessage)) { if(!deleteAnyMessage && !ctx.User.Can(UserPermissions.DeleteOwnMessage)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -1,15 +1,15 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class JoinChannelCommand : ChatCommand { public class JoinChannelClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("join"); return ctx.NameEquals("join");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel"; string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel";
ChatChannel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr)); Channel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
if(joinChan is null) { if(joinChan is null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));

View file

@ -1,20 +1,20 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class KickBanCommand(MisuzuClient msz) : ChatCommand { public class KickBanClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("kick") return ctx.NameEquals("kick")
|| ctx.NameEquals("ban"); || ctx.NameEquals("ban");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
bool isBanning = ctx.NameEquals("ban"); bool isBanning = ctx.NameEquals("ban");
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(isBanning ? ChatUserPermissions.BanUser : ChatUserPermissions.KickUser)) { if(!ctx.User.Can(isBanning ? UserPermissions.BanUser : UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -22,7 +22,7 @@ namespace SharpChat.Commands {
string? banUserTarget = ctx.Args.ElementAtOrDefault(0); string? banUserTarget = ctx.Args.ElementAtOrDefault(0);
string? banDurationStr = ctx.Args.ElementAtOrDefault(1); string? banDurationStr = ctx.Args.ElementAtOrDefault(1);
int banReasonIndex = 1; int banReasonIndex = 1;
ChatUser? banUser = null; User? banUser = null;
if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) { if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, banUserTarget ?? "User")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, banUserTarget ?? "User"));

View file

@ -2,25 +2,25 @@
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class NickCommand : ChatCommand { public class NickClientCommand : ClientCommand {
private const int MAX_GRAPHEMES = 16; private const int MAX_GRAPHEMES = 16;
private const int MAX_BYTES = MAX_GRAPHEMES * 10; private const int MAX_BYTES = MAX_GRAPHEMES * 10;
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("nick"); return ctx.NameEquals("nick");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
bool setOthersNick = ctx.User.Can(ChatUserPermissions.SetOthersNickname); bool setOthersNick = ctx.User.Can(UserPermissions.SetOthersNickname);
if(!setOthersNick && !ctx.User.Can(ChatUserPermissions.SetOwnNickname)) { if(!setOthersNick && !ctx.User.Can(UserPermissions.SetOwnNickname)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
ChatUser? targetUser = null; User? targetUser = null;
int offset = 0; int offset = 0;
if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) { if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {

View file

@ -2,19 +2,19 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
using System.Net; using System.Net;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class PardonAddressCommand(MisuzuClient msz) : ChatCommand { public class PardonAddressClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pardonip") return ctx.NameEquals("pardonip")
|| ctx.NameEquals("unbanip"); || ctx.NameEquals("unbanip");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) { if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -1,19 +1,19 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class PardonUserCommand(MisuzuClient msz) : ChatCommand { public class PardonUserClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pardon") return ctx.NameEquals("pardon")
|| ctx.NameEquals("unban"); || ctx.NameEquals("unban");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) { if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -25,7 +25,7 @@ namespace SharpChat.Commands {
return; return;
} }
ChatUser? unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget)); User? unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) { if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
unbanUserTargetIsName = false; unbanUserTargetIsName = false;
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId); unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId);

View file

@ -1,16 +1,16 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class PasswordChannelCommand : ChatCommand { public class PasswordChannelClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pwd") return ctx.NameEquals("pwd")
|| ctx.NameEquals("password"); || ctx.NameEquals("password");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) { if(!ctx.User.Can(UserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -1,17 +1,17 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class RankChannelCommand : ChatCommand { public class RankChannelClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("rank") return ctx.NameEquals("rank")
|| ctx.NameEquals("privilege") || ctx.NameEquals("privilege")
|| ctx.NameEquals("priv"); || ctx.NameEquals("priv");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) { if(!ctx.User.Can(UserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -1,23 +1,23 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
using System.Net; using System.Net;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class RemoteAddressCommand : ChatCommand { public class RemoteAddressClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("ip") return ctx.NameEquals("ip")
|| ctx.NameEquals("whois"); || ctx.NameEquals("whois");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.SeeIPAddress)) { if(!ctx.User.Can(UserPermissions.SeeIPAddress)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, "/ip")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, "/ip"));
return; return;
} }
string? ipUserStr = ctx.Args.FirstOrDefault(); string? ipUserStr = ctx.Args.FirstOrDefault();
ChatUser? ipUser = null; User? ipUser = null;
if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) { if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, ipUserStr ?? "User")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));

View file

@ -1,16 +1,16 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class ShutdownRestartCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : ChatCommand { public class ShutdownRestartClientCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : ClientCommand {
private readonly ManualResetEvent WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle)); private readonly ManualResetEvent WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle));
private readonly Func<bool> ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck)); private readonly Func<bool> ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck));
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("shutdown") return ctx.NameEquals("shutdown")
|| ctx.NameEquals("restart"); || ctx.NameEquals("restart");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
if(ctx.User.UserId != 1) { if(ctx.User.UserId != 1) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
@ -21,7 +21,7 @@ namespace SharpChat.Commands {
return; return;
if(ctx.NameEquals("restart")) if(ctx.NameEquals("restart"))
foreach(ChatConnection conn in ctx.Chat.Connections) foreach(Connection conn in ctx.Chat.Connections)
conn.PrepareForRestart(); conn.PrepareForRestart();
ctx.Chat.Update(); ctx.Chat.Update();

View file

@ -1,14 +1,14 @@
using SharpChat.Events; using SharpChat.Events;
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class WhisperCommand : ChatCommand { public class WhisperClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("whisper") return ctx.NameEquals("whisper")
|| ctx.NameEquals("msg"); || ctx.NameEquals("msg");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
if(ctx.Args.Length < 2) { if(ctx.Args.Length < 2) {
@ -17,7 +17,7 @@ namespace SharpChat.Commands {
} }
string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty; string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty;
ChatUser? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr)); User? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
if(whisperUser == null) { if(whisperUser == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, whisperUserStr)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, whisperUserStr));
@ -29,7 +29,7 @@ namespace SharpChat.Commands {
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
msgId, msgId,
ChatUser.GetDMChannelName(ctx.User, whisperUser), User.GetDMChannelName(ctx.User, whisperUser),
ctx.User.UserId, ctx.User.UserId,
ctx.User.UserName, ctx.User.UserName,
ctx.User.Colour, ctx.User.Colour,

View file

@ -1,19 +1,19 @@
using SharpChat.S2CPackets; using SharpChat.S2CPackets;
using System.Text; using System.Text;
namespace SharpChat.Commands { namespace SharpChat.ClientCommands {
public class WhoCommand : ChatCommand { public class WhoClientCommand : ClientCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("who"); return ctx.NameEquals("who");
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next(); long msgId = ctx.Chat.RandomSnowflake.Next();
StringBuilder whoChanSB = new(); StringBuilder whoChanSB = new();
string? whoChanStr = ctx.Args.FirstOrDefault(); string? whoChanStr = ctx.Args.FirstOrDefault();
if(string.IsNullOrEmpty(whoChanStr)) { if(string.IsNullOrEmpty(whoChanStr)) {
foreach(ChatUser whoUser in ctx.Chat.Users) { foreach(User whoUser in ctx.Chat.Users) {
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);"""); whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
if(whoUser == ctx.User) if(whoUser == ctx.User)
@ -29,19 +29,19 @@ namespace SharpChat.Commands {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB));
} else { } else {
ChatChannel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr)); Channel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
if(whoChan is null) { if(whoChan is null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
return; return;
} }
if(whoChan.Rank > ctx.User.Rank || (whoChan.HasPassword && !ctx.User.Can(ChatUserPermissions.JoinAnyChannel))) { if(whoChan.Rank > ctx.User.Rank || (whoChan.HasPassword && !ctx.User.Can(UserPermissions.JoinAnyChannel))) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_ERROR, true, whoChanStr)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_ERROR, true, whoChanStr));
return; return;
} }
foreach(ChatUser whoUser in ctx.Chat.GetChannelUsers(whoChan)) { foreach(User whoUser in ctx.Chat.GetChannelUsers(whoChan)) {
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);"""); whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
if(whoUser == ctx.User) if(whoUser == ctx.User)

View file

@ -2,7 +2,7 @@
using System.Net; using System.Net;
namespace SharpChat { namespace SharpChat {
public class ChatConnection : IDisposable { public class Connection : IDisposable {
public const int ID_LENGTH = 20; public const int ID_LENGTH = 20;
#if DEBUG #if DEBUG
@ -16,7 +16,7 @@ namespace SharpChat {
public string Id { get; } public string Id { get; }
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
public DateTimeOffset LastPing { get; set; } = DateTimeOffset.Now; public DateTimeOffset LastPing { get; set; } = DateTimeOffset.Now;
public ChatUser? User { get; set; } public User? User { get; set; }
private int CloseCode { get; set; } = 1000; private int CloseCode { get; set; } = 1000;
@ -25,7 +25,7 @@ namespace SharpChat {
public bool IsAlive => !IsDisposed && !HasTimedOut; public bool IsAlive => !IsDisposed && !HasTimedOut;
public ChatConnection(IWebSocketConnection sock) { public Connection(IWebSocketConnection sock) {
Socket = sock; Socket = sock;
Id = RNG.SecureRandomString(ID_LENGTH); Id = RNG.SecureRandomString(ID_LENGTH);
@ -61,7 +61,7 @@ namespace SharpChat {
CloseCode = 1012; CloseCode = 1012;
} }
~ChatConnection() { ~Connection() {
DoDispose(); DoDispose();
} }

View file

@ -5,7 +5,7 @@ using SharpChat.Snowflake;
using System.Net; using System.Net;
namespace SharpChat { namespace SharpChat {
public class ChatContext { public class Context {
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);
@ -13,15 +13,15 @@ namespace SharpChat {
public SnowflakeGenerator SnowflakeGenerator { get; } = new(); public SnowflakeGenerator SnowflakeGenerator { get; } = new();
public RandomSnowflake RandomSnowflake { get; } public RandomSnowflake RandomSnowflake { get; }
public HashSet<ChatChannel> Channels { get; } = []; public HashSet<Channel> Channels { get; } = [];
public HashSet<ChatConnection> Connections { get; } = []; public HashSet<Connection> Connections { get; } = [];
public HashSet<ChatUser> Users { get; } = []; public HashSet<User> Users { get; } = [];
public EventStorage.EventStorage Events { get; } public EventStorage.EventStorage Events { get; }
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = []; public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
public Dictionary<long, RateLimiter> UserRateLimiters { get; } = []; public Dictionary<long, RateLimiter> UserRateLimiters { get; } = [];
public Dictionary<long, ChatChannel> UserLastChannel { get; } = []; public Dictionary<long, Channel> UserLastChannel { get; } = [];
public ChatContext(EventStorage.EventStorage evtStore) { public Context(EventStorage.EventStorage evtStore) {
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore)); Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
RandomSnowflake = new(SnowflakeGenerator); RandomSnowflake = new(SnowflakeGenerator);
} }
@ -42,12 +42,12 @@ namespace SharpChat {
if(uids.Count() != 2) if(uids.Count() != 2)
return; return;
IEnumerable<ChatUser> users = Users.Where(u => uids.Any(uid => uid == u.UserId)); IEnumerable<User> users = Users.Where(u => uids.Any(uid => uid == u.UserId));
ChatUser? target = users.FirstOrDefault(u => u.UserId != mce.SenderId); User? target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
if(target == null) if(target == null)
return; return;
foreach(ChatUser user in users) foreach(User user in users)
SendTo(user, new ChatMessageAddS2CPacket( SendTo(user, new ChatMessageAddS2CPacket(
mce.MessageId, mce.MessageId,
DateTimeOffset.Now, DateTimeOffset.Now,
@ -57,7 +57,7 @@ namespace SharpChat {
true true
)); ));
} else { } else {
ChatChannel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName)); Channel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
if(channel is not null) if(channel is not null)
SendTo(channel, new ChatMessageAddS2CPacket( SendTo(channel, new ChatMessageAddS2CPacket(
mce.MessageId, mce.MessageId,
@ -83,7 +83,7 @@ namespace SharpChat {
} }
public void Update() { public void Update() {
foreach(ChatConnection conn in Connections) foreach(Connection conn in Connections)
if(!conn.IsDisposed && conn.HasTimedOut) { if(!conn.IsDisposed && conn.HasTimedOut) {
conn.Dispose(); conn.Dispose();
Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}."); Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
@ -91,7 +91,7 @@ namespace SharpChat {
Connections.RemoveWhere(conn => conn.IsDisposed); Connections.RemoveWhere(conn => conn.IsDisposed);
foreach(ChatUser user in Users) foreach(User user in Users)
if(!Connections.Any(conn => conn.User == user)) { if(!Connections.Any(conn => conn.User == user)) {
HandleDisconnect(user, UserDisconnectS2CPacket.Reason.TimeOut); HandleDisconnect(user, UserDisconnectS2CPacket.Reason.TimeOut);
Logger.Write($"Timed out {user} (no more connections)."); Logger.Write($"Timed out {user} (no more connections).");
@ -107,37 +107,37 @@ namespace SharpChat {
} }
} }
public bool IsInChannel(ChatUser user, ChatChannel channel) { public bool IsInChannel(User user, Channel channel) {
return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name)); return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name));
} }
public string[] GetUserChannelNames(ChatUser user) { public string[] GetUserChannelNames(User user) {
return [.. ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName)]; return [.. ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName)];
} }
public ChatChannel[] GetUserChannels(ChatUser user) { public Channel[] GetUserChannels(User user) {
string[] names = GetUserChannelNames(user); string[] names = GetUserChannelNames(user);
return [.. Channels.Where(c => names.Any(n => c.NameEquals(n)))]; return [.. Channels.Where(c => names.Any(n => c.NameEquals(n)))];
} }
public long[] GetChannelUserIds(ChatChannel channel) { public long[] GetChannelUserIds(Channel channel) {
return [.. ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId)]; return [.. ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId)];
} }
public ChatUser[] GetChannelUsers(ChatChannel channel) { public User[] GetChannelUsers(Channel channel) {
long[] ids = GetChannelUserIds(channel); long[] ids = GetChannelUserIds(channel);
return [.. Users.Where(u => ids.Contains(u.UserId))]; return [.. Users.Where(u => ids.Contains(u.UserId))];
} }
public void UpdateUser( public void UpdateUser(
ChatUser user, User user,
string? userName = null, string? userName = null,
string? nickName = null, string? nickName = null,
ChatColour? colour = null, Colour? colour = null,
ChatUserStatus? status = null, UserStatus? status = null,
string? statusText = null, string? statusText = null,
int? rank = null, int? rank = null,
ChatUserPermissions? perms = null, UserPermissions? perms = null,
bool silent = false bool silent = false
) { ) {
ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(user);
@ -191,14 +191,14 @@ namespace SharpChat {
} }
} }
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Kicked) { public void BanUser(User user, TimeSpan duration, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Kicked) {
if(duration > TimeSpan.Zero) { if(duration > TimeSpan.Zero) {
DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration; DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration;
SendTo(user, new ForceDisconnectS2CPacket(expires)); SendTo(user, new ForceDisconnectS2CPacket(expires));
} else } else
SendTo(user, new ForceDisconnectS2CPacket()); SendTo(user, new ForceDisconnectS2CPacket());
foreach(ChatConnection conn in Connections) foreach(Connection conn in Connections)
if(conn.User == user) if(conn.User == user)
conn.Dispose(); conn.Dispose();
Connections.RemoveWhere(conn => conn.IsDisposed); Connections.RemoveWhere(conn => conn.IsDisposed);
@ -206,7 +206,7 @@ namespace SharpChat {
HandleDisconnect(user, reason); HandleDisconnect(user, reason);
} }
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) { public void HandleJoin(User user, Channel chan, Connection conn, int maxMsgLength) {
if(!IsInChannel(user, chan)) { if(!IsInChannel(user, chan)) {
long msgId = RandomSnowflake.Next(); long msgId = RandomSnowflake.Next();
SendTo(chan, new UserConnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions)); SendTo(chan, new UserConnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
@ -248,14 +248,14 @@ namespace SharpChat {
UserLastChannel[user.UserId] = chan; UserLastChannel[user.UserId] = chan;
} }
public void HandleDisconnect(ChatUser user, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Leave) { public void HandleDisconnect(User user, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Leave) {
UpdateUser(user, status: ChatUserStatus.Offline); UpdateUser(user, status: UserStatus.Offline);
Users.Remove(user); Users.Remove(user);
UserLastChannel.Remove(user.UserId); UserLastChannel.Remove(user.UserId);
ChatChannel[] channels = GetUserChannels(user); Channel[] channels = GetUserChannels(user);
foreach(ChatChannel chan in channels) { foreach(Channel chan in channels) {
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name)); ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
long msgId = RandomSnowflake.Next(); long msgId = RandomSnowflake.Next();
@ -267,13 +267,13 @@ namespace SharpChat {
} }
} }
public void SwitchChannel(ChatUser user, ChatChannel chan, string password) { public void SwitchChannel(User user, Channel chan, string password) {
if(UserLastChannel.TryGetValue(user.UserId, out ChatChannel? ulc) && chan == ulc) { if(UserLastChannel.TryGetValue(user.UserId, out Channel? ulc) && chan == ulc) {
ForceChannel(user); ForceChannel(user);
return; return;
} }
if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.IsOwner(user)) { if(!user.Can(UserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
if(chan.Rank > user.Rank) { if(chan.Rank > user.Rank) {
SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name)); SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
ForceChannel(user); ForceChannel(user);
@ -290,11 +290,11 @@ namespace SharpChat {
ForceChannelSwitch(user, chan); ForceChannelSwitch(user, chan);
} }
public void ForceChannelSwitch(ChatUser user, ChatChannel chan) { public void ForceChannelSwitch(User user, Channel chan) {
if(!Channels.Contains(chan)) if(!Channels.Contains(chan))
return; return;
ChatChannel oldChan = UserLastChannel[user.UserId]; Channel oldChan = UserLastChannel[user.UserId];
long leaveId = RandomSnowflake.Next(); long leaveId = RandomSnowflake.Next();
SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user.UserId)); SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user.UserId));
@ -333,45 +333,45 @@ namespace SharpChat {
public void Send(S2CPacket packet) { public void Send(S2CPacket packet) {
ArgumentNullException.ThrowIfNull(packet); ArgumentNullException.ThrowIfNull(packet);
foreach(ChatConnection conn in Connections) foreach(Connection conn in Connections)
if(conn.IsAlive && conn.User is not null) if(conn.IsAlive && conn.User is not null)
conn.Send(packet); conn.Send(packet);
} }
public void SendTo(ChatUser user, S2CPacket packet) { public void SendTo(User user, S2CPacket packet) {
ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(user);
ArgumentNullException.ThrowIfNull(packet); ArgumentNullException.ThrowIfNull(packet);
foreach(ChatConnection conn in Connections) foreach(Connection conn in Connections)
if(conn.IsAlive && conn.User == user) if(conn.IsAlive && conn.User == user)
conn.Send(packet); conn.Send(packet);
} }
public void SendTo(ChatChannel channel, S2CPacket packet) { public void SendTo(Channel channel, S2CPacket packet) {
ArgumentNullException.ThrowIfNull(channel); ArgumentNullException.ThrowIfNull(channel);
ArgumentNullException.ThrowIfNull(packet); ArgumentNullException.ThrowIfNull(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.IsAlive && c.User is not null && IsInChannel(c.User, channel)); IEnumerable<Connection> conns = Connections.Where(c => c.IsAlive && c.User is not null && IsInChannel(c.User, channel));
foreach(ChatConnection conn in conns) foreach(Connection conn in conns)
conn.Send(packet); conn.Send(packet);
} }
public void SendToUserChannels(ChatUser user, S2CPacket packet) { public void SendToUserChannels(User user, S2CPacket packet) {
ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(user);
ArgumentNullException.ThrowIfNull(packet); ArgumentNullException.ThrowIfNull(packet);
IEnumerable<ChatChannel> chans = Channels.Where(c => IsInChannel(user, c)); IEnumerable<Channel> chans = Channels.Where(c => IsInChannel(user, c));
IEnumerable<ChatConnection> conns = Connections.Where(conn => conn.IsAlive && conn.User is not null && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName)))); IEnumerable<Connection> conns = Connections.Where(conn => conn.IsAlive && conn.User is not null && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
foreach(ChatConnection conn in conns) foreach(Connection conn in conns)
conn.Send(packet); conn.Send(packet);
} }
public IPAddress[] GetRemoteAddresses(ChatUser user) { public IPAddress[] GetRemoteAddresses(User user) {
return [.. Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct()]; 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(User user, Channel? chan = null) {
ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(user);
if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan)) if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
@ -380,7 +380,7 @@ namespace SharpChat {
SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name)); SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
} }
public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string? password = null) { public void UpdateChannel(Channel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
ArgumentNullException.ThrowIfNull(channel); ArgumentNullException.ThrowIfNull(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));
@ -395,15 +395,15 @@ namespace SharpChat {
channel.Password = password; channel.Password = password;
// TODO: Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively // TODO: Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
SendTo(user, new ChannelUpdateS2CPacket(channel.Name, channel.Name, channel.HasPassword, channel.IsTemporary)); SendTo(user, new ChannelUpdateS2CPacket(channel.Name, channel.Name, channel.HasPassword, channel.IsTemporary));
} }
public void RemoveChannel(ChatChannel channel) { public void RemoveChannel(Channel channel) {
if(channel == null || Channels.Count < 1) if(channel == null || Channels.Count < 1)
return; return;
ChatChannel? defaultChannel = Channels.FirstOrDefault(); Channel? defaultChannel = Channels.FirstOrDefault();
if(defaultChannel is null) if(defaultChannel is null)
return; return;
@ -412,11 +412,11 @@ namespace SharpChat {
// Move all users back to the main channel // Move all users back to the main channel
// TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel. // TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
foreach(ChatUser user in GetChannelUsers(channel)) foreach(User user in GetChannelUsers(channel))
SwitchChannel(user, defaultChannel, string.Empty); SwitchChannel(user, defaultChannel, string.Empty);
// Broadcast deletion of channel // Broadcast deletion of channel
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
SendTo(user, new ChannelDeleteS2CPacket(channel.Name)); SendTo(user, new ChannelDeleteS2CPacket(channel.Name));
} }
} }

View file

@ -6,10 +6,10 @@
string channelName, string channelName,
long senderId, long senderId,
string senderName, string senderName,
ChatColour senderColour, Colour senderColour,
int senderRank, int senderRank,
string senderNick, string senderNick,
ChatUserPermissions senderPerms, UserPermissions senderPerms,
object? data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
); );

View file

@ -12,10 +12,10 @@ namespace SharpChat.EventStorage {
string channelName, string channelName,
long senderId, long senderId,
string senderName, string senderName,
ChatColour senderColour, Colour senderColour,
int senderRank, int senderRank,
string senderNick, string senderNick,
ChatUserPermissions senderPerms, UserPermissions senderPerms,
object? data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
@ -71,12 +71,12 @@ namespace SharpChat.EventStorage {
return new StoredEventInfo( return new StoredEventInfo(
reader.GetInt64("event_id"), reader.GetInt64("event_id"),
Encoding.ASCII.GetString((byte[])reader["event_type"]), Encoding.ASCII.GetString((byte[])reader["event_type"]),
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : new ChatUser( reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : new User(
reader.GetInt64("event_sender"), reader.GetInt64("event_sender"),
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"), reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), Colour.FromMisuzu(reader.GetInt32("event_sender_colour")),
reader.GetInt32("event_sender_rank"), reader.GetInt32("event_sender_rank"),
(ChatUserPermissions)reader.GetInt32("event_sender_perms"), (UserPermissions)reader.GetInt32("event_sender_perms"),
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick") reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick")
), ),
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")), DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),

View file

@ -4,7 +4,7 @@ namespace SharpChat.EventStorage {
public class StoredEventInfo( public class StoredEventInfo(
long id, long id,
string type, string type,
ChatUser? sender, User? sender,
DateTimeOffset created, DateTimeOffset created,
DateTimeOffset? deleted, DateTimeOffset? deleted,
string? channelName, string? channelName,
@ -13,7 +13,7 @@ namespace SharpChat.EventStorage {
) { ) {
public long Id { get; set; } = id; public long Id { get; set; } = id;
public string Type { get; set; } = type; public string Type { get; set; } = type;
public ChatUser? Sender { get; set; } = sender; public User? Sender { get; set; } = sender;
public DateTimeOffset Created { get; set; } = created; public DateTimeOffset Created { get; set; } = created;
public DateTimeOffset? Deleted { get; set; } = deleted; public DateTimeOffset? Deleted { get; set; } = deleted;
public string? ChannelName { get; set; } = channelName; public string? ChannelName { get; set; } = channelName;

View file

@ -10,10 +10,10 @@ namespace SharpChat.EventStorage {
string channelName, string channelName,
long senderId, long senderId,
string senderName, string senderName,
ChatColour senderColour, Colour senderColour,
int senderRank, int senderRank,
string senderNick, string senderNick,
ChatUserPermissions senderPerms, UserPermissions senderPerms,
object? data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
@ -21,7 +21,7 @@ namespace SharpChat.EventStorage {
// 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));
Events.Add(id, new(id, type, senderId < 1 ? null : new ChatUser( Events.Add(id, new(id, type, senderId < 1 ? null : new User(
senderId, senderId,
senderName, senderName,
senderColour, senderColour,

View file

@ -4,10 +4,10 @@
string channelName, string channelName,
long senderId, long senderId,
string senderName, string senderName,
ChatColour senderColour, Colour senderColour,
int senderRank, int senderRank,
string senderNickName, string senderNickName,
ChatUserPermissions senderPerms, UserPermissions senderPerms,
DateTimeOffset msgCreated, DateTimeOffset msgCreated,
string msgText, string msgText,
bool isPrivate, bool isPrivate,
@ -18,10 +18,10 @@
public string ChannelName { get; } = channelName; public string ChannelName { get; } = channelName;
public long SenderId { get; } = senderId; public long SenderId { get; } = senderId;
public string SenderName { get; } = senderName; public string SenderName { get; } = senderName;
public ChatColour SenderColour { get; } = senderColour; public Colour SenderColour { get; } = senderColour;
public int SenderRank { get; } = senderRank; public int SenderRank { get; } = senderRank;
public string SenderNickName { get; } = senderNickName; public string SenderNickName { get; } = senderNickName;
public ChatUserPermissions SenderPerms { get; } = senderPerms; public UserPermissions SenderPerms { get; } = senderPerms;
public DateTimeOffset MessageCreated { get; } = msgCreated; public DateTimeOffset MessageCreated { get; } = msgCreated;
public string MessageText { get; } = msgText; public string MessageText { get; } = msgText;
public bool IsPrivate { get; } = isPrivate; public bool IsPrivate { get; } = isPrivate;

View file

@ -17,12 +17,12 @@ namespace SharpChat.Misuzu {
[JsonPropertyName("colour_raw")] [JsonPropertyName("colour_raw")]
public int ColourRaw { get; set; } public int ColourRaw { get; set; }
public ChatColour Colour => ChatColour.FromMisuzu(ColourRaw); public Colour Colour => Colour.FromMisuzu(ColourRaw);
[JsonPropertyName("hierarchy")] [JsonPropertyName("hierarchy")]
public int Rank { get; set; } public int Rank { get; set; }
[JsonPropertyName("perms")] [JsonPropertyName("perms")]
public ChatUserPermissions Permissions { get; set; } public UserPermissions Permissions { get; set; }
} }
} }

View file

@ -26,6 +26,6 @@ namespace SharpChat.Misuzu {
public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt; public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
public ChatColour UserColour => ChatColour.FromMisuzu(UserColourRaw); public Colour UserColour => Colour.FromMisuzu(UserColourRaw);
} }
} }

View file

@ -4,9 +4,9 @@ namespace SharpChat.S2CPackets {
public class AuthSuccessS2CPacket( public class AuthSuccessS2CPacket(
long userId, long userId,
string userName, string userName,
ChatColour userColour, Colour userColour,
int userRank, int userRank,
ChatUserPermissions userPerms, UserPermissions userPerms,
string channelName, string channelName,
int maxMsgLength int maxMsgLength
) : S2CPacket { ) : S2CPacket {
@ -22,13 +22,13 @@ namespace SharpChat.S2CPackets {
sb.Append('\t'); sb.Append('\t');
sb.Append(userRank); sb.Append(userRank);
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.KickUser) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.CreateChannel) ? (userPerms.HasFlag(ChatUserPermissions.SetChannelPermanent) ? '2' : '1') : '0'); sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
sb.Append('\t'); sb.Append('\t');
sb.Append(channelName); sb.Append(channelName);
sb.Append('\t'); sb.Append('\t');

View file

@ -31,13 +31,13 @@ namespace SharpChat.S2CPackets
sb.Append('\t'); sb.Append('\t');
sb.Append(Event.Sender.Rank); sb.Append(Event.Sender.Rank);
sb.Append(' '); sb.Append(' ');
sb.Append(Event.Sender.Permissions.HasFlag(ChatUserPermissions.KickUser) ? '1' : '0'); sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.KickUser) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(Event.Sender.Permissions.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0'); sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(Event.Sender.Permissions.HasFlag(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(Event.Sender.Permissions.HasFlag(ChatUserPermissions.CreateChannel) ? (Event.Sender.Permissions.HasFlag(ChatUserPermissions.SetChannelPermanent) ? '2' : '1') : '0'); sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.CreateChannel) ? (Event.Sender.Permissions.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
sb.Append('\t'); sb.Append('\t');
} }

View file

@ -2,7 +2,7 @@
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public class ContextUsersS2CPacket(IEnumerable<ContextUsersS2CPacket.Entry> entries) : S2CPacket { public class ContextUsersS2CPacket(IEnumerable<ContextUsersS2CPacket.Entry> entries) : S2CPacket {
public record Entry(long id, string name, ChatColour colour, int rank, ChatUserPermissions perms, bool visible); public record Entry(long id, string name, Colour colour, int rank, UserPermissions perms, bool visible);
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -20,13 +20,13 @@ namespace SharpChat.S2CPackets {
sb.Append('\t'); sb.Append('\t');
sb.Append(entry.rank); sb.Append(entry.rank);
sb.Append(' '); sb.Append(' ');
sb.Append(entry.perms.HasFlag(ChatUserPermissions.KickUser) ? '1' : '0'); sb.Append(entry.perms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(entry.perms.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0'); sb.Append(entry.perms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(entry.perms.HasFlag(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(entry.perms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(entry.perms.HasFlag(ChatUserPermissions.CreateChannel) ? (entry.perms.HasFlag(ChatUserPermissions.SetChannelPermanent) ? '2' : '1') : '0'); sb.Append(entry.perms.HasFlag(UserPermissions.CreateChannel) ? (entry.perms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
sb.Append('\t'); sb.Append('\t');
sb.Append(entry.visible ? '1' : '0'); sb.Append(entry.visible ? '1' : '0');
} }

View file

@ -5,9 +5,9 @@ namespace SharpChat.S2CPackets {
long msgId, long msgId,
long userId, long userId,
string userName, string userName,
ChatColour userColour, Colour userColour,
int userRank, int userRank,
ChatUserPermissions userPerms UserPermissions userPerms
) : S2CPacket { ) : S2CPacket {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -21,13 +21,13 @@ namespace SharpChat.S2CPackets {
sb.Append('\t'); sb.Append('\t');
sb.Append(userRank); sb.Append(userRank);
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.KickUser) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.CreateChannel) ? (userPerms.HasFlag(ChatUserPermissions.SetChannelPermanent) ? '2' : '1') : '0'); sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
sb.Append('\t'); sb.Append('\t');
sb.Append(msgId); sb.Append(msgId);

View file

@ -6,9 +6,9 @@ namespace SharpChat.S2CPackets {
DateTimeOffset joined, DateTimeOffset joined,
long userId, long userId,
string userName, string userName,
ChatColour userColour, Colour userColour,
int userRank, int userRank,
ChatUserPermissions userPerms UserPermissions userPerms
) : S2CPacket { ) : S2CPacket {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -24,13 +24,13 @@ namespace SharpChat.S2CPackets {
sb.Append('\t'); sb.Append('\t');
sb.Append(userRank); sb.Append(userRank);
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.KickUser) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.CreateChannel) ? (userPerms.HasFlag(ChatUserPermissions.SetChannelPermanent) ? '2' : '1') : '0'); sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
sb.Append('\t'); sb.Append('\t');
sb.Append(msgId); sb.Append(msgId);

View file

@ -4,9 +4,9 @@ namespace SharpChat.S2CPackets {
public class UserUpdateS2CPacket( public class UserUpdateS2CPacket(
long userId, long userId,
string userName, string userName,
ChatColour userColour, Colour userColour,
int userRank, int userRank,
ChatUserPermissions userPerms UserPermissions userPerms
) : S2CPacket { ) : S2CPacket {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
@ -20,13 +20,13 @@ namespace SharpChat.S2CPackets {
sb.Append('\t'); sb.Append('\t');
sb.Append(userRank); sb.Append(userRank);
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.KickUser) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.SetOwnNickname) ? '1' : '0'); sb.Append(userPerms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(' '); sb.Append(' ');
sb.Append(userPerms.HasFlag(ChatUserPermissions.CreateChannel) ? (userPerms.HasFlag(ChatUserPermissions.SetChannelPermanent) ? '2' : '1') : '0'); sb.Append(userPerms.HasFlag(UserPermissions.CreateChannel) ? (userPerms.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
return sb.ToString(); return sb.ToString();
} }

View file

@ -1,5 +1,5 @@
using Fleck; using Fleck;
using SharpChat.Commands; using SharpChat.ClientCommands;
using SharpChat.Config; using SharpChat.Config;
using SharpChat.EventStorage; using SharpChat.EventStorage;
using SharpChat.Misuzu; using SharpChat.Misuzu;
@ -15,7 +15,7 @@ namespace SharpChat {
public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9; public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
public IWebSocketServer Server { get; } public IWebSocketServer Server { get; }
public ChatContext Context { get; } public Context Context { get; }
private readonly HttpClient HttpClient; private readonly HttpClient HttpClient;
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu;
@ -33,7 +33,7 @@ namespace SharpChat {
private static readonly string[] DEFAULT_CHANNELS = ["lounge"]; private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
private ChatChannel DefaultChannel { get; set; } private Channel DefaultChannel { get; set; }
public SockChatServer(HttpClient httpClient, MisuzuClient msz, EventStorage.EventStorage evtStore, Config.Config config) { public SockChatServer(HttpClient httpClient, MisuzuClient msz, EventStorage.EventStorage evtStore, Config.Config config) {
Logger.Write("Initialising Sock Chat server..."); Logger.Write("Initialising Sock Chat server...");
@ -46,7 +46,7 @@ namespace SharpChat {
FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH); FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK); FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
Context = new ChatContext(evtStore); Context = new Context(evtStore);
string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS); string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
if(channelNames is not null) if(channelNames is not null)
@ -57,7 +57,7 @@ namespace SharpChat {
if(string.IsNullOrWhiteSpace(name)) if(string.IsNullOrWhiteSpace(name))
name = channelName; name = channelName;
ChatChannel channelInfo = new( Channel channelInfo = new(
name, name,
channelCfg.SafeReadValue("password", string.Empty)!, channelCfg.SafeReadValue("password", string.Empty)!,
rank: channelCfg.SafeReadValue("minRank", 0) rank: channelCfg.SafeReadValue("minRank", 0)
@ -78,23 +78,23 @@ namespace SharpChat {
]); ]);
SendMessageHandler.AddCommands([ SendMessageHandler.AddCommands([
new AFKCommand(), new AFKClientCommand(),
new NickCommand(), new NickClientCommand(),
new WhisperCommand(), new WhisperClientCommand(),
new ActionCommand(), new ActionClientCommand(),
new WhoCommand(), new WhoClientCommand(),
new JoinChannelCommand(), new JoinChannelClientCommand(),
new CreateChannelCommand(), new CreateChannelClientCommand(),
new DeleteChannelCommand(), new DeleteChannelClientCommand(),
new PasswordChannelCommand(), new PasswordChannelClientCommand(),
new RankChannelCommand(), new RankChannelClientCommand(),
new BroadcastCommand(), new BroadcastClientCommand(),
new DeleteMessageCommand(), new DeleteMessageClientCommand(),
new KickBanCommand(msz), new KickBanClientCommand(msz),
new PardonUserCommand(msz), new PardonUserClientCommand(msz),
new PardonAddressCommand(msz), new PardonAddressClientCommand(msz),
new BanListCommand(msz), new BanListClientCommand(msz),
new RemoteAddressCommand(), new RemoteAddressClientCommand(),
]); ]);
ushort port = config.SafeReadValue("port", DEFAULT_PORT); ushort port = config.SafeReadValue("port", DEFAULT_PORT);
@ -103,7 +103,7 @@ namespace SharpChat {
public void Listen(ManualResetEvent waitHandle) { public void Listen(ManualResetEvent waitHandle) {
if(waitHandle != null) if(waitHandle != null)
SendMessageHandler.AddCommand(new ShutdownRestartCommand(waitHandle, () => !IsShuttingDown && (IsShuttingDown = true))); SendMessageHandler.AddCommand(new ShutdownRestartClientCommand(waitHandle, () => !IsShuttingDown && (IsShuttingDown = true)));
Server.Start(sock => { Server.Start(sock => {
if(IsShuttingDown) { if(IsShuttingDown) {
@ -111,7 +111,7 @@ namespace SharpChat {
return; return;
} }
ChatConnection conn = new(sock); Connection conn = new(sock);
Context.Connections.Add(conn); Context.Connections.Add(conn);
sock.OnOpen = () => OnOpen(conn); sock.OnOpen = () => OnOpen(conn);
@ -123,17 +123,17 @@ namespace SharpChat {
Logger.Write("Listening..."); Logger.Write("Listening...");
} }
private void OnOpen(ChatConnection conn) { private void OnOpen(Connection conn) {
Logger.Write($"Connection opened from {conn.RemoteAddress}:{conn.RemotePort}"); Logger.Write($"Connection opened from {conn.RemoteAddress}:{conn.RemotePort}");
Context.SafeUpdate(); Context.SafeUpdate();
} }
private void OnError(ChatConnection conn, Exception ex) { private void OnError(Connection conn, Exception ex) {
Logger.Write($"[{conn.Id} {conn.RemoteAddress}] {ex}"); Logger.Write($"[{conn.Id} {conn.RemoteAddress}] {ex}");
Context.SafeUpdate(); Context.SafeUpdate();
} }
private void OnClose(ChatConnection conn) { private void OnClose(Connection conn) {
Logger.Write($"Connection closed from {conn.RemoteAddress}:{conn.RemotePort}"); Logger.Write($"Connection closed from {conn.RemoteAddress}:{conn.RemotePort}");
Context.ContextAccess.Wait(); Context.ContextAccess.Wait();
@ -149,12 +149,12 @@ namespace SharpChat {
} }
} }
private void OnMessage(ChatConnection conn, string msg) { private void OnMessage(Connection conn, string msg) {
Context.SafeUpdate(); Context.SafeUpdate();
// this doesn't affect non-authed connections????? // this doesn't affect non-authed connections?????
if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) { if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) {
ChatUser? banUser = null; User? banUser = null;
string banAddr = string.Empty; string banAddr = string.Empty;
TimeSpan banDuration = TimeSpan.MinValue; TimeSpan banDuration = TimeSpan.MinValue;
@ -162,9 +162,9 @@ namespace SharpChat {
try { try {
if(!Context.UserRateLimiters.TryGetValue(conn.User.UserId, out RateLimiter? rateLimiter)) if(!Context.UserRateLimiters.TryGetValue(conn.User.UserId, out RateLimiter? rateLimiter))
Context.UserRateLimiters.Add(conn.User.UserId, rateLimiter = new RateLimiter( Context.UserRateLimiters.Add(conn.User.UserId, rateLimiter = new RateLimiter(
ChatUser.DEFAULT_SIZE, User.DEFAULT_SIZE,
ChatUser.DEFAULT_MINIMUM_DELAY, User.DEFAULT_MINIMUM_DELAY,
ChatUser.DEFAULT_RISKY_OFFSET User.DEFAULT_RISKY_OFFSET
)); ));
rateLimiter.Update(); rateLimiter.Update();
@ -199,7 +199,7 @@ namespace SharpChat {
} }
} }
ChatPacketHandlerContext context = new(msg, Context, conn); C2SPacketHandlerContext context = new(msg, Context, conn);
C2SPacketHandler? handler = conn.User is null C2SPacketHandler? handler = conn.User is null
? GuestHandlers.FirstOrDefault(h => h.IsMatch(context)) ? GuestHandlers.FirstOrDefault(h => h.IsMatch(context))
: AuthedHandlers.FirstOrDefault(h => h.IsMatch(context)); : AuthedHandlers.FirstOrDefault(h => h.IsMatch(context));
@ -224,7 +224,7 @@ namespace SharpChat {
IsDisposed = true; IsDisposed = true;
IsShuttingDown = true; IsShuttingDown = true;
foreach(ChatConnection conn in Context.Connections) foreach(Connection conn in Context.Connections)
conn.Dispose(); conn.Dispose();
Server?.Dispose(); Server?.Dispose();

View file

@ -1,16 +1,16 @@
using SharpChat.Commands; using SharpChat.ClientCommands;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
namespace SharpChat { namespace SharpChat {
public class ChatUser( public class User(
long userId, long userId,
string userName, string userName,
ChatColour colour, Colour colour,
int rank, int rank,
ChatUserPermissions perms, UserPermissions perms,
string nickName = "", string nickName = "",
ChatUserStatus status = ChatUserStatus.Online, UserStatus status = UserStatus.Online,
string statusText = "" string statusText = ""
) { ) {
public const int DEFAULT_SIZE = 30; public const int DEFAULT_SIZE = 30;
@ -19,11 +19,11 @@ namespace SharpChat {
public long UserId { get; } = userId; public long UserId { get; } = userId;
public string UserName { get; set; } = userName ?? throw new ArgumentNullException(nameof(userName)); public string UserName { get; set; } = userName ?? throw new ArgumentNullException(nameof(userName));
public ChatColour Colour { get; set; } = colour; public Colour Colour { get; set; } = colour;
public int Rank { get; set; } = rank; public int Rank { get; set; } = rank;
public ChatUserPermissions Permissions { get; set; } = perms; public UserPermissions Permissions { get; set; } = perms;
public string NickName { get; set; } = nickName; public string NickName { get; set; } = nickName;
public ChatUserStatus Status { get; set; } = status; public UserStatus Status { get; set; } = status;
public string StatusText { get; set; } = statusText; public string StatusText { get; set; } = statusText;
public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}"; public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}";
@ -32,12 +32,12 @@ namespace SharpChat {
get { get {
StringBuilder sb = new(); StringBuilder sb = new();
if(Status == ChatUserStatus.Away) { if(Status == UserStatus.Away) {
string statusText = StatusText.Trim(); string statusText = StatusText.Trim();
StringInfo sti = new(statusText); StringInfo sti = new(statusText);
if(Encoding.UTF8.GetByteCount(statusText) > AFKCommand.MAX_BYTES if(Encoding.UTF8.GetByteCount(statusText) > AFKClientCommand.MAX_BYTES
|| sti.LengthInTextElements > AFKCommand.MAX_GRAPHEMES) || sti.LengthInTextElements > AFKClientCommand.MAX_GRAPHEMES)
statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, AFKCommand.MAX_GRAPHEMES)).Trim(); statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, AFKClientCommand.MAX_GRAPHEMES)).Trim();
sb.AppendFormat("&lt;{0}&gt;_", statusText.ToUpperInvariant()); sb.AppendFormat("&lt;{0}&gt;_", statusText.ToUpperInvariant());
} }
@ -48,8 +48,8 @@ namespace SharpChat {
} }
} }
public bool Can(ChatUserPermissions perm, bool strict = false) { public bool Can(UserPermissions perm, bool strict = false) {
ChatUserPermissions perms = Permissions & perm; UserPermissions perms = Permissions & perm;
return strict ? perms == perm : perms > 0; return strict ? perms == perm : perms > 0;
} }
@ -64,7 +64,7 @@ namespace SharpChat {
return UserId.GetHashCode(); return UserId.GetHashCode();
} }
public static string GetDMChannelName(ChatUser user1, ChatUser user2) { public static string GetDMChannelName(User user1, User user2) {
return user1.UserId < user2.UserId return user1.UserId < user2.UserId
? $"@{user1.UserId}-{user2.UserId}" ? $"@{user1.UserId}-{user2.UserId}"
: $"@{user2.UserId}-{user1.UserId}"; : $"@{user2.UserId}-{user1.UserId}";

View file

@ -1,6 +1,6 @@
namespace SharpChat { namespace SharpChat {
[Flags] [Flags]
public enum ChatUserPermissions : int { public enum UserPermissions : int {
KickUser = 0x00000001, KickUser = 0x00000001,
BanUser = 0x00000002, BanUser = 0x00000002,
//SilenceUser = 0x00000004, //SilenceUser = 0x00000004,

View file

@ -1,5 +1,5 @@
namespace SharpChat { namespace SharpChat {
public enum ChatUserStatus { public enum UserStatus {
Online, Online,
Away, Away,
Offline, Offline,

View file

@ -1,22 +1,22 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace SharpChat { namespace SharpChat {
public readonly struct ChatColour { public readonly struct Colour {
public byte Red { get; } public byte Red { get; }
public byte Green { get; } public byte Green { get; }
public byte Blue { get; } public byte Blue { get; }
public bool Inherits { get; } public bool Inherits { get; }
public static ChatColour None { get; } = new(); public static Colour None { get; } = new();
public ChatColour() { public Colour() {
Red = 0; Red = 0;
Green = 0; Green = 0;
Blue = 0; Blue = 0;
Inherits = true; Inherits = true;
} }
public ChatColour(byte red, byte green, byte blue) { public Colour(byte red, byte green, byte blue) {
Red = red; Red = red;
Green = green; Green = green;
Blue = blue; Blue = blue;
@ -24,10 +24,10 @@ namespace SharpChat {
} }
public override bool Equals([NotNullWhen(true)] object? obj) { public override bool Equals([NotNullWhen(true)] object? obj) {
return obj is ChatColour colour && Equals(colour); return obj is Colour colour && Equals(colour);
} }
public bool Equals(ChatColour other) { public bool Equals(Colour other) {
return Red == other.Red return Red == other.Red
&& Green == other.Green && Green == other.Green
&& Blue == other.Blue && Blue == other.Blue
@ -48,7 +48,7 @@ namespace SharpChat {
return (Red << 16) | (Green << 8) | Blue; return (Red << 16) | (Green << 8) | Blue;
} }
public static ChatColour FromRawRGB(int rgb) { public static Colour FromRawRGB(int rgb) {
return new( return new(
(byte)((rgb >> 16) & 0xFF), (byte)((rgb >> 16) & 0xFF),
(byte)((rgb >> 8) & 0xFF), (byte)((rgb >> 8) & 0xFF),
@ -62,17 +62,17 @@ namespace SharpChat {
return Inherits ? MSZ_INHERIT : ToRawRGB(); return Inherits ? MSZ_INHERIT : ToRawRGB();
} }
public static ChatColour FromMisuzu(int raw) { public static Colour FromMisuzu(int raw) {
return (raw & MSZ_INHERIT) > 0 return (raw & MSZ_INHERIT) > 0
? None ? None
: FromRawRGB(raw); : FromRawRGB(raw);
} }
public static bool operator ==(ChatColour left, ChatColour right) { public static bool operator ==(Colour left, Colour right) {
return left.Equals(right); return left.Equals(right);
} }
public static bool operator !=(ChatColour left, ChatColour right) { public static bool operator !=(Colour left, Colour right) {
return !(left == right); return !(left == right);
} }
} }