Changed pretty much every context mutation into an event.
Don't run this in prod yet lol.
This commit is contained in:
parent
6bda1ee09d
commit
42a0160cde
23 changed files with 584 additions and 305 deletions
|
@ -1,4 +1,6 @@
|
|||
using SharpChat.SockChat.PacketsS2C;
|
||||
using SharpChat.Events;
|
||||
using SharpChat.SockChat.PacketsS2C;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpChat.SockChat.Commands {
|
||||
|
@ -31,35 +33,34 @@ namespace SharpChat.SockChat.Commands {
|
|||
return;
|
||||
}
|
||||
|
||||
string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
|
||||
string channelName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
|
||||
|
||||
if(!SockChatUtility.CheckChannelName(createChanName)) {
|
||||
if(!SockChatUtility.CheckChannelName(channelName)) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelNameFormatErrorS2CPacket());
|
||||
return;
|
||||
}
|
||||
|
||||
if(ctx.Chat.Channels.Get(createChanName, SockChatUtility.SanitiseChannelName) != null) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelNameInUseErrorS2CPacket(createChanName));
|
||||
if(ctx.Chat.Channels.Get(channelName, SockChatUtility.SanitiseChannelName) != null) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelNameInUseErrorS2CPacket(channelName));
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelInfo createChan = new(
|
||||
createChanName,
|
||||
isTemporary: !ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPermanent),
|
||||
rank: createChanHierarchy,
|
||||
ownerId: ctx.User.UserId
|
||||
ctx.Chat.Events.Dispatch(
|
||||
"chan:add",
|
||||
channelName,
|
||||
ctx.User,
|
||||
new ChannelAddEventData(
|
||||
!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPermanent),
|
||||
createChanHierarchy,
|
||||
string.Empty
|
||||
)
|
||||
);
|
||||
|
||||
ctx.Chat.Channels.Add(createChan, sanitiseName: SockChatUtility.SanitiseChannelName);
|
||||
foreach(UserInfo ccu in ctx.Chat.Users.GetMany(minRank: ctx.Channel.Rank))
|
||||
ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(
|
||||
ctx.Channel.Name,
|
||||
ctx.Channel.HasPassword,
|
||||
ctx.Channel.IsTemporary
|
||||
));
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
ctx.Chat.Events.Dispatch("chan:leave", now, ctx.Channel, ctx.User);
|
||||
ctx.Chat.Events.Dispatch("chan:join", now, channelName, ctx.User);
|
||||
|
||||
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelCreateResponseS2CPacket(createChan.Name));
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelCreateResponseS2CPacket(channelName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ namespace SharpChat.SockChat.Commands {
|
|||
return;
|
||||
}
|
||||
|
||||
if(!ctx.User.Permissions.HasFlag(UserPermissions.DeleteChannel) || !delChan.IsOwner(ctx.User)) {
|
||||
if(!ctx.User.Permissions.HasFlag(UserPermissions.DeleteChannel) || delChan.OwnerId != ctx.User.UserId) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelDeleteNotAllowedErrorS2CPacket(delChan.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.Chat.RemoveChannel(delChan);
|
||||
ctx.Chat.Events.Dispatch("chan:delete", delChan, ctx.User);
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelDeleteResponseS2CPacket(delChan.Name));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using SharpChat.SockChat.PacketsS2C;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpChat.SockChat.Commands {
|
||||
|
@ -8,16 +9,40 @@ namespace SharpChat.SockChat.Commands {
|
|||
}
|
||||
|
||||
public void Dispatch(SockChatClientCommandContext ctx) {
|
||||
string joinChanStr = ctx.Args.FirstOrDefault() ?? string.Empty;
|
||||
ChannelInfo? joinChan = ctx.Chat.Channels.Get(joinChanStr, SockChatUtility.SanitiseChannelName);
|
||||
string channelName = ctx.Args.FirstOrDefault() ?? string.Empty;
|
||||
string password = string.Join(' ', ctx.Args.Skip(1));
|
||||
|
||||
if(joinChan == null) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorS2CPacket(joinChanStr));
|
||||
ctx.Chat.ForceChannel(ctx.User);
|
||||
ChannelInfo? channelInfo = ctx.Chat.Channels.Get(channelName, SockChatUtility.SanitiseChannelName);
|
||||
if(channelInfo == null) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorS2CPacket(channelName));
|
||||
ctx.Chat.SendTo(ctx.User, new UserChannelForceJoinS2CPacket(ctx.Channel.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.Chat.SwitchChannel(ctx.User, joinChan, string.Join(' ', ctx.Args.Skip(1)));
|
||||
if(channelInfo.Name.Equals(ctx.Channel.Name, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
// this is where the elusive commented out "samechan" error would go!
|
||||
// https://patchii.net/sockchat/sockchat/src/commit/6c2111fb4b0241f9ef31060b0f86e7176664f572/server/lib/context.php#L61
|
||||
ctx.Chat.SendTo(ctx.User, new UserChannelForceJoinS2CPacket(ctx.Channel.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!ctx.User.Permissions.HasFlag(UserPermissions.JoinAnyChannel) && channelInfo.OwnerId != ctx.User.UserId) {
|
||||
if(channelInfo.Rank > ctx.User.Rank) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelRankTooLowErrorS2CPacket(channelInfo.Name));
|
||||
ctx.Chat.SendTo(ctx.User, new UserChannelForceJoinS2CPacket(ctx.Channel.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(channelInfo.Password) && channelInfo.Password.Equals(password)) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelPasswordWrongErrorS2CPacket(channelInfo.Name));
|
||||
ctx.Chat.SendTo(ctx.User, new UserChannelForceJoinS2CPacket(ctx.Channel.Name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
ctx.Chat.Events.Dispatch("chan:leave", now, ctx.Channel, ctx.User);
|
||||
ctx.Chat.Events.Dispatch("chan:join", now, channelInfo, ctx.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using SharpChat.SockChat.PacketsS2C;
|
||||
using SharpChat.Events;
|
||||
using SharpChat.SockChat.PacketsS2C;
|
||||
|
||||
namespace SharpChat.SockChat.Commands {
|
||||
public class ChannelPasswordCommand : ISockChatClientCommand {
|
||||
|
@ -8,7 +9,7 @@ namespace SharpChat.SockChat.Commands {
|
|||
}
|
||||
|
||||
public void Dispatch(SockChatClientCommandContext ctx) {
|
||||
if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPassword) || !ctx.Channel.IsOwner(ctx.User)) {
|
||||
if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPassword) || ctx.Channel.OwnerId != ctx.User.UserId) {
|
||||
ctx.Chat.SendTo(ctx.User, new CommandNotAllowedErrorS2CPacket(ctx.Name));
|
||||
return;
|
||||
}
|
||||
|
@ -18,7 +19,7 @@ namespace SharpChat.SockChat.Commands {
|
|||
if(string.IsNullOrWhiteSpace(chanPass))
|
||||
chanPass = string.Empty;
|
||||
|
||||
ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
|
||||
ctx.Chat.Events.Dispatch("chan:update", ctx.Channel.Name, ctx.User, new ChannelUpdateEventData(password: chanPass));
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelPasswordChangedResponseS2CPacket());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using SharpChat.SockChat.PacketsS2C;
|
||||
using SharpChat.Events;
|
||||
using SharpChat.SockChat.PacketsS2C;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpChat.SockChat.Commands {
|
||||
|
@ -10,17 +11,17 @@ namespace SharpChat.SockChat.Commands {
|
|||
}
|
||||
|
||||
public void Dispatch(SockChatClientCommandContext ctx) {
|
||||
if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelHierarchy) || !ctx.Channel.IsOwner(ctx.User)) {
|
||||
if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelHierarchy) || ctx.Channel.OwnerId != ctx.User.UserId) {
|
||||
ctx.Chat.SendTo(ctx.User, new CommandNotAllowedErrorS2CPacket(ctx.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!ctx.Args.Any() || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) {
|
||||
if(!ctx.Args.Any() || !int.TryParse(ctx.Args.First(), out int chanMinRank) || chanMinRank > ctx.User.Rank) {
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelRankTooHighErrorS2CPacket());
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.Chat.UpdateChannel(ctx.Channel, minRank: chanHierarchy);
|
||||
ctx.Chat.Events.Dispatch("chan:update", ctx.Channel.Name, ctx.User, new ChannelUpdateEventData(minRank: chanMinRank));
|
||||
ctx.Chat.SendTo(ctx.User, new ChannelRankChangedResponseS2CPacket());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace SharpChat.SockChat.Commands {
|
|||
}
|
||||
|
||||
if(duration <= TimeSpan.Zero) {
|
||||
ctx.Chat.Events.Dispatch("user:kickban", banUser, new UserKickBanEventData(UserDisconnectReason.Kicked, TimeSpan.Zero));
|
||||
ctx.Chat.Events.Dispatch("user:kickban", banUser, UserKickBanEventData.OfDuration(UserDisconnectReason.Kicked, TimeSpan.Zero));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ namespace SharpChat.SockChat.Commands {
|
|||
duration, banReason
|
||||
);
|
||||
|
||||
ctx.Chat.Events.Dispatch("user:kickban", banUser, new UserKickBanEventData(UserDisconnectReason.Kicked, duration));
|
||||
ctx.Chat.Events.Dispatch("user:kickban", banUser, UserKickBanEventData.OfDuration(UserDisconnectReason.Kicked, duration));
|
||||
}).Wait();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,9 +30,13 @@ namespace SharpChat.SockChat.Commands {
|
|||
return;
|
||||
}
|
||||
|
||||
UserInfo[] userInfos = ctx.Chat.Users.GetMany(
|
||||
ctx.Chat.ChannelsUsers.GetChannelUserIds(channel)
|
||||
);
|
||||
|
||||
ctx.Chat.SendTo(ctx.User, new WhoChannelResponseS2CPacket(
|
||||
channel.Name,
|
||||
ctx.Chat.GetChannelUsers(channel).Select(user => SockChatUtility.GetUserName(user, ctx.Chat.UserStatuses.Get(user))).ToArray(),
|
||||
userInfos.Select(user => SockChatUtility.GetUserName(user, ctx.Chat.UserStatuses.Get(user))).ToArray(),
|
||||
SockChatUtility.GetUserName(ctx.User, ctx.Chat.UserStatuses.Get(ctx.User))
|
||||
));
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace SharpChat.SockChat.PacketsC2S {
|
|||
private readonly DateTimeOffset Started;
|
||||
private readonly MisuzuClient Misuzu;
|
||||
private readonly ChannelInfo DefaultChannel;
|
||||
private readonly CachedValue<string> MOTDHeaderFormat;
|
||||
private readonly CachedValue<int> MaxMessageLength;
|
||||
private readonly CachedValue<int> MaxConnections;
|
||||
|
||||
|
@ -22,12 +23,14 @@ namespace SharpChat.SockChat.PacketsC2S {
|
|||
DateTimeOffset started,
|
||||
MisuzuClient msz,
|
||||
ChannelInfo? defaultChannel,
|
||||
CachedValue<string> motdHeaderFormat,
|
||||
CachedValue<int> maxMsgLength,
|
||||
CachedValue<int> maxConns
|
||||
) {
|
||||
Started = started;
|
||||
Misuzu = msz;
|
||||
DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
|
||||
MOTDHeaderFormat = motdHeaderFormat;
|
||||
MaxMessageLength = maxMsgLength;
|
||||
MaxConnections = maxConns;
|
||||
}
|
||||
|
@ -118,19 +121,35 @@ namespace SharpChat.SockChat.PacketsC2S {
|
|||
return;
|
||||
}
|
||||
|
||||
if(ctx.Chat.Connections.GetCountForUser(fai.UserId) >= MaxConnections) {
|
||||
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.MaxSessions));
|
||||
ctx.Connection.Close(1000);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Chat.ContextAccess.WaitAsync();
|
||||
try {
|
||||
UserInfo? user = ctx.Chat.Users.Get(fai.UserId);
|
||||
|
||||
if(user == null) {
|
||||
user = new UserInfo(
|
||||
ctx.Chat.Events.Dispatch(
|
||||
"user:add",
|
||||
fai.UserId,
|
||||
fai.UserName ?? string.Empty,
|
||||
fai.Colour,
|
||||
fai.Rank,
|
||||
string.Empty,
|
||||
fai.Permissions,
|
||||
isSuper: fai.IsSuper
|
||||
new UserAddEventData(fai.IsSuper)
|
||||
);
|
||||
|
||||
user = ctx.Chat.Users.Get(fai.UserId);
|
||||
if(user == null) {
|
||||
Logger.Write($"<{ctx.Connection.RemoteEndPoint}> User didn't get added.");
|
||||
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.Null));
|
||||
ctx.Connection.Close(1000);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
string? updName = !user.UserName.Equals(fai.UserName) ? fai.UserName : null;
|
||||
int? updColour = (updColour = fai.Colour.ToMisuzu()) != user.Colour.ToMisuzu() ? updColour : null;
|
||||
|
@ -152,16 +171,11 @@ namespace SharpChat.SockChat.PacketsC2S {
|
|||
);
|
||||
}
|
||||
|
||||
// Enforce a maximum amount of connections per user
|
||||
if(ctx.Chat.Connections.GetCountForUser(user) >= MaxConnections) {
|
||||
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.FailReason.MaxSessions));
|
||||
ctx.Connection.Close(1000);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.Connection.BumpPing();
|
||||
ctx.Chat.Connections.SetUser(ctx.Connection, user);
|
||||
ctx.Connection.Send(new MOTDS2CPacket(Started, $"Welcome to Flashii Chat, {user.UserName}!"));
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(MOTDHeaderFormat.Value))
|
||||
ctx.Connection.Send(new MOTDS2CPacket(Started, string.Format(MOTDHeaderFormat.Value, user.UserName)));
|
||||
|
||||
if(File.Exists(MOTD_FILE)) {
|
||||
IEnumerable<string> lines = File.ReadAllLines(MOTD_FILE).Where(x => !string.IsNullOrWhiteSpace(x));
|
||||
|
@ -171,7 +185,49 @@ namespace SharpChat.SockChat.PacketsC2S {
|
|||
ctx.Connection.Send(new MOTDS2CPacket(File.GetLastWriteTimeUtc(MOTD_FILE), line));
|
||||
}
|
||||
|
||||
ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
|
||||
ctx.Connection.Send(new AuthSuccessS2CPacket(
|
||||
user.UserId,
|
||||
SockChatUtility.GetUserName(user, ctx.Chat.UserStatuses.Get(user)),
|
||||
user.Colour,
|
||||
user.Rank,
|
||||
user.Permissions,
|
||||
DefaultChannel.Name,
|
||||
MaxMessageLength
|
||||
));
|
||||
|
||||
UserInfo[] chanUsers = ctx.Chat.Users.GetMany(
|
||||
ctx.Chat.ChannelsUsers.GetChannelUserIds(DefaultChannel)
|
||||
);
|
||||
List<UsersPopulateS2CPacket.ListEntry> chanUserEntries = new();
|
||||
foreach(UserInfo chanUserInfo in chanUsers)
|
||||
if(chanUserInfo.UserId != user.UserId)
|
||||
chanUserEntries.Add(new(
|
||||
chanUserInfo.UserId,
|
||||
SockChatUtility.GetUserName(chanUserInfo, ctx.Chat.UserStatuses.Get(chanUserInfo)),
|
||||
chanUserInfo.Colour,
|
||||
chanUserInfo.Rank,
|
||||
chanUserInfo.Permissions,
|
||||
true
|
||||
));
|
||||
ctx.Connection.Send(new UsersPopulateS2CPacket(chanUserEntries.ToArray()));
|
||||
|
||||
ctx.Chat.Events.Dispatch(
|
||||
"user:connect",
|
||||
DefaultChannel,
|
||||
user,
|
||||
new UserConnectEventData(!ctx.Chat.ChannelsUsers.Has(DefaultChannel, user))
|
||||
);
|
||||
ctx.Chat.HandleChannelEventLog(DefaultChannel.Name, p => ctx.Connection.Send(p));
|
||||
|
||||
ChannelInfo[] chans = ctx.Chat.Channels.GetMany(isPublic: true, minRank: user.Rank);
|
||||
List<ChannelsPopulateS2CPacket.ListEntry> chanEntries = new();
|
||||
foreach(ChannelInfo chanInfo in chans)
|
||||
chanEntries.Add(new(
|
||||
chanInfo.Name,
|
||||
chanInfo.HasPassword,
|
||||
chanInfo.IsTemporary
|
||||
));
|
||||
ctx.Connection.Send(new ChannelsPopulateS2CPacket(chanEntries.ToArray()));
|
||||
} finally {
|
||||
ctx.Chat.ContextAccess.Release();
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ namespace SharpChat {
|
|||
}
|
||||
|
||||
public void HandleEvent(ChatEventInfo info) {
|
||||
// user status should be stored outside of the UserInfo class so we don't need to do this:
|
||||
UserInfo? userInfo = Users.Get(info.SenderId);
|
||||
UserStatusInfo userStatusInfo = UserStatuses.Get(info.SenderId);
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(info.ChannelName))
|
||||
|
@ -37,7 +35,27 @@ namespace SharpChat {
|
|||
// TODO: should user:connect and user:disconnect be channel agnostic?
|
||||
|
||||
switch(info.Type) {
|
||||
case "user:add":
|
||||
Users.Add(new UserInfo(
|
||||
info.SenderId,
|
||||
info.SenderName,
|
||||
info.SenderColour,
|
||||
info.SenderRank,
|
||||
info.SenderPerms,
|
||||
info.SenderNickName,
|
||||
info.Data is UserAddEventData uaData && uaData.IsSuper
|
||||
));
|
||||
break;
|
||||
|
||||
case "user:delete":
|
||||
UserStatuses.Clear(info.SenderId);
|
||||
Users.Remove(info.SenderId);
|
||||
break;
|
||||
|
||||
case "user:connect":
|
||||
if(info.Data is not UserConnectEventData ucData || !ucData.Notify)
|
||||
break;
|
||||
|
||||
SendTo(info.ChannelName, new UserConnectS2CPacket(
|
||||
info.Id,
|
||||
info.Created,
|
||||
|
@ -47,14 +65,10 @@ namespace SharpChat {
|
|||
info.SenderRank,
|
||||
info.SenderPerms
|
||||
));
|
||||
ChannelsUsers.Join(info.ChannelName, info.SenderId);
|
||||
break;
|
||||
|
||||
case "user:disconnect":
|
||||
if(userInfo != null)
|
||||
Events.Dispatch("user:status", userInfo, new UserStatusUpdateEventData(UserStatus.Offline));
|
||||
UserStatuses.Clear(info.SenderId);
|
||||
Users.Remove(info.SenderId);
|
||||
|
||||
ChannelInfo[] channels = Channels.GetMany(ChannelsUsers.GetUserChannelNames(info.SenderId));
|
||||
ChannelsUsers.DeleteUser(info.SenderId);
|
||||
|
||||
|
@ -68,8 +82,8 @@ namespace SharpChat {
|
|||
);
|
||||
|
||||
foreach(ChannelInfo chan in channels) {
|
||||
if(chan.IsTemporary && chan.IsOwner(info.SenderId))
|
||||
RemoveChannel(chan);
|
||||
if(chan.IsTemporary && chan.OwnerId == info.SenderId)
|
||||
Events.Dispatch("chan:delete", chan.Name, info);
|
||||
else
|
||||
SendTo(chan, udPacket);
|
||||
}
|
||||
|
@ -100,42 +114,46 @@ namespace SharpChat {
|
|||
break;
|
||||
|
||||
case "user:update":
|
||||
if(info.Data is not UserUpdateEventData userUpdate || userInfo is null)
|
||||
if(info.Data is not UserUpdateEventData userUpdate)
|
||||
break;
|
||||
|
||||
UserInfo? uuUserInfo = Users.Get(info.SenderId);
|
||||
if(uuUserInfo is null)
|
||||
break;
|
||||
|
||||
bool uuHasChanged = false;
|
||||
string? uuPrevName = null;
|
||||
|
||||
if(userUpdate.Name != null && !userUpdate.Name.Equals(userInfo.UserName)) {
|
||||
userInfo.UserName = userUpdate.Name;
|
||||
if(userUpdate.Name != null && !userUpdate.Name.Equals(uuUserInfo.UserName)) {
|
||||
uuUserInfo.UserName = userUpdate.Name;
|
||||
uuHasChanged = true;
|
||||
}
|
||||
|
||||
if(userUpdate.NickName != null && !userUpdate.NickName.Equals(userInfo.NickName)) {
|
||||
if(userUpdate.NickName != null && !userUpdate.NickName.Equals(uuUserInfo.NickName)) {
|
||||
if(userUpdate.Notify)
|
||||
uuPrevName = string.IsNullOrWhiteSpace(userInfo.NickName) ? userInfo.UserName : userInfo.NickName;
|
||||
uuPrevName = string.IsNullOrWhiteSpace(uuUserInfo.NickName) ? uuUserInfo.UserName : uuUserInfo.NickName;
|
||||
|
||||
userInfo.NickName = userUpdate.NickName;
|
||||
uuUserInfo.NickName = userUpdate.NickName;
|
||||
uuHasChanged = true;
|
||||
}
|
||||
|
||||
if(userUpdate.Colour.HasValue && userUpdate.Colour != userInfo.Colour.ToMisuzu()) {
|
||||
userInfo.Colour = Colour.FromMisuzu(userUpdate.Colour.Value);
|
||||
if(userUpdate.Colour.HasValue && userUpdate.Colour != uuUserInfo.Colour.ToMisuzu()) {
|
||||
uuUserInfo.Colour = Colour.FromMisuzu(userUpdate.Colour.Value);
|
||||
uuHasChanged = true;
|
||||
}
|
||||
|
||||
if(userUpdate.Rank != null && userUpdate.Rank != userInfo.Rank) {
|
||||
userInfo.Rank = userUpdate.Rank.Value;
|
||||
if(userUpdate.Rank != null && userUpdate.Rank != uuUserInfo.Rank) {
|
||||
uuUserInfo.Rank = userUpdate.Rank.Value;
|
||||
uuHasChanged = true;
|
||||
}
|
||||
|
||||
if(userUpdate.Perms.HasValue && userUpdate.Perms != userInfo.Permissions) {
|
||||
userInfo.Permissions = userUpdate.Perms.Value;
|
||||
if(userUpdate.Perms.HasValue && userUpdate.Perms != uuUserInfo.Permissions) {
|
||||
uuUserInfo.Permissions = userUpdate.Perms.Value;
|
||||
uuHasChanged = true;
|
||||
}
|
||||
|
||||
if(userUpdate.IsSuper.HasValue && userUpdate.IsSuper != userInfo.IsSuper)
|
||||
userInfo.IsSuper = userUpdate.IsSuper.Value;
|
||||
if(userUpdate.IsSuper.HasValue && userUpdate.IsSuper != uuUserInfo.IsSuper)
|
||||
uuUserInfo.IsSuper = userUpdate.IsSuper.Value;
|
||||
|
||||
if(uuHasChanged) {
|
||||
if(uuPrevName != null)
|
||||
|
@ -143,15 +161,15 @@ namespace SharpChat {
|
|||
info.Id,
|
||||
info.Created,
|
||||
string.IsNullOrWhiteSpace(info.SenderNickName) ? uuPrevName : $"~{info.SenderNickName}",
|
||||
SockChatUtility.GetUserName(userInfo, userStatusInfo)
|
||||
SockChatUtility.GetUserName(uuUserInfo, userStatusInfo)
|
||||
));
|
||||
|
||||
SendToUserChannels(info.SenderId, new UserUpdateS2CPacket(
|
||||
userInfo.UserId,
|
||||
SockChatUtility.GetUserName(userInfo, userStatusInfo),
|
||||
userInfo.Colour,
|
||||
userInfo.Rank,
|
||||
userInfo.Permissions
|
||||
uuUserInfo.UserId,
|
||||
SockChatUtility.GetUserName(uuUserInfo, userStatusInfo),
|
||||
uuUserInfo.Colour,
|
||||
uuUserInfo.Rank,
|
||||
uuUserInfo.Permissions
|
||||
));
|
||||
}
|
||||
break;
|
||||
|
@ -189,17 +207,106 @@ namespace SharpChat {
|
|||
break;
|
||||
|
||||
case "chan:join":
|
||||
SendTo(info.ChannelName, new UserChannelJoinS2CPacket(
|
||||
info.SenderId,
|
||||
SockChatUtility.GetUserName(info, userStatusInfo),
|
||||
info.SenderColour,
|
||||
info.SenderRank,
|
||||
info.SenderPerms
|
||||
));
|
||||
HandleUserChannelJoin(
|
||||
info.ChannelName,
|
||||
new UserInfo(info), // kinda stinky
|
||||
userStatusInfo
|
||||
);
|
||||
break;
|
||||
|
||||
case "chan:leave":
|
||||
ChannelsUsers.Leave(info.ChannelName, info.SenderId);
|
||||
SendTo(info.ChannelName, new UserChannelLeaveS2CPacket(info.SenderId));
|
||||
|
||||
ChannelInfo? clChannelInfo = Channels.Get(info.ChannelName);
|
||||
if(clChannelInfo?.IsTemporary == true && clChannelInfo.OwnerId == info.SenderId)
|
||||
Events.Dispatch("chan:delete", clChannelInfo.Name, info);
|
||||
break;
|
||||
|
||||
case "chan:add":
|
||||
if(info.Data is not ChannelAddEventData caData)
|
||||
break;
|
||||
|
||||
ChannelInfo caChannelInfo = new(
|
||||
info.ChannelName,
|
||||
caData.Password,
|
||||
caData.IsTemporary,
|
||||
caData.MinRank,
|
||||
info.SenderId
|
||||
);
|
||||
|
||||
Channels.Add(caChannelInfo);
|
||||
foreach(UserInfo ccu in Users.GetMany(minRank: caChannelInfo.Rank))
|
||||
SendTo(ccu, new ChannelCreateS2CPacket(
|
||||
caChannelInfo.Name,
|
||||
caChannelInfo.HasPassword,
|
||||
caChannelInfo.IsTemporary
|
||||
));
|
||||
break;
|
||||
|
||||
case "chan:update":
|
||||
if(info.Data is not ChannelUpdateEventData cuData)
|
||||
break;
|
||||
|
||||
ChannelInfo? cuChannelInfo = Channels.Get(info.ChannelName);
|
||||
if(cuChannelInfo is null)
|
||||
break;
|
||||
|
||||
string cuChannelName = cuChannelInfo.Name;
|
||||
|
||||
if(!string.IsNullOrEmpty(cuData.Name))
|
||||
cuChannelInfo.Name = cuData.Name;
|
||||
|
||||
if(cuData.MinRank.HasValue)
|
||||
cuChannelInfo.Rank = cuData.MinRank.Value;
|
||||
|
||||
if(cuData.Password != null) // this should probably be hashed
|
||||
cuChannelInfo.Password = cuData.Password;
|
||||
|
||||
if(cuData.IsTemporary.HasValue)
|
||||
cuChannelInfo.IsTemporary = cuData.IsTemporary.Value;
|
||||
|
||||
bool nameChanged = !cuChannelName.Equals(cuChannelInfo.Name, StringComparison.InvariantCultureIgnoreCase);
|
||||
if(nameChanged)
|
||||
ChannelsUsers.RenameChannel(cuChannelName, cuChannelInfo.Name);
|
||||
|
||||
// TODO: Users that no longer have access to the channel/gained access to the channel by the rank change should receive delete and create packets respectively
|
||||
// the server currently doesn't keep track of what channels a user is already aware of so can't really simulate this yet.
|
||||
foreach(UserInfo user in Users.GetMany(minRank: cuChannelInfo.Rank))
|
||||
SendTo(user, new ChannelUpdateS2CPacket(
|
||||
cuChannelName,
|
||||
cuChannelInfo.Name,
|
||||
cuChannelInfo.HasPassword,
|
||||
cuChannelInfo.IsTemporary
|
||||
));
|
||||
|
||||
if(nameChanged)
|
||||
SendTo(cuChannelInfo, new UserChannelForceJoinS2CPacket(cuChannelInfo.Name));
|
||||
break;
|
||||
|
||||
case "chan:delete":
|
||||
ChannelInfo? cdTargetChannelInfo = Channels.Get(info.ChannelName);
|
||||
ChannelInfo? cdMainChannelInfo = Channels.MainChannel;
|
||||
if(cdTargetChannelInfo == null || cdMainChannelInfo == null || cdTargetChannelInfo == Channels.MainChannel)
|
||||
break;
|
||||
|
||||
// Remove channel from the listing
|
||||
Channels.Remove(info.ChannelName);
|
||||
|
||||
// Move all users back to the main channel
|
||||
UserInfo[] cdUserInfos = Users.GetMany(ChannelsUsers.GetChannelUserIds(info.ChannelName));
|
||||
ChannelsUsers.DeleteChannel(cdMainChannelInfo);
|
||||
|
||||
foreach(UserInfo userInfo in cdUserInfos)
|
||||
HandleUserChannelJoin(
|
||||
info.ChannelName,
|
||||
userInfo,
|
||||
UserStatuses.Get(userInfo)
|
||||
);
|
||||
|
||||
// Broadcast deletion of channel
|
||||
foreach(UserInfo user in Users.GetMany(minRank: cdTargetChannelInfo.Rank))
|
||||
SendTo(user, new ChannelDeleteS2CPacket(cdTargetChannelInfo.Name));
|
||||
break;
|
||||
|
||||
case "msg:delete":
|
||||
|
@ -271,6 +378,37 @@ namespace SharpChat {
|
|||
}
|
||||
}
|
||||
|
||||
private void HandleUserChannelJoin(string channelName, UserInfo userInfo, UserStatusInfo statusInfo) {
|
||||
SendTo(userInfo.UserId, new ClearMessagesAndUsersS2CPacket());
|
||||
|
||||
UserInfo[] userInfos = Users.GetMany(ChannelsUsers.GetChannelUserIds(channelName));
|
||||
List<UsersPopulateS2CPacket.ListEntry> userEntries = new();
|
||||
foreach(UserInfo memberInfo in userInfos)
|
||||
if(memberInfo.UserId != userInfo.UserId)
|
||||
userEntries.Add(new(
|
||||
memberInfo.UserId,
|
||||
SockChatUtility.GetUserName(memberInfo, UserStatuses.Get(memberInfo)),
|
||||
memberInfo.Colour,
|
||||
memberInfo.Rank,
|
||||
memberInfo.Permissions,
|
||||
true
|
||||
));
|
||||
SendTo(userInfo.UserId, new UsersPopulateS2CPacket(userEntries.ToArray()));
|
||||
|
||||
SendTo(channelName, new UserChannelJoinS2CPacket(
|
||||
userInfo.UserId,
|
||||
SockChatUtility.GetUserName(userInfo, statusInfo),
|
||||
userInfo.Colour,
|
||||
userInfo.Rank,
|
||||
userInfo.Permissions
|
||||
));
|
||||
|
||||
HandleChannelEventLog(channelName, p => SendTo(userInfo.UserId, p));
|
||||
|
||||
ChannelsUsers.Join(channelName, userInfo.UserId);
|
||||
SendTo(userInfo.UserId, new UserChannelForceJoinS2CPacket(channelName));
|
||||
}
|
||||
|
||||
public void Update() {
|
||||
ConnectionInfo[] timedOut = Connections.GetTimedOut();
|
||||
foreach(ConnectionInfo conn in timedOut) {
|
||||
|
@ -283,6 +421,7 @@ namespace SharpChat {
|
|||
|
||||
foreach(UserInfo user in Users.All)
|
||||
if(!Connections.HasUser(user)) {
|
||||
Events.Dispatch("user:delete", user);
|
||||
Events.Dispatch(
|
||||
"user:disconnect",
|
||||
ChannelsUsers.GetUserLastChannel(user),
|
||||
|
@ -302,10 +441,6 @@ namespace SharpChat {
|
|||
}
|
||||
}
|
||||
|
||||
public UserInfo[] GetChannelUsers(ChannelInfo channel) {
|
||||
return Users.GetMany(ChannelsUsers.GetChannelUserIds(channel));
|
||||
}
|
||||
|
||||
public void HandleChannelEventLog(string channelName, Action<ISockChatS2CPacket> handler) {
|
||||
foreach(ChatEventInfo info in EventStorage.GetChannelEventLog(channelName)) {
|
||||
switch(info.Type) {
|
||||
|
@ -330,6 +465,7 @@ namespace SharpChat {
|
|||
break;
|
||||
|
||||
case "user:connect":
|
||||
if(info.Data is UserConnectEventData ucData && ucData.Notify)
|
||||
handler(new UserConnectLogS2CPacket(
|
||||
info.Id,
|
||||
info.Created,
|
||||
|
@ -375,99 +511,6 @@ namespace SharpChat {
|
|||
}
|
||||
}
|
||||
|
||||
public void HandleJoin(UserInfo user, ChannelInfo chan, SockChatConnectionInfo conn, int maxMsgLength) {
|
||||
if(!ChannelsUsers.Has(chan, user))
|
||||
Events.Dispatch("user:connect", chan, user);
|
||||
|
||||
UserStatusInfo statusInfo = UserStatuses.Get(user);
|
||||
|
||||
conn.Send(new AuthSuccessS2CPacket(
|
||||
user.UserId,
|
||||
SockChatUtility.GetUserName(user, statusInfo),
|
||||
user.Colour,
|
||||
user.Rank,
|
||||
user.Permissions,
|
||||
chan.Name,
|
||||
maxMsgLength
|
||||
));
|
||||
conn.Send(new UsersPopulateS2CPacket(GetChannelUsers(chan).Except(new[] { user }).Select(
|
||||
user => new UsersPopulateS2CPacket.ListEntry(
|
||||
user.UserId,
|
||||
SockChatUtility.GetUserName(user, statusInfo),
|
||||
user.Colour,
|
||||
user.Rank,
|
||||
user.Permissions,
|
||||
true
|
||||
)
|
||||
).OrderByDescending(user => user.Rank).ToArray()));
|
||||
|
||||
HandleChannelEventLog(chan.Name, p => conn.Send(p));
|
||||
|
||||
conn.Send(new ChannelsPopulateS2CPacket(Channels.GetMany(isPublic: true, minRank: user.Rank).Select(
|
||||
channel => new ChannelsPopulateS2CPacket.ListEntry(channel.Name, channel.HasPassword, channel.IsTemporary)
|
||||
).ToArray()));
|
||||
|
||||
if(Users.Get(userId: user.UserId) == null)
|
||||
Users.Add(user);
|
||||
|
||||
ChannelsUsers.Join(chan.Name, user.UserId);
|
||||
}
|
||||
|
||||
public void SwitchChannel(UserInfo user, ChannelInfo chan, string password) {
|
||||
if(ChannelsUsers.IsUserLastChannel(user, chan)) {
|
||||
ForceChannel(user);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!user.Permissions.HasFlag(UserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
|
||||
if(chan.Rank > user.Rank) {
|
||||
SendTo(user, new ChannelRankTooLowErrorS2CPacket(chan.Name));
|
||||
ForceChannel(user);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(chan.Password) && chan.Password.Equals(password)) {
|
||||
SendTo(user, new ChannelPasswordWrongErrorS2CPacket(chan.Name));
|
||||
ForceChannel(user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ForceChannelSwitch(user, chan);
|
||||
}
|
||||
|
||||
public void ForceChannelSwitch(UserInfo user, ChannelInfo chan) {
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
ChannelInfo? oldChan = Channels.Get(ChannelsUsers.GetUserLastChannel(user));
|
||||
|
||||
if(oldChan != null)
|
||||
Events.Dispatch("chan:leave", now, oldChan, user);
|
||||
|
||||
Events.Dispatch("chan:join", now, chan, user);
|
||||
|
||||
SendTo(user, new ClearMessagesAndUsersS2CPacket());
|
||||
SendTo(user, new UsersPopulateS2CPacket(GetChannelUsers(chan).Except(new[] { user }).Select(
|
||||
user => new UsersPopulateS2CPacket.ListEntry(
|
||||
user.UserId,
|
||||
SockChatUtility.GetUserName(user, UserStatuses.Get(user)),
|
||||
user.Colour,
|
||||
user.Rank,
|
||||
user.Permissions,
|
||||
true
|
||||
)
|
||||
).OrderByDescending(u => u.Rank).ToArray()));
|
||||
|
||||
HandleChannelEventLog(chan.Name, p => SendTo(user, p));
|
||||
ForceChannel(user, chan);
|
||||
|
||||
if(oldChan != null)
|
||||
ChannelsUsers.Leave(oldChan, user);
|
||||
ChannelsUsers.Join(chan, user);
|
||||
|
||||
if(oldChan != null && oldChan.IsTemporary && oldChan.IsOwner(user))
|
||||
RemoveChannel(oldChan);
|
||||
}
|
||||
|
||||
public void Send(ISockChatS2CPacket packet) {
|
||||
string data = packet.Pack();
|
||||
Connections.WithAuthed(conn => {
|
||||
|
@ -512,65 +555,11 @@ namespace SharpChat {
|
|||
});
|
||||
}
|
||||
|
||||
public void SendToUserChannels(UserInfo user, ISockChatS2CPacket packet) {
|
||||
SendToUserChannels(user.UserId, packet);
|
||||
}
|
||||
|
||||
public void SendToUserChannels(long userId, ISockChatS2CPacket packet) {
|
||||
ChannelInfo[] chans = Channels.GetMany(ChannelsUsers.GetUserChannelNames(userId));
|
||||
string data = packet.Pack();
|
||||
foreach(ChannelInfo chan in chans)
|
||||
SendTo(chan, data);
|
||||
}
|
||||
|
||||
public void ForceChannel(UserInfo user, ChannelInfo? chan = null) {
|
||||
chan ??= Channels.Get(ChannelsUsers.GetUserLastChannel(user));
|
||||
if(chan != null)
|
||||
SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
|
||||
}
|
||||
|
||||
public void UpdateChannel(
|
||||
ChannelInfo channel,
|
||||
bool? temporary = null,
|
||||
int? minRank = null,
|
||||
string? password = null
|
||||
) {
|
||||
string prevName = channel.Name;
|
||||
|
||||
if(temporary.HasValue)
|
||||
channel.IsTemporary = temporary.Value;
|
||||
|
||||
if(minRank.HasValue)
|
||||
channel.Rank = minRank.Value;
|
||||
|
||||
if(password != null)
|
||||
channel.Password = password;
|
||||
|
||||
// TODO: Users that no longer have access to the channel/gained access to the channel by the rank change should receive delete and create packets respectively
|
||||
// the server currently doesn't keep track of what channels a user is already aware of so can't really simulate this yet.
|
||||
foreach(UserInfo user in Users.GetMany(minRank: channel.Rank))
|
||||
SendTo(user, new ChannelUpdateS2CPacket(prevName, channel.Name, channel.HasPassword, channel.IsTemporary));
|
||||
}
|
||||
|
||||
public void RemoveChannel(ChannelInfo channel) {
|
||||
if(channel == null || Channels.PublicCount > 1)
|
||||
return;
|
||||
|
||||
ChannelInfo? defaultChannel = Channels.MainChannel;
|
||||
if(defaultChannel == null)
|
||||
return;
|
||||
|
||||
// Remove channel from the listing
|
||||
Channels.Remove(channel);
|
||||
|
||||
// Move all users back to the main channel
|
||||
// TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
|
||||
foreach(UserInfo user in GetChannelUsers(channel))
|
||||
SwitchChannel(user, defaultChannel, string.Empty);
|
||||
|
||||
// Broadcast deletion of channel
|
||||
foreach(UserInfo user in Users.GetMany(minRank: channel.Rank))
|
||||
SendTo(user, new ChannelDeleteS2CPacket(channel.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,10 +71,12 @@ namespace SharpChat.SockChat {
|
|||
if(Context.Channels.PublicCount < 1)
|
||||
Context.Channels.Add(new ChannelInfo("Default"));
|
||||
|
||||
CachedValue<string> motdHeaderFormat = config.ReadCached("motd", @"Welcome to Flashii Chat, {0}!");
|
||||
GuestHandlers.Add(new AuthC2SPacketHandler(
|
||||
started,
|
||||
Misuzu,
|
||||
Context.Channels.MainChannel,
|
||||
motdHeaderFormat,
|
||||
MaxMessageLength,
|
||||
MaxConnections
|
||||
));
|
||||
|
@ -156,14 +158,16 @@ namespace SharpChat.SockChat {
|
|||
|
||||
if(!Context.Connections.HasUser(conn.UserId)) {
|
||||
UserInfo? userInfo = Context.Users.Get(conn.UserId);
|
||||
if(userInfo != null)
|
||||
if(userInfo != null) {
|
||||
Context.Events.Dispatch("user:delete", userInfo);
|
||||
Context.Events.Dispatch(
|
||||
"user:disconnect",
|
||||
Context.ChannelsUsers.GetUserLastChannel(userInfo),
|
||||
Context.ChannelsUsers.GetUserLastChannel(conn.UserId),
|
||||
userInfo,
|
||||
new UserDisconnectEventData(UserDisconnectReason.Leave)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Context.Update();
|
||||
} finally {
|
||||
|
@ -205,7 +209,7 @@ namespace SharpChat.SockChat {
|
|||
if(banDuration == TimeSpan.MinValue) {
|
||||
Context.SendTo(userInfo, new FloodWarningS2CPacket());
|
||||
} else {
|
||||
Context.Events.Dispatch("user:kickban", userInfo, new UserKickBanEventData(UserDisconnectReason.Flood, banDuration));
|
||||
Context.Events.Dispatch("user:kickban", userInfo, UserKickBanEventData.OfDuration(UserDisconnectReason.Flood, banDuration));
|
||||
|
||||
if(banDuration > TimeSpan.Zero)
|
||||
Misuzu.CreateBanAsync(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace SharpChat {
|
||||
public class ChannelInfo {
|
||||
public string Name { get; }
|
||||
public string Name { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool IsTemporary { get; set; }
|
||||
public int Rank { get; set; }
|
||||
|
@ -25,15 +25,5 @@
|
|||
Rank = rank;
|
||||
OwnerId = ownerId;
|
||||
}
|
||||
|
||||
public bool IsOwner(long userId) {
|
||||
return OwnerId > 0
|
||||
&& OwnerId == userId;
|
||||
}
|
||||
public bool IsOwner(UserInfo user) {
|
||||
return OwnerId > 0
|
||||
&& user != null
|
||||
&& OwnerId == user.UserId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ using System.Linq;
|
|||
|
||||
namespace SharpChat {
|
||||
public class ChannelsContext {
|
||||
private readonly List<ChannelInfo> Channels = new();
|
||||
private readonly Dictionary<string, ChannelInfo> Channels = new();
|
||||
|
||||
public ChannelInfo? MainChannel { get; private set; }
|
||||
public int TotalCount { get; private set; }
|
||||
public int PublicCount { get; private set; }
|
||||
|
||||
public ChannelInfo[] All => Channels.ToArray();
|
||||
public ChannelInfo[] All => Channels.Values.ToArray();
|
||||
|
||||
public ChannelInfo? Get(
|
||||
string? name,
|
||||
|
@ -19,7 +19,7 @@ namespace SharpChat {
|
|||
if(string.IsNullOrWhiteSpace(name))
|
||||
return null;
|
||||
|
||||
foreach(ChannelInfo info in Channels) {
|
||||
foreach(ChannelInfo info in Channels.Values) {
|
||||
string chanName = info.Name;
|
||||
if(sanitise != null)
|
||||
chanName = sanitise(chanName);
|
||||
|
@ -44,7 +44,7 @@ namespace SharpChat {
|
|||
for(int i = 0; i < names.Length; ++i)
|
||||
names[i] = names[i].ToLowerInvariant();
|
||||
|
||||
foreach(ChannelInfo info in Channels) {
|
||||
foreach(ChannelInfo info in Channels.Values) {
|
||||
if(info.Rank > minRank)
|
||||
continue;
|
||||
|
||||
|
@ -71,18 +71,14 @@ namespace SharpChat {
|
|||
return chans.ToArray();
|
||||
}
|
||||
|
||||
public void Add(
|
||||
ChannelInfo info,
|
||||
bool forceMain = false,
|
||||
Func<string, string>? sanitiseName = null
|
||||
) {
|
||||
if(Get(info.Name, sanitiseName) != null)
|
||||
public void Add(ChannelInfo info, bool forceMain = false) {
|
||||
if(Channels.ContainsKey(info.Name.ToLowerInvariant()))
|
||||
throw new ArgumentException("A channel with this name has already been registered.", nameof(info));
|
||||
if(string.IsNullOrWhiteSpace(info.Name))
|
||||
throw new ArgumentException("Channel names may not be blank.", nameof(info));
|
||||
// todo: there should be more restrictions on channel names
|
||||
|
||||
Channels.Add(info);
|
||||
Channels.Add(info.Name.ToLowerInvariant(), info);
|
||||
|
||||
++TotalCount;
|
||||
if(info.IsPublic)
|
||||
|
@ -110,14 +106,14 @@ namespace SharpChat {
|
|||
if(info == null)
|
||||
return;
|
||||
|
||||
Channels.Remove(info);
|
||||
Channels.Remove(info.Name.ToLowerInvariant());
|
||||
|
||||
--TotalCount;
|
||||
if(info.IsPublic)
|
||||
--PublicCount;
|
||||
|
||||
if(MainChannel == info)
|
||||
MainChannel = Channels.FirstOrDefault(c => !c.IsPublic);
|
||||
MainChannel = Channels.Values.FirstOrDefault(c => !c.IsPublic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -253,5 +253,31 @@ namespace SharpChat {
|
|||
public void DeleteChannel(ChannelInfo channelInfo) {
|
||||
DeleteChannel(channelInfo.Name);
|
||||
}
|
||||
|
||||
public void RenameChannel(ChannelInfo channelInfo, string newName) {
|
||||
RenameChannel(channelInfo.Name, newName);
|
||||
}
|
||||
|
||||
public void RenameChannel(string prevName, string newName) {
|
||||
newName = newName.ToLowerInvariant();
|
||||
if(ChannelUsers.ContainsKey(newName))
|
||||
throw new ArgumentException("A channel with that name is already registered!", nameof(newName));
|
||||
|
||||
prevName = prevName.ToLowerInvariant();
|
||||
if(ChannelUsers.ContainsKey(prevName)) {
|
||||
ChannelUsers.Add(newName, ChannelUsers[prevName]);
|
||||
ChannelUsers.Remove(prevName);
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<long, string> kvp in UserLastChannel)
|
||||
if(kvp.Value.Equals(prevName, StringComparison.InvariantCultureIgnoreCase))
|
||||
UserLastChannel[kvp.Key] = newName;
|
||||
|
||||
foreach(HashSet<string> userChans in UserChannels.Values)
|
||||
if(userChans.Contains(prevName)) {
|
||||
userChans.Add(newName);
|
||||
userChans.Remove(prevName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,22 @@ namespace SharpChat.EventStorage {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private int RunMigrationCommand(string command, params MySqlParameter[] parameters) {
|
||||
try {
|
||||
using MySqlConnection conn = GetConnection();
|
||||
using MySqlCommand cmd = conn.CreateCommand();
|
||||
cmd.CommandTimeout = 0;
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
return cmd.ExecuteNonQuery();
|
||||
} catch(MySqlException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private MySqlDataReader? RunQuery(string command, params MySqlParameter[] parameters) {
|
||||
try {
|
||||
MySqlConnection conn = GetConnection();
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace SharpChat.EventStorage {
|
|||
if(!done) {
|
||||
Logger.Write($"Running migration '{name}'...");
|
||||
action();
|
||||
RunCommand(
|
||||
RunMigrationCommand(
|
||||
"INSERT INTO `sqc_migrations` (`migration_name`) VALUES (@name)",
|
||||
new MySqlParameter("name", name)
|
||||
);
|
||||
|
@ -19,7 +19,7 @@ namespace SharpChat.EventStorage {
|
|||
}
|
||||
|
||||
public void RunMigrations() {
|
||||
RunCommand(
|
||||
RunMigrationCommand(
|
||||
"CREATE TABLE IF NOT EXISTS `sqc_migrations` ("
|
||||
+ "`migration_name` VARCHAR(255) NOT NULL,"
|
||||
+ "`migration_completed` TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
|
||||
|
@ -36,9 +36,9 @@ namespace SharpChat.EventStorage {
|
|||
}
|
||||
|
||||
private void UpdateCollationsAndUseJsonType() {
|
||||
RunCommand("UPDATE sqc_events SET event_target = LOWER(event_target)");
|
||||
RunCommand("UPDATE sqc_events SET event_sender_nick = NULL WHERE event_sender_nick = ''");
|
||||
RunCommand(
|
||||
RunMigrationCommand("UPDATE sqc_events SET event_target = LOWER(event_target)");
|
||||
RunMigrationCommand("UPDATE sqc_events SET event_sender_nick = NULL WHERE event_sender_nick = ''");
|
||||
RunMigrationCommand(
|
||||
"ALTER TABLE `sqc_events` COLLATE='utf8mb4_unicode_520_ci',"
|
||||
+ " CHANGE COLUMN `event_type` `event_type` VARCHAR(255) NOT NULL COLLATE 'ascii_general_ci' AFTER `event_id`,"
|
||||
+ " CHANGE COLUMN `event_created` `event_created` TIMESTAMP NOT NULL DEFAULT current_timestamp() AFTER `event_type`,"
|
||||
|
@ -53,33 +53,33 @@ namespace SharpChat.EventStorage {
|
|||
|
||||
private void DeprecateEventFlags() {
|
||||
// StoredEventFlags.Action is just a field in the data object
|
||||
RunCommand(@"UPDATE sqc_events SET event_data = JSON_MERGE_PATCH(event_data, JSON_OBJECT('action', true)) WHERE event_flags & 1");
|
||||
RunMigrationCommand(@"UPDATE sqc_events SET event_data = JSON_MERGE_PATCH(event_data, JSON_OBJECT('action', true)) WHERE event_flags & 1");
|
||||
|
||||
// StoredEventFlags.Broadcast can be implied by just having a NULL as the channel name
|
||||
RunCommand(@"UPDATE sqc_events SET event_target = NULL WHERE event_flags & 2");
|
||||
RunMigrationCommand(@"UPDATE sqc_events SET event_target = NULL WHERE event_flags & 2");
|
||||
|
||||
// StoredEventFlags.Log was never meaningfully used by anything and basically just meant "not-msg:add"
|
||||
// StoredEventFlags.Private was also never meaningfully used, can be determined by checking if the channel name starts with @
|
||||
RunCommand(@"ALTER TABLE sqc_events DROP COLUMN event_flags");
|
||||
RunMigrationCommand(@"ALTER TABLE sqc_events DROP COLUMN event_flags");
|
||||
}
|
||||
|
||||
private void UpdateEventTypeNames() {
|
||||
RunCommand(@"UPDATE sqc_events SET event_type = ""msg:add"" WHERE event_type = ""SharpChat.Events.ChatMessage""");
|
||||
RunCommand(@"UPDATE sqc_events SET event_type = ""user:connect"" WHERE event_type = ""SharpChat.Events.UserConnectEvent""");
|
||||
RunCommand(@"UPDATE sqc_events SET event_type = ""user:disconnect"" WHERE event_type = ""SharpChat.Events.UserDisconnectEvent""");
|
||||
RunCommand(@"UPDATE sqc_events SET event_type = ""chan:join"" WHERE event_type = ""SharpChat.Events.UserChannelJoinEvent""");
|
||||
RunCommand(@"UPDATE sqc_events SET event_type = ""chan:leave"" WHERE event_type = ""SharpChat.Events.UserChannelLeaveEvent""");
|
||||
RunMigrationCommand(@"UPDATE sqc_events SET event_type = ""msg:add"" WHERE event_type = ""SharpChat.Events.ChatMessage""");
|
||||
RunMigrationCommand(@"UPDATE sqc_events SET event_type = ""user:connect"" WHERE event_type = ""SharpChat.Events.UserConnectEvent""");
|
||||
RunMigrationCommand(@"UPDATE sqc_events SET event_type = ""user:disconnect"" WHERE event_type = ""SharpChat.Events.UserDisconnectEvent""");
|
||||
RunMigrationCommand(@"UPDATE sqc_events SET event_type = ""chan:join"" WHERE event_type = ""SharpChat.Events.UserChannelJoinEvent""");
|
||||
RunMigrationCommand(@"UPDATE sqc_events SET event_type = ""chan:leave"" WHERE event_type = ""SharpChat.Events.UserChannelLeaveEvent""");
|
||||
}
|
||||
|
||||
private void AllowNullTarget() {
|
||||
RunCommand(
|
||||
RunMigrationCommand(
|
||||
"ALTER TABLE `sqc_events`"
|
||||
+ " CHANGE COLUMN `event_target` `event_target` VARBINARY(255) NULL AFTER `event_type`;"
|
||||
);
|
||||
}
|
||||
|
||||
private void CreateEventsTable() {
|
||||
RunCommand(
|
||||
RunMigrationCommand(
|
||||
"CREATE TABLE `sqc_events` ("
|
||||
+ "`event_id` BIGINT(20) NOT NULL,"
|
||||
+ "`event_sender` BIGINT(20) UNSIGNED NULL DEFAULT NULL,"
|
||||
|
|
21
SharpChatCommon/Events/ChannelAddEventData.cs
Normal file
21
SharpChatCommon/Events/ChannelAddEventData.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharpChat.Events {
|
||||
[ChatEventDataFor("chan:add")]
|
||||
public class ChannelAddEventData : ChatEventData {
|
||||
[JsonPropertyName("temp")]
|
||||
public bool IsTemporary { get; }
|
||||
|
||||
[JsonPropertyName("rank")]
|
||||
public int MinRank { get; }
|
||||
|
||||
[JsonPropertyName("pass")]
|
||||
public string Password { get; }
|
||||
|
||||
public ChannelAddEventData(bool isTemporary, int minRank, string password) {
|
||||
IsTemporary = isTemporary;
|
||||
MinRank = minRank;
|
||||
Password = password;
|
||||
}
|
||||
}
|
||||
}
|
35
SharpChatCommon/Events/ChannelUpdateEventData.cs
Normal file
35
SharpChatCommon/Events/ChannelUpdateEventData.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharpChat.Events {
|
||||
[ChatEventDataFor("chan:update")]
|
||||
public class ChannelUpdateEventData : ChatEventData {
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; }
|
||||
|
||||
[JsonPropertyName("temp")]
|
||||
public bool? IsTemporary { get; }
|
||||
|
||||
[JsonPropertyName("rank")]
|
||||
public int? MinRank { get; }
|
||||
|
||||
[JsonPropertyName("pass")]
|
||||
public string? Password { get; }
|
||||
|
||||
[JsonPropertyName("owner")]
|
||||
public long? OwnerId { get; set; }
|
||||
|
||||
public ChannelUpdateEventData(
|
||||
string? name = null,
|
||||
bool? isTemporary = null,
|
||||
int? minRank = null,
|
||||
string? password = null,
|
||||
long? ownerId = null
|
||||
) {
|
||||
Name = name;
|
||||
IsTemporary = isTemporary;
|
||||
MinRank = minRank;
|
||||
Password = password;
|
||||
OwnerId = ownerId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,53 @@ namespace SharpChat.Events {
|
|||
return info;
|
||||
}
|
||||
|
||||
public ChatEventInfo Dispatch(
|
||||
string eventType,
|
||||
long senderId,
|
||||
string senderName,
|
||||
Colour senderColour,
|
||||
int senderRank,
|
||||
string senderNickName,
|
||||
UserPermissions senderPerms,
|
||||
ChatEventData? eventData = null
|
||||
) {
|
||||
return Dispatch(new ChatEventInfo(
|
||||
SharpId.Next(),
|
||||
eventType,
|
||||
DateTimeOffset.UtcNow,
|
||||
string.Empty,
|
||||
senderId,
|
||||
senderName,
|
||||
senderColour,
|
||||
senderRank,
|
||||
senderNickName,
|
||||
senderPerms,
|
||||
eventData
|
||||
));
|
||||
}
|
||||
|
||||
public ChatEventInfo Dispatch(
|
||||
string eventType,
|
||||
DateTimeOffset eventCreated,
|
||||
string channelName,
|
||||
UserInfo userInfo,
|
||||
ChatEventData? eventData = null
|
||||
) {
|
||||
return Dispatch(new ChatEventInfo(
|
||||
SharpId.Next(),
|
||||
eventType,
|
||||
eventCreated,
|
||||
channelName,
|
||||
userInfo.UserId,
|
||||
userInfo.UserName,
|
||||
userInfo.Colour,
|
||||
userInfo.Rank,
|
||||
userInfo.NickName,
|
||||
userInfo.Permissions,
|
||||
eventData
|
||||
));
|
||||
}
|
||||
|
||||
public ChatEventInfo Dispatch(
|
||||
string eventType,
|
||||
DateTimeOffset eventCreated,
|
||||
|
@ -47,6 +94,27 @@ namespace SharpChat.Events {
|
|||
));
|
||||
}
|
||||
|
||||
public ChatEventInfo Dispatch(
|
||||
string eventType,
|
||||
string channelName,
|
||||
ChatEventInfo userInfo,
|
||||
ChatEventData? eventData = null
|
||||
) {
|
||||
return Dispatch(new ChatEventInfo(
|
||||
SharpId.Next(),
|
||||
eventType,
|
||||
DateTimeOffset.UtcNow,
|
||||
channelName,
|
||||
userInfo.SenderId,
|
||||
userInfo.SenderName,
|
||||
userInfo.SenderColour,
|
||||
userInfo.SenderRank,
|
||||
userInfo.SenderNickName,
|
||||
userInfo.SenderPerms,
|
||||
eventData
|
||||
));
|
||||
}
|
||||
|
||||
public ChatEventInfo Dispatch(
|
||||
string eventType,
|
||||
string channelName,
|
||||
|
@ -84,5 +152,13 @@ namespace SharpChat.Events {
|
|||
) {
|
||||
return Dispatch(eventType, string.Empty, userInfo, eventData);
|
||||
}
|
||||
|
||||
public ChatEventInfo Dispatch(
|
||||
string eventType,
|
||||
ChatEventInfo userInfo,
|
||||
ChatEventData? eventData = null
|
||||
) {
|
||||
return Dispatch(eventType, string.Empty, userInfo, eventData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
13
SharpChatCommon/Events/UserAddEventData.cs
Normal file
13
SharpChatCommon/Events/UserAddEventData.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharpChat.Events {
|
||||
[ChatEventDataFor("user:add")]
|
||||
public class UserAddEventData : ChatEventData {
|
||||
[JsonPropertyName("super")]
|
||||
public bool IsSuper { get; }
|
||||
|
||||
public UserAddEventData(bool isSuper) {
|
||||
IsSuper = isSuper;
|
||||
}
|
||||
}
|
||||
}
|
13
SharpChatCommon/Events/UserConnectEventData.cs
Normal file
13
SharpChatCommon/Events/UserConnectEventData.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharpChat.Events {
|
||||
[ChatEventDataFor("user:connect")]
|
||||
public class UserConnectEventData : ChatEventData {
|
||||
[JsonPropertyName("notify")]
|
||||
public bool Notify { get; }
|
||||
|
||||
public UserConnectEventData(bool notify) {
|
||||
Notify = notify;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,9 +18,11 @@ namespace SharpChat.Events {
|
|||
Expires = expires;
|
||||
}
|
||||
|
||||
public UserKickBanEventData(
|
||||
public static UserKickBanEventData OfDuration(
|
||||
UserDisconnectReason reason,
|
||||
TimeSpan duration
|
||||
) : this(reason, DateTimeOffset.UtcNow.Add(duration)) {}
|
||||
) {
|
||||
return new UserKickBanEventData(reason, DateTimeOffset.UtcNow.Add(duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace SharpChat {
|
||||
using SharpChat.Events;
|
||||
|
||||
namespace SharpChat {
|
||||
public class UserInfo {
|
||||
public const int DEFAULT_SIZE = 30;
|
||||
public const int DEFAULT_MINIMUM_DELAY = 10000;
|
||||
|
@ -30,6 +32,16 @@
|
|||
IsSuper = isSuper;
|
||||
}
|
||||
|
||||
public UserInfo(ChatEventInfo eventInfo)
|
||||
: this(
|
||||
eventInfo.SenderId,
|
||||
eventInfo.SenderName,
|
||||
eventInfo.SenderColour,
|
||||
eventInfo.SenderRank,
|
||||
eventInfo.SenderPerms,
|
||||
eventInfo.SenderNickName
|
||||
) {}
|
||||
|
||||
public static string GetDMChannelName(UserInfo user1, UserInfo user2) {
|
||||
return user1.UserId < user2.UserId
|
||||
? $"@{user1.UserId}-{user2.UserId}"
|
||||
|
|
|
@ -12,18 +12,18 @@ namespace SharpChat {
|
|||
UserAndNickName = UserName | NickName,
|
||||
}
|
||||
|
||||
private readonly List<UserInfo> Users = new();
|
||||
private readonly Dictionary<long, UserInfo> Users = new();
|
||||
|
||||
public int TotalCount { get; private set; }
|
||||
|
||||
public UserInfo[] All => Users.ToArray();
|
||||
public UserInfo[] All => Users.Values.ToArray();
|
||||
|
||||
public UserInfo? Get(
|
||||
long? userId = null,
|
||||
string? name = null,
|
||||
NameTarget nameTarget = NameTarget.UserName
|
||||
) {
|
||||
foreach(UserInfo info in Users) {
|
||||
foreach(UserInfo info in Users.Values) {
|
||||
if(userId != null && info.UserId != userId)
|
||||
continue;
|
||||
if(name != null) {
|
||||
|
@ -52,7 +52,7 @@ namespace SharpChat {
|
|||
) {
|
||||
List<UserInfo> users = new();
|
||||
|
||||
foreach(UserInfo info in Users) {
|
||||
foreach(UserInfo info in Users.Values) {
|
||||
if(minRank != null && info.Rank < minRank)
|
||||
continue;
|
||||
|
||||
|
@ -79,34 +79,32 @@ namespace SharpChat {
|
|||
}
|
||||
|
||||
public void Add(UserInfo info) {
|
||||
if(Get(info.UserId, info.UserName) != null)
|
||||
throw new ArgumentException("A user with that id and/or name has already been registred.", nameof(info));
|
||||
if(Users.ContainsKey(info.UserId))
|
||||
return;
|
||||
|
||||
Users.Add(info);
|
||||
Users.Add(info.UserId, info);
|
||||
++TotalCount;
|
||||
}
|
||||
|
||||
public void Remove(UserInfo info) {
|
||||
if(!Users.Contains(info)) {
|
||||
Remove(info.UserId);
|
||||
return;
|
||||
}
|
||||
|
||||
Users.Remove(info);
|
||||
public void Remove(long userId) {
|
||||
if(!Users.ContainsKey(userId))
|
||||
return;
|
||||
|
||||
Users.Remove(userId);
|
||||
--TotalCount;
|
||||
}
|
||||
|
||||
public void Remove(
|
||||
long? userId = null,
|
||||
string? name = null,
|
||||
string name,
|
||||
NameTarget nameTarget = NameTarget.UserName
|
||||
) {
|
||||
UserInfo? info = Get(userId, name, nameTarget);
|
||||
if(info == null)
|
||||
return;
|
||||
|
||||
Users.Remove(info);
|
||||
--TotalCount;
|
||||
UserInfo? info = Get(name: name, nameTarget: nameTarget);
|
||||
if(info != null)
|
||||
Remove(info.UserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue