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 {
public interface C2SPacketHandler {
bool IsMatch(ChatPacketHandlerContext ctx);
void Handle(ChatPacketHandlerContext ctx);
bool IsMatch(C2SPacketHandlerContext ctx);
void Handle(C2SPacketHandlerContext ctx);
}
}

View file

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

View file

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

View file

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

View file

@ -11,27 +11,27 @@ namespace SharpChat.C2SPacketHandlers {
) : C2SPacketHandler {
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)));
}
public void AddCommands(IEnumerable<ChatCommand> commands) {
public void AddCommands(IEnumerable<ClientCommand> commands) {
Commands.AddRange(commands ?? throw new ArgumentNullException(nameof(commands)));
}
public bool IsMatch(ChatPacketHandlerContext ctx) {
public bool IsMatch(C2SPacketHandlerContext ctx) {
return ctx.CheckPacketId("2");
}
public void Handle(ChatPacketHandlerContext ctx) {
public void Handle(C2SPacketHandlerContext ctx) {
string[] args = ctx.SplitText(3);
ChatUser? user = ctx.Connection.User;
User? user = ctx.Connection.User;
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;
// Extra validation step, not necessary at all but enforces proper formatting in SCv1.
@ -40,12 +40,12 @@ namespace SharpChat.C2SPacketHandlers {
ctx.Chat.ContextAccess.Wait();
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)))
return;
if(user.Status != ChatUserStatus.Online)
ctx.Chat.UpdateUser(user, status: ChatUserStatus.Online);
if(user.Status != UserStatus.Online)
ctx.Chat.UpdateUser(user, status: UserStatus.Online);
int maxMsgLength = MaxMessageLength;
StringInfo messageTextInfo = new(messageText);
@ -60,8 +60,8 @@ namespace SharpChat.C2SPacketHandlers {
#endif
if(messageText.StartsWith('/')) {
ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
foreach(ChatCommand cmd in Commands)
ClientCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
foreach(ClientCommand cmd in Commands)
if(cmd.IsMatch(context)) {
cmd.Dispatch(context);
return;

View file

@ -1,5 +1,5 @@
namespace SharpChat {
public class ChatChannel(
public class Channel(
string name,
string password = "",
bool isTemporary = false,
@ -19,7 +19,7 @@
return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
}
public bool IsOwner(ChatUser user) {
public bool IsOwner(User user) {
return OwnerId > 0
&& user != null
&& 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 {
public class ChatCommandContext {
public class ClientCommandContext {
public string Name { get; }
public string[] Args { get; }
public ChatContext Chat { get; }
public ChatUser User { get; }
public ChatConnection Connection { get; }
public ChatChannel Channel { get; }
public Context Chat { get; }
public User User { get; }
public Connection Connection { get; }
public Channel Channel { get; }
public ChatCommandContext(
public ClientCommandContext(
string text,
ChatContext chat,
ChatUser user,
ChatConnection connection,
ChatChannel channel
Context chat,
User user,
Connection connection,
Channel channel
) {
ArgumentNullException.ThrowIfNull(text);
@ -26,13 +26,13 @@
Args = [.. parts.Skip(1)];
}
public ChatCommandContext(
public ClientCommandContext(
string name,
string[] args,
ChatContext chat,
ChatUser user,
ChatConnection connection,
ChatChannel channel
Context chat,
User user,
Connection connection,
Channel channel
) {
Name = name ?? throw new ArgumentNullException(nameof(name));
Args = args ?? throw new ArgumentNullException(nameof(args));

View file

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

View file

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

View file

@ -1,19 +1,19 @@
using SharpChat.Misuzu;
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class BanListCommand(MisuzuClient msz) : ChatCommand {
namespace SharpChat.ClientCommands {
public class BanListClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("bans")
|| ctx.NameEquals("banned");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}

View file

@ -1,17 +1,17 @@
using SharpChat.Events;
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class BroadcastCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class BroadcastClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("say")
|| ctx.NameEquals("broadcast");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}

View file

@ -1,15 +1,15 @@
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class CreateChannelCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class CreateChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("create");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}
@ -34,7 +34,7 @@ namespace SharpChat.Commands {
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));
return;
}
@ -44,15 +44,15 @@ namespace SharpChat.Commands {
return;
}
ChatChannel createChan = new(
Channel createChan = new(
createChanName,
isTemporary: !ctx.User.Can(ChatUserPermissions.SetChannelPermanent),
isTemporary: !ctx.User.Can(UserPermissions.SetChannelPermanent),
rank: createChanHierarchy,
ownerId: ctx.User.UserId
);
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.SwitchChannel(ctx.User, createChan, createChan.Password);

View file

@ -1,15 +1,15 @@
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class DeleteChannelCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class DeleteChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("delchan") || (
ctx.NameEquals("delete")
&& ctx.Args.FirstOrDefault()?.All(char.IsDigit) == false
);
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(ctx.Args.Length < 1 || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) {
@ -18,14 +18,14 @@ namespace SharpChat.Commands {
}
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) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
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));
return;
}

View file

@ -1,21 +1,21 @@
using SharpChat.EventStorage;
using SharpChat.S2CPackets;
namespace SharpChat.Commands
namespace SharpChat.ClientCommands
{
public class DeleteMessageCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
public class DeleteMessageClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("delmsg") || (
ctx.NameEquals("delete")
&& ctx.Args.FirstOrDefault()?.All(char.IsDigit) == true
);
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}

View file

@ -1,15 +1,15 @@
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class JoinChannelCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class JoinChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("join");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
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) {
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.S2CPackets;
namespace SharpChat.Commands {
public class KickBanCommand(MisuzuClient msz) : ChatCommand {
namespace SharpChat.ClientCommands {
public class KickBanClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("kick")
|| ctx.NameEquals("ban");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
bool isBanning = ctx.NameEquals("ban");
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}"));
return;
}
@ -22,7 +22,7 @@ namespace SharpChat.Commands {
string? banUserTarget = ctx.Args.ElementAtOrDefault(0);
string? banDurationStr = ctx.Args.ElementAtOrDefault(1);
int banReasonIndex = 1;
ChatUser? banUser = null;
User? banUser = 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"));

View file

@ -2,25 +2,25 @@
using System.Globalization;
using System.Text;
namespace SharpChat.Commands {
public class NickCommand : ChatCommand {
namespace SharpChat.ClientCommands {
public class NickClientCommand : ClientCommand {
private const int MAX_GRAPHEMES = 16;
private const int MAX_BYTES = MAX_GRAPHEMES * 10;
public bool IsMatch(ChatCommandContext ctx) {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("nick");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}
ChatUser? targetUser = null;
User? targetUser = null;
int offset = 0;
if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {

View file

@ -2,19 +2,19 @@
using SharpChat.S2CPackets;
using System.Net;
namespace SharpChat.Commands {
public class PardonAddressCommand(MisuzuClient msz) : ChatCommand {
namespace SharpChat.ClientCommands {
public class PardonAddressClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pardonip")
|| ctx.NameEquals("unbanip");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}

View file

@ -1,19 +1,19 @@
using SharpChat.Misuzu;
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class PardonUserCommand(MisuzuClient msz) : ChatCommand {
namespace SharpChat.ClientCommands {
public class PardonUserClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ChatCommandContext ctx) {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pardon")
|| ctx.NameEquals("unban");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}
@ -25,7 +25,7 @@ namespace SharpChat.Commands {
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)) {
unbanUserTargetIsName = false;
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId);

View file

@ -1,16 +1,16 @@
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class PasswordChannelCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class PasswordChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pwd")
|| ctx.NameEquals("password");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}

View file

@ -1,17 +1,17 @@
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class RankChannelCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class RankChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("rank")
|| ctx.NameEquals("privilege")
|| ctx.NameEquals("priv");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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}"));
return;
}

View file

@ -1,23 +1,23 @@
using SharpChat.S2CPackets;
using System.Net;
namespace SharpChat.Commands {
public class RemoteAddressCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class RemoteAddressClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("ip")
|| ctx.NameEquals("whois");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
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"));
return;
}
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) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));

View file

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

View file

@ -1,14 +1,14 @@
using SharpChat.Events;
using SharpChat.S2CPackets;
namespace SharpChat.Commands {
public class WhisperCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class WhisperClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("whisper")
|| ctx.NameEquals("msg");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(ctx.Args.Length < 2) {
@ -17,7 +17,7 @@ namespace SharpChat.Commands {
}
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) {
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(
msgId,
ChatUser.GetDMChannelName(ctx.User, whisperUser),
User.GetDMChannelName(ctx.User, whisperUser),
ctx.User.UserId,
ctx.User.UserName,
ctx.User.Colour,

View file

@ -1,19 +1,19 @@
using SharpChat.S2CPackets;
using System.Text;
namespace SharpChat.Commands {
public class WhoCommand : ChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
namespace SharpChat.ClientCommands {
public class WhoClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("who");
}
public void Dispatch(ChatCommandContext ctx) {
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
StringBuilder whoChanSB = new();
string? whoChanStr = ctx.Args.FirstOrDefault();
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);""");
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));
} 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) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
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));
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);""");
if(whoUser == ctx.User)

View file

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

View file

@ -5,7 +5,7 @@ using SharpChat.Snowflake;
using System.Net;
namespace SharpChat {
public class ChatContext {
public class Context {
public record ChannelUserAssoc(long UserId, string ChannelName);
public readonly SemaphoreSlim ContextAccess = new(1, 1);
@ -13,15 +13,15 @@ namespace SharpChat {
public SnowflakeGenerator SnowflakeGenerator { get; } = new();
public RandomSnowflake RandomSnowflake { get; }
public HashSet<ChatChannel> Channels { get; } = [];
public HashSet<ChatConnection> Connections { get; } = [];
public HashSet<ChatUser> Users { get; } = [];
public HashSet<Channel> Channels { get; } = [];
public HashSet<Connection> Connections { get; } = [];
public HashSet<User> Users { get; } = [];
public EventStorage.EventStorage Events { get; }
public HashSet<ChannelUserAssoc> ChannelUsers { 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));
RandomSnowflake = new(SnowflakeGenerator);
}
@ -42,12 +42,12 @@ namespace SharpChat {
if(uids.Count() != 2)
return;
IEnumerable<ChatUser> users = Users.Where(u => uids.Any(uid => uid == u.UserId));
ChatUser? target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
IEnumerable<User> users = Users.Where(u => uids.Any(uid => uid == u.UserId));
User? target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
if(target == null)
return;
foreach(ChatUser user in users)
foreach(User user in users)
SendTo(user, new ChatMessageAddS2CPacket(
mce.MessageId,
DateTimeOffset.Now,
@ -57,7 +57,7 @@ namespace SharpChat {
true
));
} else {
ChatChannel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
Channel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
if(channel is not null)
SendTo(channel, new ChatMessageAddS2CPacket(
mce.MessageId,
@ -83,7 +83,7 @@ namespace SharpChat {
}
public void Update() {
foreach(ChatConnection conn in Connections)
foreach(Connection conn in Connections)
if(!conn.IsDisposed && conn.HasTimedOut) {
conn.Dispose();
Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
@ -91,7 +91,7 @@ namespace SharpChat {
Connections.RemoveWhere(conn => conn.IsDisposed);
foreach(ChatUser user in Users)
foreach(User user in Users)
if(!Connections.Any(conn => conn.User == user)) {
HandleDisconnect(user, UserDisconnectS2CPacket.Reason.TimeOut);
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));
}
public string[] GetUserChannelNames(ChatUser user) {
public string[] GetUserChannelNames(User user) {
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);
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)];
}
public ChatUser[] GetChannelUsers(ChatChannel channel) {
public User[] GetChannelUsers(Channel channel) {
long[] ids = GetChannelUserIds(channel);
return [.. Users.Where(u => ids.Contains(u.UserId))];
}
public void UpdateUser(
ChatUser user,
User user,
string? userName = null,
string? nickName = null,
ChatColour? colour = null,
ChatUserStatus? status = null,
Colour? colour = null,
UserStatus? status = null,
string? statusText = null,
int? rank = null,
ChatUserPermissions? perms = null,
UserPermissions? perms = null,
bool silent = false
) {
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) {
DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration;
SendTo(user, new ForceDisconnectS2CPacket(expires));
} else
SendTo(user, new ForceDisconnectS2CPacket());
foreach(ChatConnection conn in Connections)
foreach(Connection conn in Connections)
if(conn.User == user)
conn.Dispose();
Connections.RemoveWhere(conn => conn.IsDisposed);
@ -206,7 +206,7 @@ namespace SharpChat {
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)) {
long msgId = RandomSnowflake.Next();
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;
}
public void HandleDisconnect(ChatUser user, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Leave) {
UpdateUser(user, status: ChatUserStatus.Offline);
public void HandleDisconnect(User user, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Leave) {
UpdateUser(user, status: UserStatus.Offline);
Users.Remove(user);
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));
long msgId = RandomSnowflake.Next();
@ -267,13 +267,13 @@ namespace SharpChat {
}
}
public void SwitchChannel(ChatUser user, ChatChannel chan, string password) {
if(UserLastChannel.TryGetValue(user.UserId, out ChatChannel? ulc) && chan == ulc) {
public void SwitchChannel(User user, Channel chan, string password) {
if(UserLastChannel.TryGetValue(user.UserId, out Channel? ulc) && chan == ulc) {
ForceChannel(user);
return;
}
if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
if(!user.Can(UserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
if(chan.Rank > user.Rank) {
SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
ForceChannel(user);
@ -290,11 +290,11 @@ namespace SharpChat {
ForceChannelSwitch(user, chan);
}
public void ForceChannelSwitch(ChatUser user, ChatChannel chan) {
public void ForceChannelSwitch(User user, Channel chan) {
if(!Channels.Contains(chan))
return;
ChatChannel oldChan = UserLastChannel[user.UserId];
Channel oldChan = UserLastChannel[user.UserId];
long leaveId = RandomSnowflake.Next();
SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user.UserId));
@ -333,45 +333,45 @@ namespace SharpChat {
public void Send(S2CPacket packet) {
ArgumentNullException.ThrowIfNull(packet);
foreach(ChatConnection conn in Connections)
foreach(Connection conn in Connections)
if(conn.IsAlive && conn.User is not null)
conn.Send(packet);
}
public void SendTo(ChatUser user, S2CPacket packet) {
public void SendTo(User user, S2CPacket packet) {
ArgumentNullException.ThrowIfNull(user);
ArgumentNullException.ThrowIfNull(packet);
foreach(ChatConnection conn in Connections)
foreach(Connection conn in Connections)
if(conn.IsAlive && conn.User == user)
conn.Send(packet);
}
public void SendTo(ChatChannel channel, S2CPacket packet) {
public void SendTo(Channel channel, S2CPacket packet) {
ArgumentNullException.ThrowIfNull(channel);
ArgumentNullException.ThrowIfNull(packet);
// 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));
foreach(ChatConnection conn in conns)
IEnumerable<Connection> conns = Connections.Where(c => c.IsAlive && c.User is not null && IsInChannel(c.User, channel));
foreach(Connection conn in conns)
conn.Send(packet);
}
public void SendToUserChannels(ChatUser user, S2CPacket packet) {
public void SendToUserChannels(User user, S2CPacket packet) {
ArgumentNullException.ThrowIfNull(user);
ArgumentNullException.ThrowIfNull(packet);
IEnumerable<ChatChannel> 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))));
foreach(ChatConnection conn in conns)
IEnumerable<Channel> chans = Channels.Where(c => IsInChannel(user, c));
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(Connection conn in conns)
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()];
}
public void ForceChannel(ChatUser user, ChatChannel? chan = null) {
public void ForceChannel(User user, Channel? chan = null) {
ArgumentNullException.ThrowIfNull(user);
if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
@ -380,7 +380,7 @@ namespace SharpChat {
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);
if(!Channels.Contains(channel))
throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
@ -395,15 +395,15 @@ namespace SharpChat {
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
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));
}
public void RemoveChannel(ChatChannel channel) {
public void RemoveChannel(Channel channel) {
if(channel == null || Channels.Count < 1)
return;
ChatChannel? defaultChannel = Channels.FirstOrDefault();
Channel? defaultChannel = Channels.FirstOrDefault();
if(defaultChannel is null)
return;
@ -412,11 +412,11 @@ namespace SharpChat {
// 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.
foreach(ChatUser user in GetChannelUsers(channel))
foreach(User user in GetChannelUsers(channel))
SwitchChannel(user, defaultChannel, string.Empty);
// 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));
}
}

View file

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

View file

@ -12,10 +12,10 @@ namespace SharpChat.EventStorage {
string channelName,
long senderId,
string senderName,
ChatColour senderColour,
Colour senderColour,
int senderRank,
string senderNick,
ChatUserPermissions senderPerms,
UserPermissions senderPerms,
object? data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
@ -71,12 +71,12 @@ namespace SharpChat.EventStorage {
return new StoredEventInfo(
reader.GetInt64("event_id"),
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.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"),
(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")
),
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),

View file

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

View file

@ -10,10 +10,10 @@ namespace SharpChat.EventStorage {
string channelName,
long senderId,
string senderName,
ChatColour senderColour,
Colour senderColour,
int senderRank,
string senderNick,
ChatUserPermissions senderPerms,
UserPermissions senderPerms,
object? data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
@ -21,7 +21,7 @@ namespace SharpChat.EventStorage {
// VES is meant as an emergency fallback but this is something else
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,
senderName,
senderColour,

View file

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

View file

@ -17,12 +17,12 @@ namespace SharpChat.Misuzu {
[JsonPropertyName("colour_raw")]
public int ColourRaw { get; set; }
public ChatColour Colour => ChatColour.FromMisuzu(ColourRaw);
public Colour Colour => Colour.FromMisuzu(ColourRaw);
[JsonPropertyName("hierarchy")]
public int Rank { get; set; }
[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 ChatColour UserColour => ChatColour.FromMisuzu(UserColourRaw);
public Colour UserColour => Colour.FromMisuzu(UserColourRaw);
}
}

View file

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

View file

@ -31,13 +31,13 @@ namespace SharpChat.S2CPackets
sb.Append('\t');
sb.Append(Event.Sender.Rank);
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(Event.Sender.Permissions.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0');
sb.Append(Event.Sender.Permissions.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
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(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');
}

View file

@ -2,7 +2,7 @@
namespace SharpChat.S2CPackets {
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() {
StringBuilder sb = new();
@ -20,13 +20,13 @@ namespace SharpChat.S2CPackets {
sb.Append('\t');
sb.Append(entry.rank);
sb.Append(' ');
sb.Append(entry.perms.HasFlag(ChatUserPermissions.KickUser) ? '1' : '0');
sb.Append(entry.perms.HasFlag(UserPermissions.KickUser) ? '1' : '0');
sb.Append(' ');
sb.Append(entry.perms.HasFlag(ChatUserPermissions.ViewLogs) ? '1' : '0');
sb.Append(entry.perms.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
sb.Append(' ');
sb.Append(entry.perms.HasFlag(ChatUserPermissions.SetOwnNickname) ? '1' : '0');
sb.Append(entry.perms.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
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(entry.visible ? '1' : '0');
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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