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;
|
using System.Linq;
|
||||||
|
|
||||||
namespace SharpChat.SockChat.Commands {
|
namespace SharpChat.SockChat.Commands {
|
||||||
|
@ -31,35 +33,34 @@ namespace SharpChat.SockChat.Commands {
|
||||||
return;
|
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());
|
ctx.Chat.SendTo(ctx.User, new ChannelNameFormatErrorS2CPacket());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ctx.Chat.Channels.Get(createChanName, SockChatUtility.SanitiseChannelName) != null) {
|
if(ctx.Chat.Channels.Get(channelName, SockChatUtility.SanitiseChannelName) != null) {
|
||||||
ctx.Chat.SendTo(ctx.User, new ChannelNameInUseErrorS2CPacket(createChanName));
|
ctx.Chat.SendTo(ctx.User, new ChannelNameInUseErrorS2CPacket(channelName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelInfo createChan = new(
|
ctx.Chat.Events.Dispatch(
|
||||||
createChanName,
|
"chan:add",
|
||||||
isTemporary: !ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPermanent),
|
channelName,
|
||||||
rank: createChanHierarchy,
|
ctx.User,
|
||||||
ownerId: ctx.User.UserId
|
new ChannelAddEventData(
|
||||||
|
!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPermanent),
|
||||||
|
createChanHierarchy,
|
||||||
|
string.Empty
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.Chat.Channels.Add(createChan, sanitiseName: SockChatUtility.SanitiseChannelName);
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
foreach(UserInfo ccu in ctx.Chat.Users.GetMany(minRank: ctx.Channel.Rank))
|
ctx.Chat.Events.Dispatch("chan:leave", now, ctx.Channel, ctx.User);
|
||||||
ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(
|
ctx.Chat.Events.Dispatch("chan:join", now, channelName, ctx.User);
|
||||||
ctx.Channel.Name,
|
|
||||||
ctx.Channel.HasPassword,
|
|
||||||
ctx.Channel.IsTemporary
|
|
||||||
));
|
|
||||||
|
|
||||||
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
|
ctx.Chat.SendTo(ctx.User, new ChannelCreateResponseS2CPacket(channelName));
|
||||||
ctx.Chat.SendTo(ctx.User, new ChannelCreateResponseS2CPacket(createChan.Name));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,12 @@ namespace SharpChat.SockChat.Commands {
|
||||||
return;
|
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));
|
ctx.Chat.SendTo(ctx.User, new ChannelDeleteNotAllowedErrorS2CPacket(delChan.Name));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Chat.RemoveChannel(delChan);
|
ctx.Chat.Events.Dispatch("chan:delete", delChan, ctx.User);
|
||||||
ctx.Chat.SendTo(ctx.User, new ChannelDeleteResponseS2CPacket(delChan.Name));
|
ctx.Chat.SendTo(ctx.User, new ChannelDeleteResponseS2CPacket(delChan.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using SharpChat.SockChat.PacketsS2C;
|
using SharpChat.SockChat.PacketsS2C;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace SharpChat.SockChat.Commands {
|
namespace SharpChat.SockChat.Commands {
|
||||||
|
@ -8,16 +9,40 @@ namespace SharpChat.SockChat.Commands {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispatch(SockChatClientCommandContext ctx) {
|
public void Dispatch(SockChatClientCommandContext ctx) {
|
||||||
string joinChanStr = ctx.Args.FirstOrDefault() ?? string.Empty;
|
string channelName = ctx.Args.FirstOrDefault() ?? string.Empty;
|
||||||
ChannelInfo? joinChan = ctx.Chat.Channels.Get(joinChanStr, SockChatUtility.SanitiseChannelName);
|
string password = string.Join(' ', ctx.Args.Skip(1));
|
||||||
|
|
||||||
if(joinChan == null) {
|
ChannelInfo? channelInfo = ctx.Chat.Channels.Get(channelName, SockChatUtility.SanitiseChannelName);
|
||||||
ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorS2CPacket(joinChanStr));
|
if(channelInfo == null) {
|
||||||
ctx.Chat.ForceChannel(ctx.User);
|
ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorS2CPacket(channelName));
|
||||||
|
ctx.Chat.SendTo(ctx.User, new UserChannelForceJoinS2CPacket(ctx.Channel.Name));
|
||||||
return;
|
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 {
|
namespace SharpChat.SockChat.Commands {
|
||||||
public class ChannelPasswordCommand : ISockChatClientCommand {
|
public class ChannelPasswordCommand : ISockChatClientCommand {
|
||||||
|
@ -8,7 +9,7 @@ namespace SharpChat.SockChat.Commands {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispatch(SockChatClientCommandContext ctx) {
|
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));
|
ctx.Chat.SendTo(ctx.User, new CommandNotAllowedErrorS2CPacket(ctx.Name));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +19,7 @@ namespace SharpChat.SockChat.Commands {
|
||||||
if(string.IsNullOrWhiteSpace(chanPass))
|
if(string.IsNullOrWhiteSpace(chanPass))
|
||||||
chanPass = string.Empty;
|
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());
|
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;
|
using System.Linq;
|
||||||
|
|
||||||
namespace SharpChat.SockChat.Commands {
|
namespace SharpChat.SockChat.Commands {
|
||||||
|
@ -10,17 +11,17 @@ namespace SharpChat.SockChat.Commands {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispatch(SockChatClientCommandContext ctx) {
|
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));
|
ctx.Chat.SendTo(ctx.User, new CommandNotAllowedErrorS2CPacket(ctx.Name));
|
||||||
return;
|
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());
|
ctx.Chat.SendTo(ctx.User, new ChannelRankTooHighErrorS2CPacket());
|
||||||
return;
|
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());
|
ctx.Chat.SendTo(ctx.User, new ChannelRankChangedResponseS2CPacket());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace SharpChat.SockChat.Commands {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(duration <= TimeSpan.Zero) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ namespace SharpChat.SockChat.Commands {
|
||||||
duration, banReason
|
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();
|
}).Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,13 @@ namespace SharpChat.SockChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserInfo[] userInfos = ctx.Chat.Users.GetMany(
|
||||||
|
ctx.Chat.ChannelsUsers.GetChannelUserIds(channel)
|
||||||
|
);
|
||||||
|
|
||||||
ctx.Chat.SendTo(ctx.User, new WhoChannelResponseS2CPacket(
|
ctx.Chat.SendTo(ctx.User, new WhoChannelResponseS2CPacket(
|
||||||
channel.Name,
|
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))
|
SockChatUtility.GetUserName(ctx.User, ctx.Chat.UserStatuses.Get(ctx.User))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace SharpChat.SockChat.PacketsC2S {
|
||||||
private readonly DateTimeOffset Started;
|
private readonly DateTimeOffset Started;
|
||||||
private readonly MisuzuClient Misuzu;
|
private readonly MisuzuClient Misuzu;
|
||||||
private readonly ChannelInfo DefaultChannel;
|
private readonly ChannelInfo DefaultChannel;
|
||||||
|
private readonly CachedValue<string> MOTDHeaderFormat;
|
||||||
private readonly CachedValue<int> MaxMessageLength;
|
private readonly CachedValue<int> MaxMessageLength;
|
||||||
private readonly CachedValue<int> MaxConnections;
|
private readonly CachedValue<int> MaxConnections;
|
||||||
|
|
||||||
|
@ -22,12 +23,14 @@ namespace SharpChat.SockChat.PacketsC2S {
|
||||||
DateTimeOffset started,
|
DateTimeOffset started,
|
||||||
MisuzuClient msz,
|
MisuzuClient msz,
|
||||||
ChannelInfo? defaultChannel,
|
ChannelInfo? defaultChannel,
|
||||||
|
CachedValue<string> motdHeaderFormat,
|
||||||
CachedValue<int> maxMsgLength,
|
CachedValue<int> maxMsgLength,
|
||||||
CachedValue<int> maxConns
|
CachedValue<int> maxConns
|
||||||
) {
|
) {
|
||||||
Started = started;
|
Started = started;
|
||||||
Misuzu = msz;
|
Misuzu = msz;
|
||||||
DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
|
DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
|
||||||
|
MOTDHeaderFormat = motdHeaderFormat;
|
||||||
MaxMessageLength = maxMsgLength;
|
MaxMessageLength = maxMsgLength;
|
||||||
MaxConnections = maxConns;
|
MaxConnections = maxConns;
|
||||||
}
|
}
|
||||||
|
@ -118,19 +121,35 @@ namespace SharpChat.SockChat.PacketsC2S {
|
||||||
return;
|
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();
|
await ctx.Chat.ContextAccess.WaitAsync();
|
||||||
try {
|
try {
|
||||||
UserInfo? user = ctx.Chat.Users.Get(fai.UserId);
|
UserInfo? user = ctx.Chat.Users.Get(fai.UserId);
|
||||||
|
|
||||||
if(user == null) {
|
if(user == null) {
|
||||||
user = new UserInfo(
|
ctx.Chat.Events.Dispatch(
|
||||||
|
"user:add",
|
||||||
fai.UserId,
|
fai.UserId,
|
||||||
fai.UserName ?? string.Empty,
|
fai.UserName ?? string.Empty,
|
||||||
fai.Colour,
|
fai.Colour,
|
||||||
fai.Rank,
|
fai.Rank,
|
||||||
|
string.Empty,
|
||||||
fai.Permissions,
|
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 {
|
} else {
|
||||||
string? updName = !user.UserName.Equals(fai.UserName) ? fai.UserName : null;
|
string? updName = !user.UserName.Equals(fai.UserName) ? fai.UserName : null;
|
||||||
int? updColour = (updColour = fai.Colour.ToMisuzu()) != user.Colour.ToMisuzu() ? updColour : 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.Connection.BumpPing();
|
||||||
ctx.Chat.Connections.SetUser(ctx.Connection, user);
|
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)) {
|
if(File.Exists(MOTD_FILE)) {
|
||||||
IEnumerable<string> lines = File.ReadAllLines(MOTD_FILE).Where(x => !string.IsNullOrWhiteSpace(x));
|
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.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 {
|
} finally {
|
||||||
ctx.Chat.ContextAccess.Release();
|
ctx.Chat.ContextAccess.Release();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,6 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleEvent(ChatEventInfo info) {
|
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);
|
UserStatusInfo userStatusInfo = UserStatuses.Get(info.SenderId);
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(info.ChannelName))
|
if(!string.IsNullOrWhiteSpace(info.ChannelName))
|
||||||
|
@ -37,7 +35,27 @@ namespace SharpChat {
|
||||||
// TODO: should user:connect and user:disconnect be channel agnostic?
|
// TODO: should user:connect and user:disconnect be channel agnostic?
|
||||||
|
|
||||||
switch(info.Type) {
|
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":
|
case "user:connect":
|
||||||
|
if(info.Data is not UserConnectEventData ucData || !ucData.Notify)
|
||||||
|
break;
|
||||||
|
|
||||||
SendTo(info.ChannelName, new UserConnectS2CPacket(
|
SendTo(info.ChannelName, new UserConnectS2CPacket(
|
||||||
info.Id,
|
info.Id,
|
||||||
info.Created,
|
info.Created,
|
||||||
|
@ -47,14 +65,10 @@ namespace SharpChat {
|
||||||
info.SenderRank,
|
info.SenderRank,
|
||||||
info.SenderPerms
|
info.SenderPerms
|
||||||
));
|
));
|
||||||
|
ChannelsUsers.Join(info.ChannelName, info.SenderId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "user:disconnect":
|
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));
|
ChannelInfo[] channels = Channels.GetMany(ChannelsUsers.GetUserChannelNames(info.SenderId));
|
||||||
ChannelsUsers.DeleteUser(info.SenderId);
|
ChannelsUsers.DeleteUser(info.SenderId);
|
||||||
|
|
||||||
|
@ -68,8 +82,8 @@ namespace SharpChat {
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach(ChannelInfo chan in channels) {
|
foreach(ChannelInfo chan in channels) {
|
||||||
if(chan.IsTemporary && chan.IsOwner(info.SenderId))
|
if(chan.IsTemporary && chan.OwnerId == info.SenderId)
|
||||||
RemoveChannel(chan);
|
Events.Dispatch("chan:delete", chan.Name, info);
|
||||||
else
|
else
|
||||||
SendTo(chan, udPacket);
|
SendTo(chan, udPacket);
|
||||||
}
|
}
|
||||||
|
@ -100,42 +114,46 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "user:update":
|
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;
|
break;
|
||||||
|
|
||||||
bool uuHasChanged = false;
|
bool uuHasChanged = false;
|
||||||
string? uuPrevName = null;
|
string? uuPrevName = null;
|
||||||
|
|
||||||
if(userUpdate.Name != null && !userUpdate.Name.Equals(userInfo.UserName)) {
|
if(userUpdate.Name != null && !userUpdate.Name.Equals(uuUserInfo.UserName)) {
|
||||||
userInfo.UserName = userUpdate.Name;
|
uuUserInfo.UserName = userUpdate.Name;
|
||||||
uuHasChanged = true;
|
uuHasChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(userUpdate.NickName != null && !userUpdate.NickName.Equals(userInfo.NickName)) {
|
if(userUpdate.NickName != null && !userUpdate.NickName.Equals(uuUserInfo.NickName)) {
|
||||||
if(userUpdate.Notify)
|
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;
|
uuHasChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(userUpdate.Colour.HasValue && userUpdate.Colour != userInfo.Colour.ToMisuzu()) {
|
if(userUpdate.Colour.HasValue && userUpdate.Colour != uuUserInfo.Colour.ToMisuzu()) {
|
||||||
userInfo.Colour = Colour.FromMisuzu(userUpdate.Colour.Value);
|
uuUserInfo.Colour = Colour.FromMisuzu(userUpdate.Colour.Value);
|
||||||
uuHasChanged = true;
|
uuHasChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(userUpdate.Rank != null && userUpdate.Rank != userInfo.Rank) {
|
if(userUpdate.Rank != null && userUpdate.Rank != uuUserInfo.Rank) {
|
||||||
userInfo.Rank = userUpdate.Rank.Value;
|
uuUserInfo.Rank = userUpdate.Rank.Value;
|
||||||
uuHasChanged = true;
|
uuHasChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(userUpdate.Perms.HasValue && userUpdate.Perms != userInfo.Permissions) {
|
if(userUpdate.Perms.HasValue && userUpdate.Perms != uuUserInfo.Permissions) {
|
||||||
userInfo.Permissions = userUpdate.Perms.Value;
|
uuUserInfo.Permissions = userUpdate.Perms.Value;
|
||||||
uuHasChanged = true;
|
uuHasChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(userUpdate.IsSuper.HasValue && userUpdate.IsSuper != userInfo.IsSuper)
|
if(userUpdate.IsSuper.HasValue && userUpdate.IsSuper != uuUserInfo.IsSuper)
|
||||||
userInfo.IsSuper = userUpdate.IsSuper.Value;
|
uuUserInfo.IsSuper = userUpdate.IsSuper.Value;
|
||||||
|
|
||||||
if(uuHasChanged) {
|
if(uuHasChanged) {
|
||||||
if(uuPrevName != null)
|
if(uuPrevName != null)
|
||||||
|
@ -143,15 +161,15 @@ namespace SharpChat {
|
||||||
info.Id,
|
info.Id,
|
||||||
info.Created,
|
info.Created,
|
||||||
string.IsNullOrWhiteSpace(info.SenderNickName) ? uuPrevName : $"~{info.SenderNickName}",
|
string.IsNullOrWhiteSpace(info.SenderNickName) ? uuPrevName : $"~{info.SenderNickName}",
|
||||||
SockChatUtility.GetUserName(userInfo, userStatusInfo)
|
SockChatUtility.GetUserName(uuUserInfo, userStatusInfo)
|
||||||
));
|
));
|
||||||
|
|
||||||
SendToUserChannels(info.SenderId, new UserUpdateS2CPacket(
|
SendToUserChannels(info.SenderId, new UserUpdateS2CPacket(
|
||||||
userInfo.UserId,
|
uuUserInfo.UserId,
|
||||||
SockChatUtility.GetUserName(userInfo, userStatusInfo),
|
SockChatUtility.GetUserName(uuUserInfo, userStatusInfo),
|
||||||
userInfo.Colour,
|
uuUserInfo.Colour,
|
||||||
userInfo.Rank,
|
uuUserInfo.Rank,
|
||||||
userInfo.Permissions
|
uuUserInfo.Permissions
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -189,17 +207,106 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "chan:join":
|
case "chan:join":
|
||||||
SendTo(info.ChannelName, new UserChannelJoinS2CPacket(
|
HandleUserChannelJoin(
|
||||||
info.SenderId,
|
info.ChannelName,
|
||||||
SockChatUtility.GetUserName(info, userStatusInfo),
|
new UserInfo(info), // kinda stinky
|
||||||
info.SenderColour,
|
userStatusInfo
|
||||||
info.SenderRank,
|
);
|
||||||
info.SenderPerms
|
|
||||||
));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "chan:leave":
|
case "chan:leave":
|
||||||
|
ChannelsUsers.Leave(info.ChannelName, info.SenderId);
|
||||||
SendTo(info.ChannelName, new UserChannelLeaveS2CPacket(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;
|
break;
|
||||||
|
|
||||||
case "msg:delete":
|
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() {
|
public void Update() {
|
||||||
ConnectionInfo[] timedOut = Connections.GetTimedOut();
|
ConnectionInfo[] timedOut = Connections.GetTimedOut();
|
||||||
foreach(ConnectionInfo conn in timedOut) {
|
foreach(ConnectionInfo conn in timedOut) {
|
||||||
|
@ -283,6 +421,7 @@ namespace SharpChat {
|
||||||
|
|
||||||
foreach(UserInfo user in Users.All)
|
foreach(UserInfo user in Users.All)
|
||||||
if(!Connections.HasUser(user)) {
|
if(!Connections.HasUser(user)) {
|
||||||
|
Events.Dispatch("user:delete", user);
|
||||||
Events.Dispatch(
|
Events.Dispatch(
|
||||||
"user:disconnect",
|
"user:disconnect",
|
||||||
ChannelsUsers.GetUserLastChannel(user),
|
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) {
|
public void HandleChannelEventLog(string channelName, Action<ISockChatS2CPacket> handler) {
|
||||||
foreach(ChatEventInfo info in EventStorage.GetChannelEventLog(channelName)) {
|
foreach(ChatEventInfo info in EventStorage.GetChannelEventLog(channelName)) {
|
||||||
switch(info.Type) {
|
switch(info.Type) {
|
||||||
|
@ -330,11 +465,12 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "user:connect":
|
case "user:connect":
|
||||||
handler(new UserConnectLogS2CPacket(
|
if(info.Data is UserConnectEventData ucData && ucData.Notify)
|
||||||
info.Id,
|
handler(new UserConnectLogS2CPacket(
|
||||||
info.Created,
|
info.Id,
|
||||||
SockChatUtility.GetUserName(info)
|
info.Created,
|
||||||
));
|
SockChatUtility.GetUserName(info)
|
||||||
|
));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "user:disconnect":
|
case "user:disconnect":
|
||||||
|
@ -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) {
|
public void Send(ISockChatS2CPacket packet) {
|
||||||
string data = packet.Pack();
|
string data = packet.Pack();
|
||||||
Connections.WithAuthed(conn => {
|
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) {
|
public void SendToUserChannels(long userId, ISockChatS2CPacket packet) {
|
||||||
ChannelInfo[] chans = Channels.GetMany(ChannelsUsers.GetUserChannelNames(userId));
|
ChannelInfo[] chans = Channels.GetMany(ChannelsUsers.GetUserChannelNames(userId));
|
||||||
string data = packet.Pack();
|
string data = packet.Pack();
|
||||||
foreach(ChannelInfo chan in chans)
|
foreach(ChannelInfo chan in chans)
|
||||||
SendTo(chan, data);
|
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)
|
if(Context.Channels.PublicCount < 1)
|
||||||
Context.Channels.Add(new ChannelInfo("Default"));
|
Context.Channels.Add(new ChannelInfo("Default"));
|
||||||
|
|
||||||
|
CachedValue<string> motdHeaderFormat = config.ReadCached("motd", @"Welcome to Flashii Chat, {0}!");
|
||||||
GuestHandlers.Add(new AuthC2SPacketHandler(
|
GuestHandlers.Add(new AuthC2SPacketHandler(
|
||||||
started,
|
started,
|
||||||
Misuzu,
|
Misuzu,
|
||||||
Context.Channels.MainChannel,
|
Context.Channels.MainChannel,
|
||||||
|
motdHeaderFormat,
|
||||||
MaxMessageLength,
|
MaxMessageLength,
|
||||||
MaxConnections
|
MaxConnections
|
||||||
));
|
));
|
||||||
|
@ -156,13 +158,15 @@ namespace SharpChat.SockChat {
|
||||||
|
|
||||||
if(!Context.Connections.HasUser(conn.UserId)) {
|
if(!Context.Connections.HasUser(conn.UserId)) {
|
||||||
UserInfo? userInfo = Context.Users.Get(conn.UserId);
|
UserInfo? userInfo = Context.Users.Get(conn.UserId);
|
||||||
if(userInfo != null)
|
if(userInfo != null) {
|
||||||
|
Context.Events.Dispatch("user:delete", userInfo);
|
||||||
Context.Events.Dispatch(
|
Context.Events.Dispatch(
|
||||||
"user:disconnect",
|
"user:disconnect",
|
||||||
Context.ChannelsUsers.GetUserLastChannel(userInfo),
|
Context.ChannelsUsers.GetUserLastChannel(conn.UserId),
|
||||||
userInfo,
|
userInfo,
|
||||||
new UserDisconnectEventData(UserDisconnectReason.Leave)
|
new UserDisconnectEventData(UserDisconnectReason.Leave)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.Update();
|
Context.Update();
|
||||||
|
@ -205,7 +209,7 @@ namespace SharpChat.SockChat {
|
||||||
if(banDuration == TimeSpan.MinValue) {
|
if(banDuration == TimeSpan.MinValue) {
|
||||||
Context.SendTo(userInfo, new FloodWarningS2CPacket());
|
Context.SendTo(userInfo, new FloodWarningS2CPacket());
|
||||||
} else {
|
} 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)
|
if(banDuration > TimeSpan.Zero)
|
||||||
Misuzu.CreateBanAsync(
|
Misuzu.CreateBanAsync(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public class ChannelInfo {
|
public class ChannelInfo {
|
||||||
public string Name { get; }
|
public string Name { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public bool IsTemporary { get; set; }
|
public bool IsTemporary { get; set; }
|
||||||
public int Rank { get; set; }
|
public int Rank { get; set; }
|
||||||
|
@ -25,15 +25,5 @@
|
||||||
Rank = rank;
|
Rank = rank;
|
||||||
OwnerId = ownerId;
|
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 {
|
namespace SharpChat {
|
||||||
public class ChannelsContext {
|
public class ChannelsContext {
|
||||||
private readonly List<ChannelInfo> Channels = new();
|
private readonly Dictionary<string, ChannelInfo> Channels = new();
|
||||||
|
|
||||||
public ChannelInfo? MainChannel { get; private set; }
|
public ChannelInfo? MainChannel { get; private set; }
|
||||||
public int TotalCount { get; private set; }
|
public int TotalCount { get; private set; }
|
||||||
public int PublicCount { get; private set; }
|
public int PublicCount { get; private set; }
|
||||||
|
|
||||||
public ChannelInfo[] All => Channels.ToArray();
|
public ChannelInfo[] All => Channels.Values.ToArray();
|
||||||
|
|
||||||
public ChannelInfo? Get(
|
public ChannelInfo? Get(
|
||||||
string? name,
|
string? name,
|
||||||
|
@ -19,7 +19,7 @@ namespace SharpChat {
|
||||||
if(string.IsNullOrWhiteSpace(name))
|
if(string.IsNullOrWhiteSpace(name))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
foreach(ChannelInfo info in Channels) {
|
foreach(ChannelInfo info in Channels.Values) {
|
||||||
string chanName = info.Name;
|
string chanName = info.Name;
|
||||||
if(sanitise != null)
|
if(sanitise != null)
|
||||||
chanName = sanitise(chanName);
|
chanName = sanitise(chanName);
|
||||||
|
@ -44,7 +44,7 @@ namespace SharpChat {
|
||||||
for(int i = 0; i < names.Length; ++i)
|
for(int i = 0; i < names.Length; ++i)
|
||||||
names[i] = names[i].ToLowerInvariant();
|
names[i] = names[i].ToLowerInvariant();
|
||||||
|
|
||||||
foreach(ChannelInfo info in Channels) {
|
foreach(ChannelInfo info in Channels.Values) {
|
||||||
if(info.Rank > minRank)
|
if(info.Rank > minRank)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -71,18 +71,14 @@ namespace SharpChat {
|
||||||
return chans.ToArray();
|
return chans.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(
|
public void Add(ChannelInfo info, bool forceMain = false) {
|
||||||
ChannelInfo info,
|
if(Channels.ContainsKey(info.Name.ToLowerInvariant()))
|
||||||
bool forceMain = false,
|
|
||||||
Func<string, string>? sanitiseName = null
|
|
||||||
) {
|
|
||||||
if(Get(info.Name, sanitiseName) != null)
|
|
||||||
throw new ArgumentException("A channel with this name has already been registered.", nameof(info));
|
throw new ArgumentException("A channel with this name has already been registered.", nameof(info));
|
||||||
if(string.IsNullOrWhiteSpace(info.Name))
|
if(string.IsNullOrWhiteSpace(info.Name))
|
||||||
throw new ArgumentException("Channel names may not be blank.", nameof(info));
|
throw new ArgumentException("Channel names may not be blank.", nameof(info));
|
||||||
// todo: there should be more restrictions on channel names
|
// todo: there should be more restrictions on channel names
|
||||||
|
|
||||||
Channels.Add(info);
|
Channels.Add(info.Name.ToLowerInvariant(), info);
|
||||||
|
|
||||||
++TotalCount;
|
++TotalCount;
|
||||||
if(info.IsPublic)
|
if(info.IsPublic)
|
||||||
|
@ -110,14 +106,14 @@ namespace SharpChat {
|
||||||
if(info == null)
|
if(info == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Channels.Remove(info);
|
Channels.Remove(info.Name.ToLowerInvariant());
|
||||||
|
|
||||||
--TotalCount;
|
--TotalCount;
|
||||||
if(info.IsPublic)
|
if(info.IsPublic)
|
||||||
--PublicCount;
|
--PublicCount;
|
||||||
|
|
||||||
if(MainChannel == info)
|
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) {
|
public void DeleteChannel(ChannelInfo channelInfo) {
|
||||||
DeleteChannel(channelInfo.Name);
|
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;
|
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) {
|
private MySqlDataReader? RunQuery(string command, params MySqlParameter[] parameters) {
|
||||||
try {
|
try {
|
||||||
MySqlConnection conn = GetConnection();
|
MySqlConnection conn = GetConnection();
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace SharpChat.EventStorage {
|
||||||
if(!done) {
|
if(!done) {
|
||||||
Logger.Write($"Running migration '{name}'...");
|
Logger.Write($"Running migration '{name}'...");
|
||||||
action();
|
action();
|
||||||
RunCommand(
|
RunMigrationCommand(
|
||||||
"INSERT INTO `sqc_migrations` (`migration_name`) VALUES (@name)",
|
"INSERT INTO `sqc_migrations` (`migration_name`) VALUES (@name)",
|
||||||
new MySqlParameter("name", name)
|
new MySqlParameter("name", name)
|
||||||
);
|
);
|
||||||
|
@ -19,7 +19,7 @@ namespace SharpChat.EventStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunMigrations() {
|
public void RunMigrations() {
|
||||||
RunCommand(
|
RunMigrationCommand(
|
||||||
"CREATE TABLE IF NOT EXISTS `sqc_migrations` ("
|
"CREATE TABLE IF NOT EXISTS `sqc_migrations` ("
|
||||||
+ "`migration_name` VARCHAR(255) NOT NULL,"
|
+ "`migration_name` VARCHAR(255) NOT NULL,"
|
||||||
+ "`migration_completed` TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
|
+ "`migration_completed` TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
|
||||||
|
@ -36,9 +36,9 @@ namespace SharpChat.EventStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCollationsAndUseJsonType() {
|
private void UpdateCollationsAndUseJsonType() {
|
||||||
RunCommand("UPDATE sqc_events SET event_target = LOWER(event_target)");
|
RunMigrationCommand("UPDATE sqc_events SET event_target = LOWER(event_target)");
|
||||||
RunCommand("UPDATE sqc_events SET event_sender_nick = NULL WHERE event_sender_nick = ''");
|
RunMigrationCommand("UPDATE sqc_events SET event_sender_nick = NULL WHERE event_sender_nick = ''");
|
||||||
RunCommand(
|
RunMigrationCommand(
|
||||||
"ALTER TABLE `sqc_events` COLLATE='utf8mb4_unicode_520_ci',"
|
"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_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`,"
|
+ " 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() {
|
private void DeprecateEventFlags() {
|
||||||
// StoredEventFlags.Action is just a field in the data object
|
// 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
|
// 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.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 @
|
// 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() {
|
private void UpdateEventTypeNames() {
|
||||||
RunCommand(@"UPDATE sqc_events SET event_type = ""msg:add"" WHERE event_type = ""SharpChat.Events.ChatMessage""");
|
RunMigrationCommand(@"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""");
|
RunMigrationCommand(@"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""");
|
RunMigrationCommand(@"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""");
|
RunMigrationCommand(@"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 = ""chan:leave"" WHERE event_type = ""SharpChat.Events.UserChannelLeaveEvent""");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AllowNullTarget() {
|
private void AllowNullTarget() {
|
||||||
RunCommand(
|
RunMigrationCommand(
|
||||||
"ALTER TABLE `sqc_events`"
|
"ALTER TABLE `sqc_events`"
|
||||||
+ " CHANGE COLUMN `event_target` `event_target` VARBINARY(255) NULL AFTER `event_type`;"
|
+ " CHANGE COLUMN `event_target` `event_target` VARBINARY(255) NULL AFTER `event_type`;"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateEventsTable() {
|
private void CreateEventsTable() {
|
||||||
RunCommand(
|
RunMigrationCommand(
|
||||||
"CREATE TABLE `sqc_events` ("
|
"CREATE TABLE `sqc_events` ("
|
||||||
+ "`event_id` BIGINT(20) NOT NULL,"
|
+ "`event_id` BIGINT(20) NOT NULL,"
|
||||||
+ "`event_sender` BIGINT(20) UNSIGNED NULL DEFAULT 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;
|
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(
|
public ChatEventInfo Dispatch(
|
||||||
string eventType,
|
string eventType,
|
||||||
DateTimeOffset eventCreated,
|
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(
|
public ChatEventInfo Dispatch(
|
||||||
string eventType,
|
string eventType,
|
||||||
string channelName,
|
string channelName,
|
||||||
|
@ -84,5 +152,13 @@ namespace SharpChat.Events {
|
||||||
) {
|
) {
|
||||||
return Dispatch(eventType, string.Empty, userInfo, eventData);
|
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;
|
Expires = expires;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserKickBanEventData(
|
public static UserKickBanEventData OfDuration(
|
||||||
UserDisconnectReason reason,
|
UserDisconnectReason reason,
|
||||||
TimeSpan duration
|
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 class UserInfo {
|
||||||
public const int DEFAULT_SIZE = 30;
|
public const int DEFAULT_SIZE = 30;
|
||||||
public const int DEFAULT_MINIMUM_DELAY = 10000;
|
public const int DEFAULT_MINIMUM_DELAY = 10000;
|
||||||
|
@ -30,6 +32,16 @@
|
||||||
IsSuper = isSuper;
|
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) {
|
public static string GetDMChannelName(UserInfo user1, UserInfo user2) {
|
||||||
return user1.UserId < user2.UserId
|
return user1.UserId < user2.UserId
|
||||||
? $"@{user1.UserId}-{user2.UserId}"
|
? $"@{user1.UserId}-{user2.UserId}"
|
||||||
|
|
|
@ -12,18 +12,18 @@ namespace SharpChat {
|
||||||
UserAndNickName = UserName | NickName,
|
UserAndNickName = UserName | NickName,
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<UserInfo> Users = new();
|
private readonly Dictionary<long, UserInfo> Users = new();
|
||||||
|
|
||||||
public int TotalCount { get; private set; }
|
public int TotalCount { get; private set; }
|
||||||
|
|
||||||
public UserInfo[] All => Users.ToArray();
|
public UserInfo[] All => Users.Values.ToArray();
|
||||||
|
|
||||||
public UserInfo? Get(
|
public UserInfo? Get(
|
||||||
long? userId = null,
|
long? userId = null,
|
||||||
string? name = null,
|
string? name = null,
|
||||||
NameTarget nameTarget = NameTarget.UserName
|
NameTarget nameTarget = NameTarget.UserName
|
||||||
) {
|
) {
|
||||||
foreach(UserInfo info in Users) {
|
foreach(UserInfo info in Users.Values) {
|
||||||
if(userId != null && info.UserId != userId)
|
if(userId != null && info.UserId != userId)
|
||||||
continue;
|
continue;
|
||||||
if(name != null) {
|
if(name != null) {
|
||||||
|
@ -52,7 +52,7 @@ namespace SharpChat {
|
||||||
) {
|
) {
|
||||||
List<UserInfo> users = new();
|
List<UserInfo> users = new();
|
||||||
|
|
||||||
foreach(UserInfo info in Users) {
|
foreach(UserInfo info in Users.Values) {
|
||||||
if(minRank != null && info.Rank < minRank)
|
if(minRank != null && info.Rank < minRank)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -79,34 +79,32 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(UserInfo info) {
|
public void Add(UserInfo info) {
|
||||||
if(Get(info.UserId, info.UserName) != null)
|
if(Users.ContainsKey(info.UserId))
|
||||||
throw new ArgumentException("A user with that id and/or name has already been registred.", nameof(info));
|
return;
|
||||||
|
|
||||||
Users.Add(info);
|
Users.Add(info.UserId, info);
|
||||||
++TotalCount;
|
++TotalCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(UserInfo info) {
|
public void Remove(UserInfo info) {
|
||||||
if(!Users.Contains(info)) {
|
Remove(info.UserId);
|
||||||
Remove(info.UserId);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Users.Remove(info);
|
public void Remove(long userId) {
|
||||||
|
if(!Users.ContainsKey(userId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Users.Remove(userId);
|
||||||
--TotalCount;
|
--TotalCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(
|
public void Remove(
|
||||||
long? userId = null,
|
string name,
|
||||||
string? name = null,
|
|
||||||
NameTarget nameTarget = NameTarget.UserName
|
NameTarget nameTarget = NameTarget.UserName
|
||||||
) {
|
) {
|
||||||
UserInfo? info = Get(userId, name, nameTarget);
|
UserInfo? info = Get(name: name, nameTarget: nameTarget);
|
||||||
if(info == null)
|
if(info != null)
|
||||||
return;
|
Remove(info.UserId);
|
||||||
|
|
||||||
Users.Remove(info);
|
|
||||||
--TotalCount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue