Cleaned up S2C packet classes.

This commit is contained in:
flash 2025-04-25 22:14:48 +00:00
parent 9f283e48fe
commit b8ec381f3b
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
19 changed files with 130 additions and 203 deletions

View file

@ -23,14 +23,14 @@ namespace SharpChat.C2SPacketHandlers {
string? authMethod = args.ElementAtOrDefault(1); string? authMethod = args.ElementAtOrDefault(1);
if(string.IsNullOrWhiteSpace(authMethod)) { if(string.IsNullOrWhiteSpace(authMethod)) {
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
return; return;
} }
string? authToken = args.ElementAtOrDefault(2); string? authToken = args.ElementAtOrDefault(2);
if(string.IsNullOrWhiteSpace(authToken)) { if(string.IsNullOrWhiteSpace(authToken)) {
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
return; return;
} }
@ -49,7 +49,7 @@ namespace SharpChat.C2SPacketHandlers {
fai = await Misuzu.AuthVerifyAsync(authMethod, authToken, ipAddr); fai = await Misuzu.AuthVerifyAsync(authMethod, authToken, ipAddr);
} catch(Exception ex) { } catch(Exception ex) {
Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}"); Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
#if DEBUG #if DEBUG
throw; throw;
@ -60,7 +60,7 @@ namespace SharpChat.C2SPacketHandlers {
if(fai?.Success != true) { if(fai?.Success != true) {
Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai?.Reason ?? "unknown"}"); Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai?.Reason ?? "unknown"}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
return; return;
} }
@ -70,7 +70,7 @@ namespace SharpChat.C2SPacketHandlers {
fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr); fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr);
} catch(Exception ex) { } catch(Exception ex) {
Logger.Write($"<{ctx.Connection.Id}> Failed auth ban check: {ex}"); Logger.Write($"<{ctx.Connection.Id}> Failed auth ban check: {ex}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
#if DEBUG #if DEBUG
throw; throw;
@ -81,7 +81,7 @@ namespace SharpChat.C2SPacketHandlers {
if(fbi?.IsBanned == true && !fbi.HasExpired) { if(fbi?.IsBanned == true && !fbi.HasExpired) {
Logger.Write($"<{ctx.Connection.Id}> User is banned."); Logger.Write($"<{ctx.Connection.Id}> User is banned.");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailReason.Banned, fbi)); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Banned, fbi.IsPermanent ? DateTimeOffset.MaxValue : fbi.ExpiresAt));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
return; return;
} }
@ -109,7 +109,7 @@ namespace SharpChat.C2SPacketHandlers {
// Enforce a maximum amount of connections per user // Enforce a maximum amount of connections per user
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) { if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailReason.MaxSessions)); ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
return; return;
} }

View file

@ -93,7 +93,7 @@ namespace SharpChat {
foreach(ChatUser user in Users) foreach(ChatUser user in Users)
if(!Connections.Any(conn => conn.User == user)) { if(!Connections.Any(conn => conn.User == user)) {
HandleDisconnect(user, UserDisconnectReason.TimeOut); HandleDisconnect(user, UserDisconnectS2CPacket.Reason.TimeOut);
Logger.Write($"Timed out {user} (no more connections)."); Logger.Write($"Timed out {user} (no more connections).");
} }
} }
@ -191,12 +191,12 @@ namespace SharpChat {
} }
} }
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) { public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Kicked) {
if(duration > TimeSpan.Zero) { if(duration > TimeSpan.Zero) {
DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration; DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration;
SendTo(user, new ForceDisconnectS2CPacket(ForceDisconnectReason.Banned, expires)); SendTo(user, new ForceDisconnectS2CPacket(expires));
} else } else
SendTo(user, new ForceDisconnectS2CPacket(ForceDisconnectReason.Kicked)); SendTo(user, new ForceDisconnectS2CPacket());
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(conn.User == user) if(conn.User == user)
@ -248,7 +248,7 @@ namespace SharpChat {
UserLastChannel[user.UserId] = chan; UserLastChannel[user.UserId] = chan;
} }
public void HandleDisconnect(ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) { public void HandleDisconnect(ChatUser user, UserDisconnectS2CPacket.Reason reason = UserDisconnectS2CPacket.Reason.Leave) {
UpdateUser(user, status: ChatUserStatus.Offline); UpdateUser(user, status: ChatUserStatus.Offline);
Users.Remove(user); Users.Remove(user);
UserLastChannel.Remove(user.UserId); UserLastChannel.Remove(user.UserId);
@ -259,7 +259,7 @@ namespace SharpChat {
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name)); ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
long msgId = RandomSnowflake.Next(); long msgId = RandomSnowflake.Next();
SendTo(chan, new UserDisconnectS2CPacket(msgId, DateTimeOffset.Now, user, reason)); SendTo(chan, new UserDisconnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, 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); 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))
@ -297,14 +297,14 @@ namespace SharpChat {
ChatChannel oldChan = UserLastChannel[user.UserId]; ChatChannel oldChan = UserLastChannel[user.UserId];
long leaveId = RandomSnowflake.Next(); long leaveId = RandomSnowflake.Next();
SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user)); SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user.UserId));
Events.AddEvent(leaveId, "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log); Events.AddEvent(leaveId, "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
long joinId = RandomSnowflake.Next(); long joinId = RandomSnowflake.Next();
SendTo(chan, new UserChannelJoinS2CPacket(joinId, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions)); SendTo(chan, new UserChannelJoinS2CPacket(joinId, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
Events.AddEvent(joinId, "chan:join", chan.Name, user.UserId, user.LegacyName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log); Events.AddEvent(joinId, "chan:join", chan.Name, user.UserId, user.LegacyName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
SendTo(user, new ContextClearS2CPacket(ContextClearMode.MessagesUsers)); SendTo(user, new ContextClearS2CPacket(ContextClearS2CPacket.Mode.MessagesUsers));
SendTo(user, new ContextUsersS2CPacket( SendTo(user, new ContextUsersS2CPacket(
GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank) GetChannelUsers(chan).Except([user]).OrderByDescending(u => u.Rank)
.Select(u => new ContextUsersS2CPacket.Entry( .Select(u => new ContextUsersS2CPacket.Entry(
@ -377,7 +377,7 @@ namespace SharpChat {
if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan)) if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
throw new ArgumentException("no channel???"); throw new ArgumentException("no channel???");
SendTo(user, new UserChannelForceJoinS2CPacket(chan)); SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
} }
public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string? password = null) { public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
@ -417,7 +417,7 @@ namespace SharpChat {
// Broadcast deletion of channel // Broadcast deletion of channel
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank))
SendTo(user, new ChannelDeleteS2CPacket(channel)); SendTo(user, new ChannelDeleteS2CPacket(channel.Name));
} }
} }
} }

View file

@ -19,12 +19,18 @@ namespace SharpChat.Commands {
} }
Task.Run(async () => { Task.Run(async () => {
MisuzuBanInfo[]? mbi = await Misuzu.GetBanListAsync(); MisuzuBanInfo[]? mbis = await Misuzu.GetBanListAsync();
if(mbis is null)
if(mbi is null)
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true)); ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true));
else else
ctx.Chat.SendTo(ctx.User, new BanListS2CPacket(msgId, mbi)); ctx.Chat.SendTo(ctx.User, new BanListS2CPacket(
msgId,
mbis.Where(mbi => mbi.IsBanned && !mbi.HasExpired)
.Select(mbi => new BanListS2CPacket.Entry(
BanListS2CPacket.Type.UserName, // Misuzu currently only does username bans so we can just do this
mbi.UserName ?? $"({mbi.UserId})"
))
));
}).Wait(); }).Wait();
} }
} }

View file

@ -1,52 +1,38 @@
using SharpChat.Misuzu; using System.Text;
using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public enum AuthFailReason { public class AuthFailS2CPacket(
AuthInvalid, AuthFailS2CPacket.Reason reason,
MaxSessions, DateTimeOffset? expiresAt = null
Banned, ) : S2CPacket {
} public enum Reason {
AuthInvalid,
public class AuthFailS2CPacket : S2CPacket { MaxSessions,
public AuthFailReason Reason { get; private set; } Banned,
public MisuzuBanInfo? BanInfo { get; private set; }
public AuthFailS2CPacket(AuthFailReason reason, MisuzuBanInfo? fbi = null) {
Reason = reason;
if(reason == AuthFailReason.Banned)
BanInfo = fbi ?? throw new ArgumentNullException(nameof(fbi));
} }
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('1'); sb.Append("1\tn\t");
sb.Append("\tn\t");
switch(Reason) { switch(reason) {
case AuthFailReason.AuthInvalid: case Reason.AuthInvalid:
default: default:
sb.Append("authfail"); sb.Append("authfail");
break; break;
case AuthFailReason.MaxSessions: case Reason.MaxSessions:
sb.Append("sockfail"); sb.Append("sockfail");
break; break;
case AuthFailReason.Banned: case Reason.Banned:
sb.Append("joinfail"); sb.Append("joinfail\t");
if(expiresAt is null || expiresAt == DateTimeOffset.MaxValue)
sb.Append("-1");
else
sb.Append(expiresAt.Value.ToUnixTimeSeconds());
break; break;
} }
if(Reason == AuthFailReason.Banned) {
sb.Append('\t');
if(BanInfo?.IsPermanent == true)
sb.Append("-1");
else
sb.Append(BanInfo?.ExpiresAt.ToUnixTimeSeconds() ?? 0);
}
return sb.ToString(); return sb.ToString();
} }
} }

View file

@ -1,29 +1,28 @@
using SharpChat.Misuzu; using System.Text;
using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public class BanListS2CPacket( public class BanListS2CPacket(
long msgId, long msgId,
IEnumerable<MisuzuBanInfo> bans IEnumerable<BanListS2CPacket.Entry> entries
) : S2CPacket { ) : S2CPacket {
public IEnumerable<MisuzuBanInfo> Bans { get; private set; } = bans ?? throw new ArgumentNullException(nameof(bans)); public enum Type {
UserName,
IPAddress,
}
public record Entry(Type type, string value);
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('2'); sb.Append("2\t");
sb.Append('\t');
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds()); sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
sb.Append("\t-1\t0\fbanlist\f"); sb.Append("\t-1\t0\fbanlist\f");
sb.Append(string.Join(", ", entries.Select(entry => string.Format(
foreach(MisuzuBanInfo ban in Bans) { @"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('{0} '+ this.innerHTML);"">{1}</a>",
string banStr = string.IsNullOrEmpty(ban.UserName) ? (ban.RemoteAddress ?? "::") : (ban.UserName ?? $"({ban.UserId})"); entry.type == Type.IPAddress ? "/unbanip" : "/unban",
sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", banStr); entry.value
} ))));
if(Bans.Any())
sb.Length -= 2;
sb.Append('\t'); sb.Append('\t');
sb.Append(msgId); sb.Append(msgId);
sb.Append("\t10010"); sb.Append("\t10010");

View file

@ -1,17 +1,14 @@
using System.Text; using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public class ChannelDeleteS2CPacket(ChatChannel channel) : S2CPacket { public class ChannelDeleteS2CPacket(
public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel)); string channelName
) : S2CPacket {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('4'); sb.Append("4\t2\t");
sb.Append('\t'); sb.Append(channelName);
sb.Append('2');
sb.Append('\t');
sb.Append(Channel.Name);
return sb.ToString(); return sb.ToString();
} }

View file

@ -10,10 +10,7 @@ namespace SharpChat.S2CPackets {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('4'); sb.Append("4\t1\t");
sb.Append('\t');
sb.Append('1');
sb.Append('\t');
sb.Append(previousName); sb.Append(previousName);
sb.Append('\t'); sb.Append('\t');
sb.Append(newName); sb.Append(newName);

View file

@ -9,13 +9,10 @@ namespace SharpChat.S2CPackets {
bool isAction, bool isAction,
bool isPrivate bool isPrivate
) : S2CPacket { ) : S2CPacket {
public string Text { get; } = text ?? throw new ArgumentNullException(nameof(text));
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('2'); sb.Append("2\t");
sb.Append('\t');
sb.Append(created.ToUnixTimeSeconds()); sb.Append(created.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
@ -27,7 +24,7 @@ namespace SharpChat.S2CPackets {
sb.Append("<i>"); sb.Append("<i>");
sb.Append( sb.Append(
Text.Replace("<", "&lt;") text.Replace("<", "&lt;")
.Replace(">", "&gt;") .Replace(">", "&gt;")
.Replace("\n", " <br/> ") .Replace("\n", " <br/> ")
.Replace("\t", " ") .Replace("\t", " ")

View file

@ -5,8 +5,7 @@ namespace SharpChat.S2CPackets {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('6'); sb.Append("6\t");
sb.Append('\t');
sb.Append(eventId); sb.Append(eventId);
return sb.ToString(); return sb.ToString();

View file

@ -7,28 +7,22 @@ namespace SharpChat.S2CPackets {
bool isError = true, bool isError = true,
params object[] args params object[] args
) : S2CPacket { ) : S2CPacket {
public string StringId { get; private set; } = stringId ?? throw new ArgumentNullException(nameof(stringId));
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
if(StringId == LCR.WELCOME) { if(stringId == LCR.WELCOME) {
sb.Append('7'); sb.Append("7\t1\t");
sb.Append('\t');
sb.Append('1');
sb.Append('\t');
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds()); sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
sb.Append("\t-1\tChatBot\tinherit\t\t"); sb.Append("\t-1\tChatBot\tinherit\t\t");
} else { } else {
sb.Append('2'); sb.Append("2\t");
sb.Append('\t');
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds()); sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
sb.Append("\t-1\t"); sb.Append("\t-1\t");
} }
sb.Append(isError ? '1' : '0'); sb.Append(isError ? '1' : '0');
sb.Append('\f'); sb.Append('\f');
sb.Append(StringId == LCR.WELCOME ? LCR.BROADCAST : StringId); sb.Append(stringId == LCR.WELCOME ? LCR.BROADCAST : stringId);
if(args.Length > 0) if(args.Length > 0)
foreach(object arg in args) { foreach(object arg in args) {
@ -38,8 +32,8 @@ namespace SharpChat.S2CPackets {
sb.Append('\t'); sb.Append('\t');
if(StringId == LCR.WELCOME) { if(stringId == LCR.WELCOME) {
sb.Append(StringId); sb.Append(stringId);
sb.Append("\t0"); sb.Append("\t0");
} else } else
sb.Append(msgId); sb.Append(msgId);

View file

@ -7,10 +7,7 @@ namespace SharpChat.S2CPackets {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('7'); sb.Append("7\t2\t");
sb.Append('\t');
sb.Append('2');
sb.Append('\t');
sb.Append(entries.Count()); sb.Append(entries.Count());
foreach(Entry entry in entries) { foreach(Entry entry in entries) {

View file

@ -1,20 +1,19 @@
using System.Text; using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public enum ContextClearMode { public class ContextClearS2CPacket(ContextClearS2CPacket.Mode mode) : S2CPacket {
Messages = 0, public enum Mode {
Users = 1, Messages = 0,
Channels = 2, Users = 1,
MessagesUsers = 3, Channels = 2,
MessagesUsersChannels = 4, MessagesUsers = 3,
} MessagesUsersChannels = 4,
}
public class ContextClearS2CPacket(ContextClearMode mode) : S2CPacket {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('8'); sb.Append("8\t");
sb.Append('\t');
sb.Append((int)mode); sb.Append((int)mode);
return sb.ToString(); return sb.ToString();

View file

@ -6,8 +6,6 @@ namespace SharpChat.S2CPackets
public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) : S2CPacket { public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) : S2CPacket {
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";
public string Pack() { public 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);
@ -15,10 +13,7 @@ namespace SharpChat.S2CPackets
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('7'); sb.Append("7\t1\t");
sb.Append('\t');
sb.Append('1');
sb.Append('\t');
sb.Append(Event.Created.ToUnixTimeSeconds()); sb.Append(Event.Created.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
@ -26,8 +21,7 @@ namespace SharpChat.S2CPackets
case "msg:add": case "msg:add":
case "SharpChat.Events.ChatMessage": case "SharpChat.Events.ChatMessage":
if(isBroadcast || Event.Sender is null) { if(isBroadcast || Event.Sender is null) {
sb.Append(V1_CHATBOT); sb.Append("-1\tChatBot\tinherit\t\t0\fsay\f");
sb.Append("0\fsay\f");
} else { } else {
sb.Append(Event.Sender.UserId); sb.Append(Event.Sender.UserId);
sb.Append('\t'); sb.Append('\t');
@ -64,41 +58,37 @@ namespace SharpChat.S2CPackets
case "user:connect": case "user:connect":
case "SharpChat.Events.UserConnectEvent": case "SharpChat.Events.UserConnectEvent":
sb.Append(V1_CHATBOT); sb.Append("-1\tChatBot\tinherit\t\t0\fjoin\f");
sb.Append("0\fjoin\f");
sb.Append(Event.Sender?.LegacyName ?? "?????"); sb.Append(Event.Sender?.LegacyName ?? "?????");
break; break;
case "chan:join": case "chan:join":
case "SharpChat.Events.UserChannelJoinEvent": case "SharpChat.Events.UserChannelJoinEvent":
sb.Append(V1_CHATBOT); sb.Append("-1\tChatBot\tinherit\t\t0\fjchan\f");
sb.Append("0\fjchan\f");
sb.Append(Event.Sender?.LegacyName ?? "?????"); sb.Append(Event.Sender?.LegacyName ?? "?????");
break; break;
case "chan:leave": case "chan:leave":
case "SharpChat.Events.UserChannelLeaveEvent": case "SharpChat.Events.UserChannelLeaveEvent":
sb.Append(V1_CHATBOT); sb.Append("-1\tChatBot\tinherit\t\t0\flchan\f");
sb.Append("0\flchan\f");
sb.Append(Event.Sender?.LegacyName ?? "?????"); sb.Append(Event.Sender?.LegacyName ?? "?????");
break; break;
case "user:disconnect": case "user:disconnect":
case "SharpChat.Events.UserDisconnectEvent": case "SharpChat.Events.UserDisconnectEvent":
sb.Append(V1_CHATBOT); sb.Append("-1\tChatBot\tinherit\t\t0\f");
sb.Append("0\f");
switch((UserDisconnectReason)Event.Data.RootElement.GetProperty("reason").GetByte()) { switch((UserDisconnectS2CPacket.Reason)Event.Data.RootElement.GetProperty("reason").GetByte()) {
case UserDisconnectReason.Flood: case UserDisconnectS2CPacket.Reason.Flood:
sb.Append("flood"); sb.Append("flood");
break; break;
case UserDisconnectReason.Kicked: case UserDisconnectS2CPacket.Reason.Kicked:
sb.Append("kick"); sb.Append("kick");
break; break;
case UserDisconnectReason.TimeOut: case UserDisconnectS2CPacket.Reason.TimeOut:
sb.Append("timeout"); sb.Append("timeout");
break; break;
case UserDisconnectReason.Leave: case UserDisconnectS2CPacket.Reason.Leave:
default: default:
sb.Append("leave"); sb.Append("leave");
break; break;

View file

@ -7,10 +7,7 @@ namespace SharpChat.S2CPackets {
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('7'); sb.Append("7\t0\t");
sb.Append('\t');
sb.Append('0');
sb.Append('\t');
sb.Append(entries.Count()); sb.Append(entries.Count());
foreach(Entry entry in entries) { foreach(Entry entry in entries) {

View file

@ -1,40 +1,21 @@
using System.Text; using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public enum ForceDisconnectReason { public class ForceDisconnectS2CPacket(DateTimeOffset? expires = null) : S2CPacket {
Kicked = 0,
Banned = 1,
}
public class ForceDisconnectS2CPacket : S2CPacket {
public ForceDisconnectReason Reason { get; private set; }
public DateTimeOffset Expires { get; private set; }
public ForceDisconnectS2CPacket(ForceDisconnectReason reason, DateTimeOffset? expires = null) {
Reason = reason;
if(reason == ForceDisconnectReason.Banned) {
if(!expires.HasValue)
throw new ArgumentNullException(nameof(expires));
Expires = expires.Value;
}
}
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('9'); sb.Append("9\t");
sb.Append('\t');
sb.Append((int)Reason);
if(Reason == ForceDisconnectReason.Banned) { if(expires.HasValue && expires.Value > DateTimeOffset.UtcNow) {
sb.Append('\t'); sb.Append("1\t");
if(Expires.Year >= 2100) if(expires.Value < DateTimeOffset.MaxValue)
sb.Append("-1"); sb.Append(expires.Value.ToUnixTimeSeconds());
else else
sb.Append(Expires.ToUnixTimeSeconds()); sb.Append("-1");
} } else
sb.Append('0');
return sb.ToString(); return sb.ToString();
} }
} }

View file

@ -1,17 +1,12 @@
using System.Text; using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public class UserChannelForceJoinS2CPacket(ChatChannel channel) : S2CPacket { public class UserChannelForceJoinS2CPacket(string channelName) : S2CPacket {
public ChatChannel Channel { get; private set; } = channel ?? throw new ArgumentNullException(nameof(channel));
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('5'); sb.Append("5\t2\t");
sb.Append('\t'); sb.Append(channelName);
sb.Append('2');
sb.Append('\t');
sb.Append(Channel.Name);
return sb.ToString(); return sb.ToString();
} }

View file

@ -1,17 +1,12 @@
using System.Text; using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public class UserChannelLeaveS2CPacket(long msgId, ChatUser user) : S2CPacket { public class UserChannelLeaveS2CPacket(long msgId, long userId) : S2CPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user));
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('5'); sb.Append("5\t1\t");
sb.Append('\t'); sb.Append(userId);
sb.Append('1');
sb.Append('\t');
sb.Append(User.UserId);
sb.Append('\t'); sb.Append('\t');
sb.Append(msgId); sb.Append(msgId);

View file

@ -1,43 +1,41 @@
using System.Text; using System.Text;
namespace SharpChat.S2CPackets { namespace SharpChat.S2CPackets {
public enum UserDisconnectReason {
Leave,
TimeOut,
Kicked,
Flood,
}
public class UserDisconnectS2CPacket( public class UserDisconnectS2CPacket(
long msgId, long msgId,
DateTimeOffset disconnected, DateTimeOffset disconnected,
ChatUser user, long userId,
UserDisconnectReason reason string userName,
UserDisconnectS2CPacket.Reason reason
) : S2CPacket { ) : S2CPacket {
public ChatUser User { get; private set; } = user ?? throw new ArgumentNullException(nameof(user)); public enum Reason {
Leave,
TimeOut,
Kicked,
Flood,
}
public string Pack() { public string Pack() {
StringBuilder sb = new(); StringBuilder sb = new();
sb.Append('3'); sb.Append("3\t");
sb.Append(userId);
sb.Append('\t'); sb.Append('\t');
sb.Append(User.UserId); sb.Append(userName);
sb.Append('\t');
sb.Append(User.LegacyNameWithStatus);
sb.Append('\t'); sb.Append('\t');
switch(reason) { switch(reason) {
case UserDisconnectReason.Leave: case Reason.Leave:
default: default:
sb.Append("leave"); sb.Append("leave");
break; break;
case UserDisconnectReason.TimeOut: case Reason.TimeOut:
sb.Append("timeout"); sb.Append("timeout");
break; break;
case UserDisconnectReason.Kicked: case Reason.Kicked:
sb.Append("kick"); sb.Append("kick");
break; break;
case UserDisconnectReason.Flood: case Reason.Flood:
sb.Append("flood"); sb.Append("flood");
break; break;
} }

View file

@ -181,7 +181,7 @@ namespace SharpChat {
if(banDuration == TimeSpan.MinValue) { if(banDuration == TimeSpan.MinValue) {
Context.SendTo(conn.User, new CommandResponseS2CPacket(Context.RandomSnowflake.Next(), LCR.FLOOD_WARN, false)); Context.SendTo(conn.User, new CommandResponseS2CPacket(Context.RandomSnowflake.Next(), LCR.FLOOD_WARN, false));
} else { } else {
Context.BanUser(conn.User, banDuration, UserDisconnectReason.Flood); Context.BanUser(conn.User, banDuration, UserDisconnectS2CPacket.Reason.Flood);
if(banDuration > TimeSpan.Zero) if(banDuration > TimeSpan.Zero)
Misuzu.CreateBanAsync( Misuzu.CreateBanAsync(