Switched to Index brand random Snowflakes instead of SharpIds.

If you were still handling message ids as integers in an environment that can't handle signed 64-bit integers you're going to be having a fun time after this update!
This commit is contained in:
flash 2025-04-25 20:05:55 +00:00
parent f5c8f2ae1d
commit e17aed7c25
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
77 changed files with 290 additions and 338 deletions
SharpChat.sln
SharpChat
SharpChatCommon

View file

@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
start.sh = start.sh start.sh = start.sh
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpChatCommon", "SharpChatCommon\SharpChatCommon.csproj", "{C8B619A7-7815-426D-B459-20EE26F7460E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -24,6 +26,10 @@ Global
{DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Debug|Any CPU.Build.0 = Debug|Any CPU {DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Release|Any CPU.Build.0 = Release|Any CPU {DDB24C19-B802-4C96-AC15-0449C6FC77F2}.Release|Any CPU.Build.0 = Release|Any CPU
{C8B619A7-7815-426D-B459-20EE26F7460E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8B619A7-7815-426D-B459-20EE26F7460E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8B619A7-7815-426D-B459-20EE26F7460E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8B619A7-7815-426D-B459-20EE26F7460E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -1,7 +1,4 @@
using System; namespace SharpChat {
using System.Linq;
namespace SharpChat {
public class ChatCommandContext { public class ChatCommandContext {
public string Name { get; } public string Name { get; }
public string[] Args { get; } public string[] Args { get; }

View file

@ -1,6 +1,4 @@
using Fleck; using Fleck;
using System;
using System.Collections.Generic;
using System.Net; using System.Net;
namespace SharpChat { namespace SharpChat {

View file

@ -1,30 +1,35 @@
using SharpChat.Events; using SharpChat.Events;
using SharpChat.EventStorage; using SharpChat.EventStorage;
using SharpChat.Packet; using SharpChat.Packet;
using System; using SharpChat.Snowflake;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Threading;
namespace SharpChat { namespace SharpChat {
public class ChatContext(IEventStorage evtStore) { public class ChatContext {
public record ChannelUserAssoc(long UserId, string ChannelName); public record ChannelUserAssoc(long UserId, string ChannelName);
public readonly SemaphoreSlim ContextAccess = new(1, 1); public readonly SemaphoreSlim ContextAccess = new(1, 1);
public SnowflakeGenerator SnowflakeGenerator { get; } = new();
public RandomSnowflake RandomSnowflake { get; }
public HashSet<ChatChannel> Channels { get; } = []; public HashSet<ChatChannel> Channels { get; } = [];
public HashSet<ChatConnection> Connections { get; } = []; public HashSet<ChatConnection> Connections { get; } = [];
public HashSet<ChatUser> Users { get; } = []; public HashSet<ChatUser> Users { get; } = [];
public IEventStorage Events { get; } = evtStore ?? throw new ArgumentNullException(nameof(evtStore)); public IEventStorage 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, ChatChannel> UserLastChannel { get; } = [];
public ChatContext(IEventStorage evtStore) {
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
RandomSnowflake = new(SnowflakeGenerator);
}
public void DispatchEvent(IChatEvent eventInfo) { public void DispatchEvent(IChatEvent eventInfo) {
if(eventInfo is MessageCreateEvent mce) { if(eventInfo is MessageCreateEvent mce) {
if(mce.IsBroadcast) { if(mce.IsBroadcast) {
Send(new LegacyCommandResponse(LCR.BROADCAST, false, mce.MessageText)); Send(new LegacyCommandResponse(RandomSnowflake.Next(), LCR.BROADCAST, false, mce.MessageText));
} else if(mce.IsPrivate) { } else if(mce.IsPrivate) {
// The channel name returned by GetDMChannelName should not be exposed to the user, instead @<Target User> should be displayed // The channel name returned by GetDMChannelName should not be exposed to the user, instead @<Target User> should be displayed
// e.g. nook sees @Arysil and Arysil sees @nook // e.g. nook sees @Arysil and Arysil sees @nook
@ -185,7 +190,7 @@ namespace SharpChat {
} }
if(hasChanged) if(hasChanged)
SendToUserChannels(user, new UserUpdatePacket(user, previousName)); SendToUserChannels(user, new UserUpdatePacket(RandomSnowflake.Next(), user, previousName));
} }
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) { public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
@ -205,8 +210,9 @@ namespace SharpChat {
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) { public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
if(!IsInChannel(user, chan)) { if(!IsInChannel(user, chan)) {
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user)); long msgId = RandomSnowflake.Next();
Events.AddEvent(SharpId.Next(), "user:connect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log); SendTo(chan, new UserConnectPacket(msgId, DateTimeOffset.Now, user));
Events.AddEvent(msgId, "user:connect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
} }
conn.Send(new AuthSuccessPacket(user, chan, maxMsgLength)); conn.Send(new AuthSuccessPacket(user, chan, maxMsgLength));
@ -233,8 +239,9 @@ namespace SharpChat {
foreach(ChatChannel chan in channels) { foreach(ChatChannel chan in channels) {
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name)); ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason)); long msgId = RandomSnowflake.Next();
Events.AddEvent(SharpId.Next(), "user:disconnect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, new { reason = (int)reason }, StoredEventFlags.Log); SendTo(chan, new UserDisconnectPacket(msgId, DateTimeOffset.Now, user, reason));
Events.AddEvent(msgId, "user:disconnect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, new { reason = (int)reason }, StoredEventFlags.Log);
if(chan.IsTemporary && chan.IsOwner(user)) if(chan.IsTemporary && chan.IsOwner(user))
RemoveChannel(chan); RemoveChannel(chan);
@ -249,13 +256,13 @@ namespace SharpChat {
if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.IsOwner(user)) { if(!user.Can(ChatUserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
if(chan.Rank > user.Rank) { if(chan.Rank > user.Rank) {
SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name)); SendTo(user, new LegacyCommandResponse(RandomSnowflake.Next(), LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
ForceChannel(user); ForceChannel(user);
return; return;
} }
if(!string.IsNullOrEmpty(chan.Password) && chan.Password != password) { if(!string.IsNullOrEmpty(chan.Password) && chan.Password != password) {
SendTo(user, new LegacyCommandResponse(LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name)); SendTo(user, new LegacyCommandResponse(RandomSnowflake.Next(), LCR.CHANNEL_INVALID_PASSWORD, true, chan.Name));
ForceChannel(user); ForceChannel(user);
return; return;
} }
@ -270,10 +277,13 @@ namespace SharpChat {
ChatChannel oldChan = UserLastChannel[user.UserId]; ChatChannel oldChan = UserLastChannel[user.UserId];
SendTo(oldChan, new UserChannelLeavePacket(user)); long leaveId = RandomSnowflake.Next();
Events.AddEvent(SharpId.Next(), "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log); SendTo(oldChan, new UserChannelLeavePacket(leaveId, user));
SendTo(chan, new UserChannelJoinPacket(user)); Events.AddEvent(leaveId, "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
Events.AddEvent(SharpId.Next(), "chan:join", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
long joinId = RandomSnowflake.Next();
SendTo(chan, new UserChannelJoinPacket(joinId, user));
Events.AddEvent(joinId, "chan:join", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
SendTo(user, new ContextClearPacket(ContextClearMode.MessagesUsers)); SendTo(user, new ContextClearPacket(ContextClearMode.MessagesUsers));
SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank))); SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)));

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat {
namespace SharpChat {
public class ChatPacketHandlerContext( public class ChatPacketHandlerContext(
string text, string text,
ChatContext chat, ChatContext chat,

View file

@ -1,7 +1,6 @@
using System; using SharpChat.Commands;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using SharpChat.Commands;
namespace SharpChat { namespace SharpChat {
public class ChatUser( public class ChatUser(

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat {
namespace SharpChat {
[Flags] [Flags]
public enum ChatUserPermissions : int { public enum ChatUserPermissions : int {
KickUser = 0x00000001, KickUser = 0x00000001,

View file

@ -1,7 +1,4 @@
using SharpChat.Packet; using System.Globalization;
using System;
using System.Globalization;
using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Commands { namespace SharpChat.Commands {

View file

@ -1,6 +1,4 @@
using SharpChat.Events; using SharpChat.Events;
using System;
using System.Linq;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class ActionCommand : IChatCommand { public class ActionCommand : IChatCommand {
@ -18,7 +16,7 @@ namespace SharpChat.Commands {
return; return;
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), ctx.Chat.RandomSnowflake.Next(),
ctx.Channel.Name, ctx.Channel.Name,
ctx.User.UserId, ctx.User.UserId,
ctx.User.UserName, ctx.User.UserName,

View file

@ -1,7 +1,5 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class BanListCommand(MisuzuClient msz) : IChatCommand { public class BanListCommand(MisuzuClient msz) : IChatCommand {
@ -13,8 +11,10 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) { if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -22,9 +22,9 @@ namespace SharpChat.Commands {
MisuzuBanInfo[]? mbi = await Misuzu.GetBanListAsync(); MisuzuBanInfo[]? mbi = await Misuzu.GetBanListAsync();
if(mbi is null) if(mbi is null)
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.GENERIC_ERROR, true));
else else
ctx.Chat.SendTo(ctx.User, new BanListPacket(mbi)); ctx.Chat.SendTo(ctx.User, new BanListPacket(msgId, mbi));
}).Wait(); }).Wait();
} }
} }

View file

@ -1,6 +1,5 @@
using SharpChat.Events; using SharpChat.Events;
using SharpChat.Packet; using SharpChat.Packet;
using System;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class BroadcastCommand : IChatCommand { public class BroadcastCommand : IChatCommand {
@ -10,13 +9,15 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.Broadcast)) { if(!ctx.User.Can(ChatUserPermissions.Broadcast)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), msgId,
string.Empty, string.Empty,
ctx.User.UserId, ctx.User.UserId,
ctx.User.UserName, ctx.User.UserName,

View file

@ -1,5 +1,4 @@
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class CreateChannelCommand : IChatCommand { public class CreateChannelCommand : IChatCommand {
@ -8,8 +7,10 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.CreateChannel)) { if(!ctx.User.Can(ChatUserPermissions.CreateChannel)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -17,7 +18,7 @@ namespace SharpChat.Commands {
bool createChanHasHierarchy; bool createChanHasHierarchy;
if(ctx.Args.Length < 1 || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) { if(ctx.Args.Length < 1 || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -27,19 +28,19 @@ namespace SharpChat.Commands {
createChanHierarchy = 0; createChanHierarchy = 0;
if(createChanHierarchy > ctx.User.Rank) { if(createChanHierarchy > ctx.User.Rank) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.INSUFFICIENT_HIERARCHY));
return; return;
} }
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(!ChatChannel.CheckName(createChanName)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NAME_INVALID)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_NAME_INVALID));
return; return;
} }
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) { if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_ALREADY_EXISTS, true, createChanName)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
return; return;
} }
@ -55,7 +56,7 @@ namespace SharpChat.Commands {
ctx.Chat.SendTo(ccu, new ChannelCreatePacket(ctx.Channel)); ctx.Chat.SendTo(ccu, new ChannelCreatePacket(ctx.Channel));
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password); ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_CREATED, false, createChan.Name)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_CREATED, false, createChan.Name));
} }
} }
} }

View file

@ -1,5 +1,4 @@
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class DeleteChannelCommand : IChatCommand { public class DeleteChannelCommand : IChatCommand {
@ -11,8 +10,10 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
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())) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -20,17 +21,17 @@ namespace SharpChat.Commands {
ChatChannel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName)); ChatChannel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
if(delChan == null) { if(delChan == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, delChanName)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
return; return;
} }
if(!ctx.User.Can(ChatUserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) { if(!ctx.User.Can(ChatUserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETE_FAILED, true, delChan.Name)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
return; return;
} }
ctx.Chat.RemoveChannel(delChan); ctx.Chat.RemoveChannel(delChan);
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_DELETED, false, delChan.Name)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_DELETED, false, delChan.Name));
} }
} }
} }

View file

@ -1,6 +1,5 @@
using SharpChat.EventStorage; using SharpChat.EventStorage;
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq;
namespace SharpChat.Commands namespace SharpChat.Commands
{ {
@ -13,24 +12,25 @@ namespace SharpChat.Commands
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
bool deleteAnyMessage = ctx.User.Can(ChatUserPermissions.DeleteAnyMessage); bool deleteAnyMessage = ctx.User.Can(ChatUserPermissions.DeleteAnyMessage);
if(!deleteAnyMessage && !ctx.User.Can(ChatUserPermissions.DeleteOwnMessage)) { if(!deleteAnyMessage && !ctx.User.Can(ChatUserPermissions.DeleteOwnMessage)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
string? firstArg = ctx.Args.FirstOrDefault(); string? firstArg = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) { if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
StoredEventInfo? delMsg = ctx.Chat.Events.GetEvent(delSeqId); StoredEventInfo? delMsg = ctx.Chat.Events.GetEvent(delSeqId);
if(delMsg?.Sender is null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) { if(delMsg?.Sender is null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.MESSAGE_DELETE_ERROR));
return; return;
} }

View file

@ -1,5 +1,4 @@
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class JoinChannelCommand : IChatCommand { public class JoinChannelCommand : IChatCommand {
@ -8,11 +7,12 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
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)); ChatChannel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
if(joinChan is null) { if(joinChan is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, joinChanStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
ctx.Chat.ForceChannel(ctx.User); ctx.Chat.ForceChannel(ctx.User);
return; return;
} }

View file

@ -1,8 +1,5 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class KickBanCommand(MisuzuClient msz) : IChatCommand { public class KickBanCommand(MisuzuClient msz) : IChatCommand {
@ -15,9 +12,10 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
bool isBanning = ctx.NameEquals("ban"); 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 ? ChatUserPermissions.BanUser : ChatUserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -27,19 +25,19 @@ namespace SharpChat.Commands {
ChatUser? banUser = null; ChatUser? 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 LegacyCommandResponse(LCR.USER_NOT_FOUND, true, banUserTarget ?? "User")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_NOT_FOUND, true, banUserTarget ?? "User"));
return; return;
} }
if(!ctx.User.IsSuper && banUser.Rank >= ctx.User.Rank && banUser != ctx.User) { if(!ctx.User.IsSuper && banUser.Rank >= ctx.User.Rank && banUser != ctx.User) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
return; return;
} }
TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero; TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero;
if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) { if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) {
if(durationSeconds < 0) { if(durationSeconds < 0) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -61,12 +59,12 @@ namespace SharpChat.Commands {
// obviously it makes no sense to only check for one ip address but that's current misuzu limitations // obviously it makes no sense to only check for one ip address but that's current misuzu limitations
MisuzuBanInfo? fbi = await Misuzu.CheckBanAsync(userId, userIp); MisuzuBanInfo? fbi = await Misuzu.CheckBanAsync(userId, userIp);
if(fbi is null) { if(fbi is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.GENERIC_ERROR, true));
return; return;
} }
if(fbi.IsBanned && !fbi.HasExpired) { if(fbi.IsBanned && !fbi.HasExpired) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
return; return;
} }

View file

@ -1,7 +1,5 @@
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Commands { namespace SharpChat.Commands {
@ -14,10 +12,11 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
bool setOthersNick = ctx.User.Can(ChatUserPermissions.SetOthersNickname); bool setOthersNick = ctx.User.Can(ChatUserPermissions.SetOthersNickname);
if(!setOthersNick && !ctx.User.Can(ChatUserPermissions.SetOwnNickname)) { if(!setOthersNick && !ctx.User.Can(ChatUserPermissions.SetOwnNickname)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -32,7 +31,7 @@ namespace SharpChat.Commands {
targetUser ??= ctx.User; targetUser ??= ctx.User;
if(ctx.Args.Length < offset) { if(ctx.Args.Length < offset) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -53,7 +52,7 @@ namespace SharpChat.Commands {
} }
if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) { if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.NAME_IN_USE, true, nickStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.NAME_IN_USE, true, nickStr));
return; return;
} }

View file

@ -1,9 +1,6 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class PardonAddressCommand(MisuzuClient msz) : IChatCommand { public class PardonAddressCommand(MisuzuClient msz) : IChatCommand {
@ -15,14 +12,16 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) { if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
string? unbanAddrTarget = ctx.Args.FirstOrDefault(); string? unbanAddrTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress? unbanAddr)) { if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress? unbanAddr)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -31,20 +30,20 @@ namespace SharpChat.Commands {
Task.Run(async () => { Task.Run(async () => {
MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(ipAddr: unbanAddrTarget); MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(ipAddr: unbanAddrTarget);
if(banInfo is null) { if(banInfo is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.GENERIC_ERROR, true));
return; return;
} }
if(!banInfo.IsBanned || banInfo.HasExpired) { if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
return; return;
} }
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.RemoteAddress); bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.RemoteAddress);
if(wasBanned) if(wasBanned)
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanAddrTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_UNBANNED, false, unbanAddrTarget));
else else
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
}).Wait(); }).Wait();
} }
} }

View file

@ -1,8 +1,5 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class PardonUserCommand(MisuzuClient msz) : IChatCommand { public class PardonUserCommand(MisuzuClient msz) : IChatCommand {
@ -14,15 +11,17 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) { if(!ctx.User.Can(ChatUserPermissions.BanUser | ChatUserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
bool unbanUserTargetIsName = true; bool unbanUserTargetIsName = true;
string? unbanUserTarget = ctx.Args.FirstOrDefault(); string? unbanUserTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanUserTarget)) { if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -38,20 +37,20 @@ namespace SharpChat.Commands {
Task.Run(async () => { Task.Run(async () => {
MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(unbanUserTarget, userIdIsName: unbanUserTargetIsName); MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(unbanUserTarget, userIdIsName: unbanUserTargetIsName);
if(banInfo is null) { if(banInfo is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.GENERIC_ERROR, true));
return; return;
} }
if(!banInfo.IsBanned || banInfo.HasExpired) { if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_NOT_BANNED, true, unbanUserTarget));
return; return;
} }
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.UserId); bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.UserId);
if(wasBanned) if(wasBanned)
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanUserTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_UNBANNED, false, unbanUserTarget));
else else
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_NOT_BANNED, true, unbanUserTarget));
}).Wait(); }).Wait();
} }
} }

View file

@ -8,8 +8,10 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) { if(!ctx.User.Can(ChatUserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
@ -19,7 +21,7 @@ namespace SharpChat.Commands {
chanPass = string.Empty; chanPass = string.Empty;
ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass); ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_PASSWORD_CHANGED, false)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_PASSWORD_CHANGED, false));
} }
} }
} }

View file

@ -1,5 +1,4 @@
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class RankChannelCommand : IChatCommand { public class RankChannelCommand : IChatCommand {
@ -10,18 +9,20 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) { if(!ctx.User.Can(ChatUserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
if(ctx.Args.Length < 1 || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) { if(ctx.Args.Length < 1 || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.INSUFFICIENT_HIERARCHY));
return; return;
} }
ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy); ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_HIERARCHY_CHANGED, false)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.CHANNEL_HIERARCHY_CHANGED, false));
} }
} }
} }

View file

@ -1,5 +1,4 @@
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq;
using System.Net; using System.Net;
namespace SharpChat.Commands { namespace SharpChat.Commands {
@ -10,8 +9,10 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(ChatUserPermissions.SeeIPAddress)) { if(!ctx.User.Can(ChatUserPermissions.SeeIPAddress)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, "/ip")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, "/ip"));
return; return;
} }
@ -19,12 +20,12 @@ namespace SharpChat.Commands {
ChatUser? ipUser = null; ChatUser? 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 LegacyCommandResponse(LCR.USER_NOT_FOUND, true, ipUserStr ?? "User")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
return; return;
} }
foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser)) foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser))
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.IP_ADDRESS, false, ipUser.UserName, ip)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.IP_ADDRESS, false, ipUser.UserName, ip));
} }
} }
} }

View file

@ -1,6 +1,4 @@
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Threading;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class ShutdownRestartCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : IChatCommand { public class ShutdownRestartCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : IChatCommand {
@ -14,7 +12,8 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
if(ctx.User.UserId != 1) { if(ctx.User.UserId != 1) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); long msgId = ctx.Chat.RandomSnowflake.Next();
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }

View file

@ -1,7 +1,5 @@
using SharpChat.Events; using SharpChat.Events;
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Linq;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class WhisperCommand : IChatCommand { public class WhisperCommand : IChatCommand {
@ -11,8 +9,10 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(ctx.Args.Length < 2) { if(ctx.Args.Length < 2) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -20,7 +20,7 @@ namespace SharpChat.Commands {
ChatUser? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr)); ChatUser? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
if(whisperUser == null) { if(whisperUser == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, whisperUserStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USER_NOT_FOUND, true, whisperUserStr));
return; return;
} }
@ -28,7 +28,7 @@ namespace SharpChat.Commands {
return; return;
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), msgId,
ChatUser.GetDMChannelName(ctx.User, whisperUser), ChatUser.GetDMChannelName(ctx.User, whisperUser),
ctx.User.UserId, ctx.User.UserId,
ctx.User.UserName, ctx.User.UserName,

View file

@ -1,5 +1,4 @@
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Commands { namespace SharpChat.Commands {
@ -9,6 +8,7 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
StringBuilder whoChanSB = new(); StringBuilder whoChanSB = new();
string? whoChanStr = ctx.Args.FirstOrDefault(); string? whoChanStr = ctx.Args.FirstOrDefault();
@ -27,17 +27,17 @@ namespace SharpChat.Commands {
if(whoChanSB.Length > 2) if(whoChanSB.Length > 2)
whoChanSB.Length -= 2; whoChanSB.Length -= 2;
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_SERVER, false, whoChanSB)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB));
} else { } else {
ChatChannel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr)); ChatChannel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
if(whoChan is null) { if(whoChan is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, whoChanStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(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(ChatUserPermissions.JoinAnyChannel))) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_ERROR, true, whoChanStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USERS_LISTING_ERROR, true, whoChanStr));
return; return;
} }
@ -55,7 +55,7 @@ namespace SharpChat.Commands {
if(whoChanSB.Length > 2) if(whoChanSB.Length > 2)
whoChanSB.Length -= 2; whoChanSB.Length -= 2;
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_CHANNEL, false, whoChan.Name, whoChanSB)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(msgId, LCR.USERS_LISTING_CHANNEL, false, whoChan.Name, whoChanSB));
} }
} }
} }

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat.Config {
namespace SharpChat.Config {
public class CachedValue<T>(IConfig config, string name, TimeSpan lifetime, T? fallback) { public class CachedValue<T>(IConfig config, string name, TimeSpan lifetime, T? fallback) {
private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config)); private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
private string Name { get; } = name ?? throw new ArgumentNullException(nameof(name)); private string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat.Config {
namespace SharpChat.Config {
public abstract class ConfigException : Exception { public abstract class ConfigException : Exception {
public ConfigException(string message) : base(message) { } public ConfigException(string message) : base(message) { }
public ConfigException(string message, Exception ex) : base(message, ex) { } public ConfigException(string message, Exception ex) : base(message, ex) { }

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat.Config {
namespace SharpChat.Config {
public interface IConfig : IDisposable { public interface IConfig : IDisposable {
/// <summary> /// <summary>
/// Creates a proxy object that forces all names to start with the given prefix. /// Creates a proxy object that forces all names to start with the given prefix.

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat.Config {
namespace SharpChat.Config {
public class ScopedConfig(IConfig config, string prefix) : IConfig { public class ScopedConfig(IConfig config, string prefix) : IConfig {
private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config)); private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
private string Prefix { get; } = prefix ?? throw new ArgumentNullException(nameof(prefix)); private string Prefix { get; } = prefix ?? throw new ArgumentNullException(nameof(prefix));

View file

@ -1,7 +1,4 @@
using System; using System.Text;
using System.IO;
using System.Text;
using System.Threading;
namespace SharpChat.Config { namespace SharpChat.Config {
public class StreamConfig : IConfig { public class StreamConfig : IConfig {

View file

@ -1,7 +1,4 @@
using System.Collections.Generic; namespace SharpChat.EventStorage {
namespace SharpChat.EventStorage
{
public interface IEventStorage { public interface IEventStorage {
void AddEvent( void AddEvent(
long id, long id,

View file

@ -1,6 +1,4 @@
using MySqlConnector; using MySqlConnector;
using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;

View file

@ -1,5 +1,4 @@
using MySqlConnector; using MySqlConnector;
using System;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage {
public partial class MariaDBEventStorage { public partial class MariaDBEventStorage {

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat.EventStorage {
namespace SharpChat.EventStorage {
[Flags] [Flags]
public enum StoredEventFlags { public enum StoredEventFlags {
None = 0, None = 0,

View file

@ -1,5 +1,4 @@
using System; using System.Text.Json;
using System.Text.Json;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage {
public class StoredEventInfo( public class StoredEventInfo(

View file

@ -1,7 +1,4 @@
using System; using System.Text.Json;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage {
public class VirtualEventStorage : IEventStorage { public class VirtualEventStorage : IEventStorage {

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat.Events {
namespace SharpChat.Events {
public class MessageCreateEvent( public class MessageCreateEvent(
long msgId, long msgId,
string channelName, string channelName,

View file

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

View file

@ -1,5 +1,4 @@
using System; using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace SharpChat.Misuzu { namespace SharpChat.Misuzu {
public class MisuzuAuthInfo { public class MisuzuAuthInfo {

View file

@ -1,5 +1,4 @@
using System; using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace SharpChat.Misuzu { namespace SharpChat.Misuzu {
public class MisuzuBanInfo { public class MisuzuBanInfo {

View file

@ -1,13 +1,8 @@
using SharpChat.Config; using SharpChat.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
namespace SharpChat.Misuzu { namespace SharpChat.Misuzu {
public class MisuzuClient { public class MisuzuClient {

View file

@ -1,6 +1,4 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using System;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
@ -10,7 +8,7 @@ namespace SharpChat.Packet {
Banned, Banned,
} }
public class AuthFailPacket : ServerPacket { public class AuthFailPacket : IServerPacket {
public AuthFailReason Reason { get; private set; } public AuthFailReason Reason { get; private set; }
public MisuzuBanInfo? BanInfo { get; private set; } public MisuzuBanInfo? BanInfo { get; private set; }
@ -21,7 +19,7 @@ namespace SharpChat.Packet {
BanInfo = fbi ?? throw new ArgumentNullException(nameof(fbi)); BanInfo = fbi ?? throw new ArgumentNullException(nameof(fbi));
} }
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('1'); sb.Append('1');

View file

@ -1,17 +1,15 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class AuthSuccessPacket( public class AuthSuccessPacket(
ChatUser user, ChatUser user,
ChatChannel channel, ChatChannel channel,
int maxMsgLength int maxMsgLength
) : ServerPacket { ) : IServerPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user)); public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel)); public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('1'); sb.Append('1');

View file

@ -1,14 +1,14 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class BanListPacket(IEnumerable<MisuzuBanInfo> bans) : ServerPacket { public class BanListPacket(
long msgId,
IEnumerable<MisuzuBanInfo> bans
) : IServerPacket {
public IEnumerable<MisuzuBanInfo> Bans { get; private set; } = bans ?? throw new ArgumentNullException(nameof(bans)); public IEnumerable<MisuzuBanInfo> Bans { get; private set; } = bans ?? throw new ArgumentNullException(nameof(bans));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('2'); sb.Append('2');
@ -25,7 +25,7 @@ namespace SharpChat.Packet {
sb.Length -= 2; sb.Length -= 2;
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(msgId);
sb.Append("\t10010"); sb.Append("\t10010");
yield return sb.ToString(); yield return sb.ToString();

View file

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

View file

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

View file

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

View file

@ -1,6 +1,4 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ChatMessageAddPacket( public class ChatMessageAddPacket(
@ -10,10 +8,10 @@ namespace SharpChat.Packet {
string text, string text,
bool isAction, bool isAction,
bool isPrivate bool isPrivate
) : ServerPacket(msgId) { ) : IServerPacket {
public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text)); public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('2'); sb.Append('2');
@ -39,7 +37,7 @@ namespace SharpChat.Packet {
sb.Append("</i>"); sb.Append("</i>");
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(msgId);
sb.AppendFormat( sb.AppendFormat(
"\t1{0}0{1}{2}", "\t1{0}0{1}{2}",
isAction ? '1' : '0', isAction ? '1' : '0',

View file

@ -1,9 +1,8 @@
using System.Collections.Generic; using System.Text;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ChatMessageDeletePacket(long eventId) : ServerPacket { public class ChatMessageDeletePacket(long eventId) : IServerPacket {
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('6'); sb.Append('6');

View file

@ -1,13 +1,10 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ContextChannelsPacket(IEnumerable<ChatChannel> channels) : ServerPacket { public class ContextChannelsPacket(IEnumerable<ChatChannel> channels) : IServerPacket {
public IEnumerable<ChatChannel> Channels { get; private set; } = channels?.Where(c => c != null) ?? throw new ArgumentNullException(nameof(channels)); public IEnumerable<ChatChannel> Channels { get; private set; } = channels?.Where(c => c != null) ?? throw new ArgumentNullException(nameof(channels));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('7'); sb.Append('7');

View file

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Text;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public enum ContextClearMode { public enum ContextClearMode {
@ -10,8 +9,8 @@ namespace SharpChat.Packet {
MessagesUsersChannels = 4, MessagesUsersChannels = 4,
} }
public class ContextClearPacket(ContextClearMode mode) : ServerPacket { public class ContextClearPacket(ContextClearMode mode) : IServerPacket {
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('8'); sb.Append('8');

View file

@ -1,16 +1,14 @@
using SharpChat.EventStorage; using SharpChat.EventStorage;
using System;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet namespace SharpChat.Packet
{ {
public class ContextMessagePacket(StoredEventInfo evt, bool notify = false) : ServerPacket { public class ContextMessagePacket(StoredEventInfo evt, bool notify = false) : IServerPacket {
public StoredEventInfo Event { get; private set; } = evt ?? throw new ArgumentNullException(nameof(evt)); public StoredEventInfo Event { get; private set; } = evt ?? throw new ArgumentNullException(nameof(evt));
private const string V1_CHATBOT = "-1\tChatBot\tinherit\t\t"; private const string V1_CHATBOT = "-1\tChatBot\tinherit\t\t";
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
bool isAction = Event.Flags.HasFlag(StoredEventFlags.Action); bool isAction = Event.Flags.HasFlag(StoredEventFlags.Action);
bool isBroadcast = Event.Flags.HasFlag(StoredEventFlags.Broadcast); bool isBroadcast = Event.Flags.HasFlag(StoredEventFlags.Broadcast);
bool isPrivate = Event.Flags.HasFlag(StoredEventFlags.Private); bool isPrivate = Event.Flags.HasFlag(StoredEventFlags.Private);
@ -98,7 +96,7 @@ namespace SharpChat.Packet
} }
sb.Append('\t'); sb.Append('\t');
sb.Append(Event.Id < 1 ? SequenceId : Event.Id); sb.Append(Event.Id);
sb.Append('\t'); sb.Append('\t');
sb.Append(notify ? '1' : '0'); sb.Append(notify ? '1' : '0');
sb.AppendFormat( sb.AppendFormat(

View file

@ -1,13 +1,10 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class ContextUsersPacket(IEnumerable<ChatUser> users) : ServerPacket { public class ContextUsersPacket(IEnumerable<ChatUser> users) : IServerPacket {
public IEnumerable<ChatUser> Users { get; private set; } = users?.Where(u => u != null) ?? throw new ArgumentNullException(nameof(users)); public IEnumerable<ChatUser> Users { get; private set; } = users?.Where(u => u != null) ?? throw new ArgumentNullException(nameof(users));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('7'); sb.Append('7');

View file

@ -1,6 +1,4 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public enum ForceDisconnectReason { public enum ForceDisconnectReason {
@ -8,7 +6,7 @@ namespace SharpChat.Packet {
Banned = 1, Banned = 1,
} }
public class ForceDisconnectPacket : ServerPacket { public class ForceDisconnectPacket : IServerPacket {
public ForceDisconnectReason Reason { get; private set; } public ForceDisconnectReason Reason { get; private set; }
public DateTimeOffset Expires { get; private set; } public DateTimeOffset Expires { get; private set; }
@ -22,7 +20,7 @@ namespace SharpChat.Packet {
} }
} }
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('9'); sb.Append('9');

View file

@ -1,16 +1,15 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class LegacyCommandResponse( public class LegacyCommandResponse(
long msgId,
string stringId, string stringId,
bool isError = true, bool isError = true,
params object[] args params object[] args
) : ServerPacket { ) : IServerPacket {
public string StringId { get; private set; } = stringId ?? throw new ArgumentNullException(nameof(stringId)); public string StringId { get; private set; } = stringId ?? throw new ArgumentNullException(nameof(stringId));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
if(StringId == LCR.WELCOME) { if(StringId == LCR.WELCOME) {
@ -43,7 +42,7 @@ namespace SharpChat.Packet {
sb.Append(StringId); sb.Append(StringId);
sb.Append("\t0"); sb.Append("\t0");
} else } else
sb.Append(SequenceId); sb.Append(msgId);
sb.Append("\t10010"); sb.Append("\t10010");
/*sb.AppendFormat( /*sb.AppendFormat(

View file

@ -1,8 +1,6 @@
using System.Collections.Generic; namespace SharpChat.Packet {
public class PongPacket : IServerPacket {
namespace SharpChat.Packet { public IEnumerable<string> Pack() {
public class PongPacket : ServerPacket {
public override IEnumerable<string> Pack() {
yield return "0\tpong"; yield return "0\tpong";
} }
} }

View file

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

View file

@ -1,12 +1,10 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserChannelJoinPacket(ChatUser user) : ServerPacket { public class UserChannelJoinPacket(long msgId, ChatUser user) : IServerPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user)); public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('5'); sb.Append('5');
@ -15,7 +13,7 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append(User.Pack()); sb.Append(User.Pack());
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(msgId);
yield return sb.ToString(); yield return sb.ToString();
} }

View file

@ -1,12 +1,10 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserChannelLeavePacket(ChatUser user) : ServerPacket { public class UserChannelLeavePacket(long msgId, ChatUser user) : IServerPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user)); public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('5'); sb.Append('5');
@ -15,7 +13,7 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append(User.UserId); sb.Append(User.UserId);
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(msgId);
yield return sb.ToString(); yield return sb.ToString();
} }

View file

@ -1,12 +1,10 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserConnectPacket(DateTimeOffset joined, ChatUser user) : ServerPacket { public class UserConnectPacket(long msgId, DateTimeOffset joined, ChatUser user) : IServerPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user)); public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('1'); sb.Append('1');
@ -15,7 +13,7 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append(User.Pack()); sb.Append(User.Pack());
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(msgId);
yield return sb.ToString(); yield return sb.ToString();
} }

View file

@ -1,6 +1,4 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public enum UserDisconnectReason { public enum UserDisconnectReason {
@ -10,10 +8,15 @@ namespace SharpChat.Packet {
Flood, Flood,
} }
public class UserDisconnectPacket(DateTimeOffset disconnected, ChatUser user, UserDisconnectReason reason) : ServerPacket { public class UserDisconnectPacket(
long msgId,
DateTimeOffset disconnected,
ChatUser user,
UserDisconnectReason reason
) : IServerPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user)); public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('3'); sb.Append('3');
@ -42,7 +45,7 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append(disconnected.ToUnixTimeSeconds()); sb.Append(disconnected.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(msgId);
yield return sb.ToString(); yield return sb.ToString();
} }

View file

@ -1,12 +1,10 @@
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet {
public class UserUpdatePacket(ChatUser user, string previousName = "") : ServerPacket { public class UserUpdatePacket(long msgId, ChatUser user, string previousName = "") : IServerPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user)); public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public override IEnumerable<string> Pack() { public IEnumerable<string> Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
bool isSilent = string.IsNullOrEmpty(previousName); bool isSilent = string.IsNullOrEmpty(previousName);
@ -20,7 +18,7 @@ namespace SharpChat.Packet {
sb.Append('\f'); sb.Append('\f');
sb.Append(User.LegacyNameWithStatus); sb.Append(User.LegacyNameWithStatus);
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(msgId);
sb.Append("\t10010"); sb.Append("\t10010");
yield return sb.ToString(); yield return sb.ToString();
sb.Clear(); sb.Clear();

View file

@ -1,11 +1,6 @@
using SharpChat.Config; using SharpChat.Config;
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace SharpChat.PacketHandlers { namespace SharpChat.PacketHandlers {
public class AuthHandler( public class AuthHandler(
@ -121,14 +116,14 @@ namespace SharpChat.PacketHandlers {
ctx.Connection.BumpPing(); ctx.Connection.BumpPing();
ctx.Connection.User = user; ctx.Connection.User = user;
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!")); ctx.Connection.Send(new LegacyCommandResponse(0, LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!"));
if(File.Exists("welcome.txt")) { if(File.Exists("welcome.txt")) {
IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x)); IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));
string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count())); string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
if(!string.IsNullOrWhiteSpace(line)) if(!string.IsNullOrWhiteSpace(line))
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, line)); ctx.Connection.Send(new LegacyCommandResponse(0, LCR.WELCOME, false, line));
} }
ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength); ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);

View file

@ -1,8 +1,5 @@
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.Packet; using SharpChat.Packet;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace SharpChat.PacketHandlers { namespace SharpChat.PacketHandlers {
public class PingHandler(MisuzuClient msz) : IChatPacketHandler { public class PingHandler(MisuzuClient msz) : IChatPacketHandler {

View file

@ -1,13 +1,14 @@
using SharpChat.Config; using SharpChat.Config;
using SharpChat.Events; using SharpChat.Events;
using System; using SharpChat.Snowflake;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text; using System.Text;
namespace SharpChat.PacketHandlers { namespace SharpChat.PacketHandlers {
public class SendMessageHandler(CachedValue<int> maxMsgLength) : IChatPacketHandler { public class SendMessageHandler(
RandomSnowflake randomSnowflake,
CachedValue<int> maxMsgLength
) : IChatPacketHandler {
private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength)); private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
private List<IChatCommand> Commands { get; } = []; private List<IChatCommand> Commands { get; } = [];
@ -68,7 +69,7 @@ namespace SharpChat.PacketHandlers {
} }
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(), randomSnowflake.Next(),
channel.Name, channel.Name,
user.UserId, user.UserId,
user.UserName, user.UserName,

View file

@ -1,11 +1,7 @@
using SharpChat.Config; using SharpChat.Config;
using SharpChat.EventStorage; using SharpChat.EventStorage;
using SharpChat.Misuzu; using SharpChat.Misuzu;
using System;
using System.IO;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading;
namespace SharpChat { namespace SharpChat {
public class Program { public class Program {

View file

@ -1,5 +1,4 @@
using System; using System.Buffers;
using System.Buffers;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace SharpChat { namespace SharpChat {

View file

@ -1,6 +1,4 @@
using System; namespace SharpChat {
namespace SharpChat {
public class RateLimiter { public class RateLimiter {
private readonly int Size; private readonly int Size;
private readonly int MinimumDelay; private readonly int MinimumDelay;

View file

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@ -31,4 +32,8 @@
<EmbeddedResource Include="version.txt" /> <EmbeddedResource Include="version.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SharpChatCommon\SharpChatCommon.csproj" />
</ItemGroup>
</Project> </Project>

View file

@ -1,9 +1,6 @@
#nullable disable #nullable disable
using Fleck; using Fleck;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;

View file

@ -1,13 +0,0 @@
using System;
using System.Threading;
namespace SharpChat {
public static class SharpId {
private const long EPOCH = 1588377600000;
private static int Counter = 0;
public static long Next()
=> ((DateTimeOffset.Now.ToUnixTimeMilliseconds() - EPOCH) << 8)
| (ushort)(Interlocked.Increment(ref Counter) & 0xFFFF);
}
}

View file

@ -1,5 +1,4 @@
using System.IO; using System.Reflection;
using System.Reflection;
using System.Text; using System.Text;
namespace SharpChat { namespace SharpChat {

View file

@ -5,11 +5,6 @@ using SharpChat.EventStorage;
using SharpChat.Misuzu; using SharpChat.Misuzu;
using SharpChat.Packet; using SharpChat.Packet;
using SharpChat.PacketHandlers; using SharpChat.PacketHandlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
namespace SharpChat { namespace SharpChat {
public class SockChatServer : IDisposable { public class SockChatServer : IDisposable {
@ -79,7 +74,7 @@ namespace SharpChat {
AuthedHandlers.AddRange([ AuthedHandlers.AddRange([
new PingHandler(Misuzu), new PingHandler(Misuzu),
SendMessageHandler = new SendMessageHandler(MaxMessageLength), SendMessageHandler = new SendMessageHandler(Context.RandomSnowflake, MaxMessageLength),
]); ]);
SendMessageHandler.AddCommands([ SendMessageHandler.AddCommands([
@ -184,7 +179,7 @@ namespace SharpChat {
if(banUser is not null) { if(banUser is not null) {
if(banDuration == TimeSpan.MinValue) { if(banDuration == TimeSpan.MinValue) {
Context.SendTo(conn.User, new LegacyCommandResponse(LCR.FLOOD_WARN, false)); Context.SendTo(conn.User, new LegacyCommandResponse(Context.RandomSnowflake.Next(), LCR.FLOOD_WARN, false));
} else { } else {
Context.BanUser(conn.User, banDuration, UserDisconnectReason.Flood); Context.BanUser(conn.User, banDuration, UserDisconnectReason.Flood);

View file

@ -1,5 +1,4 @@
using System; using System.Diagnostics;
using System.Diagnostics;
using System.Text; using System.Text;
namespace SharpChat { namespace SharpChat {

View file

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>SharpChat</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,13 @@
using System.Security.Cryptography;
namespace SharpChat.Snowflake {
public class RandomSnowflake(
SnowflakeGenerator? generator = null
) {
public readonly SnowflakeGenerator Generator = generator ?? new SnowflakeGenerator();
public long Next(DateTimeOffset? at = null) {
return Generator.Next(Math.Abs(BitConverter.ToInt64(RandomNumberGenerator.GetBytes(8))), at);
}
}
}

View file

@ -0,0 +1,35 @@
namespace SharpChat.Snowflake {
public class SnowflakeGenerator {
public const long MASK = 0x7FFFFFFFFFFFFFFF;
// previous default epoch was 1588377600000, but snowflakes are much larger than SharpIds
public const long EPOCH = 1356998400000;
public const byte SHIFT = 16;
public readonly long Epoch;
public readonly byte Shift;
public readonly long TimestampMask;
public readonly long SequenceMask;
public SnowflakeGenerator(long epoch = EPOCH, byte shift = SHIFT) {
if(epoch is < 0 or > MASK)
throw new ArgumentException("Epoch must be a positive int64.", nameof(epoch));
if(shift is < 1 or > 63)
throw new ArgumentException("Shift must be between or equal to 1 and 63", nameof(shift));
Epoch = epoch;
Shift = shift;
// i think Index only does this as a hack for how integers work in PHP but its gonna run Once per application instance lol
TimestampMask = ~(~0L << (63 - shift));
SequenceMask = ~(~0L << shift);
}
public long Now(DateTimeOffset? at = null) {
return Math.Max(0, (at ?? DateTimeOffset.UtcNow).ToUnixTimeMilliseconds() - Epoch);
}
public long Next(long sequence, DateTimeOffset? at = null) {
return ((Now(at) & TimestampMask) << Shift) | (sequence & SequenceMask);
}
}
}