Reworking event dispatching... I think?

I did this make in february but left it uncommitted. Hopefully it's stable!
This commit is contained in:
flash 2023-07-23 21:31:13 +00:00
parent 4e0def980f
commit 8c19c22736
13 changed files with 369 additions and 70 deletions

View file

@ -1,4 +1,5 @@
using SharpChat.EventStorage; using SharpChat.Events;
using SharpChat.EventStorage;
using SharpChat.Packet; using SharpChat.Packet;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -26,6 +27,61 @@ namespace SharpChat
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore)); Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
} }
public void DispatchEvent(IChatEvent eventInfo) {
if(eventInfo is MessageCreateEvent mce) {
if(mce.IsBroadcast) {
Send(new LegacyCommandResponse(LCR.BROADCAST, false, mce.MessageText));
} else if(mce.IsPrivate) {
// The channel name returned by GetDMChannelName should not be exposed to the user, instead @<Target User> should be displayed
// e.g. nook sees @Arysil and Arysil sees @nook
// this entire routine is garbage, channels should probably in the db
if(!mce.ChannelName.StartsWith("@"))
return;
IEnumerable<long> uids = mce.ChannelName[1..].Split('-', 3).Select(u => long.TryParse(u, out long up) ? up : -1);
if(uids.Count() != 2)
return;
IEnumerable<ChatUser> users = Users.Where(u => uids.Any(uid => uid == u.UserId));
ChatUser target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
if(target == null)
return;
foreach(ChatUser user in users)
SendTo(user, new ChatMessageAddPacket(
mce.MessageId,
DateTimeOffset.Now,
mce.SenderId,
mce.SenderId == user.UserId ? $"{target.LegacyName} {mce.MessageText}" : mce.MessageText,
mce.IsAction,
true
));
} else {
ChatChannel channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
SendTo(channel, new ChatMessageAddPacket(
mce.MessageId,
DateTimeOffset.Now,
mce.SenderId,
mce.MessageText,
mce.IsAction,
false
));
}
Events.AddEvent(
mce.MessageId, "msg:add",
mce.ChannelName,
mce.SenderId, mce.SenderName, mce.SenderColour, mce.SenderRank, mce.SenderNickName, mce.SenderPerms,
new { text = mce.MessageText },
(mce.IsBroadcast ? StoredEventFlags.Broadcast : 0)
| (mce.IsAction ? StoredEventFlags.Action : 0)
| (mce.IsPrivate ? StoredEventFlags.Private : 0)
);
return;
}
}
public void Update() { public void Update() {
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(!conn.IsDisposed && conn.HasTimedOut) { if(!conn.IsDisposed && conn.HasTimedOut) {

View file

@ -1,9 +1,8 @@
using SharpChat.EventStorage; using SharpChat.Events;
using System; using System;
using System.Linq; using System.Linq;
namespace SharpChat.Commands namespace SharpChat.Commands {
{
public class ActionCommand : IChatCommand { public class ActionCommand : IChatCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("action") return ctx.NameEquals("action")
@ -11,11 +10,21 @@ namespace SharpChat.Commands
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
throw new NotImplementedException(); if(!ctx.Args.Any())
} return;
public (string, bool)? ActionDispatch(ChatCommandContext ctx) { string actionStr = string.Join(' ', ctx.Args);
return ctx.Args.Any() ? (string.Join(' ', ctx.Args), true) : null; if(string.IsNullOrWhiteSpace(actionStr))
return;
ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(),
ctx.Channel,
ctx.User,
DateTimeOffset.Now,
actionStr,
false, true, false
));
} }
} }
} }

View file

@ -1,4 +1,6 @@
using SharpChat.Packet; using SharpChat.Events;
using SharpChat.Packet;
using System;
namespace SharpChat.Commands { namespace SharpChat.Commands {
public class BroadcastCommand : IChatCommand { public class BroadcastCommand : IChatCommand {
@ -13,7 +15,14 @@ namespace SharpChat.Commands {
return; return;
} }
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.BROADCAST, false, string.Join(' ', ctx.Args))); ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(),
string.Empty,
ctx.User,
DateTimeOffset.Now,
string.Join(' ', ctx.Args),
false, false, true
));
} }
} }
} }

View file

@ -1,10 +1,9 @@
using SharpChat.EventStorage; using SharpChat.Events;
using SharpChat.Packet; using SharpChat.Packet;
using System; using System;
using System.Linq; using System.Linq;
namespace SharpChat.Commands namespace SharpChat.Commands {
{
public class WhisperCommand : IChatCommand { public class WhisperCommand : IChatCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("whisper") return ctx.NameEquals("whisper")
@ -28,25 +27,13 @@ namespace SharpChat.Commands
if(whisperUser == ctx.User) if(whisperUser == ctx.User)
return; return;
string whisperStr = string.Join(' ', ctx.Args.Skip(1)); ctx.Chat.DispatchEvent(new MessageCreateEvent(
string whisperChan = ChatUser.GetDMChannelName(ctx.User, whisperUser);
DateTimeOffset dateTime = DateTimeOffset.Now;
ctx.Chat.SendTo(whisperUser, new ChatMessageAddPacket(
SharpId.Next(), SharpId.Next(),
dateTime, ChatUser.GetDMChannelName(ctx.User, whisperUser),
ctx.User.UserId, ctx.User,
whisperStr, DateTimeOffset.Now,
false, string.Join(' ', ctx.Args.Skip(1)),
true true, false, false
));
ctx.Chat.SendTo(ctx.User, new ChatMessageAddPacket(
SharpId.Next(),
dateTime,
ctx.User.UserId,
$"{whisperUser.LegacyName} {whisperStr}",
false,
true
)); ));
} }
} }

View file

@ -3,6 +3,31 @@
namespace SharpChat.EventStorage namespace SharpChat.EventStorage
{ {
public interface IEventStorage { public interface IEventStorage {
void AddEvent(
long id, string type,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
);
void AddEvent(
long id, string type,
string channelName,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
);
void AddEvent(
long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
);
void AddEvent(
long id, string type,
string channelName,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
);
long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None); long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None);
void RemoveEvent(StoredEventInfo evt); void RemoveEvent(StoredEventInfo evt);
StoredEventInfo GetEvent(long seqId); StoredEventInfo GetEvent(long seqId);

View file

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Dynamic; using System.Dynamic;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Channels;
namespace SharpChat.EventStorage namespace SharpChat.EventStorage
{ {
@ -14,12 +15,42 @@ namespace SharpChat.EventStorage
ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString)); ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString));
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) { public void AddEvent(
long id, string type,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
string channelName,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags);
}
public void AddEvent(
long id, string type,
string channelName,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
if(type == null) if(type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
long id = SharpId.Next();
RunCommand( RunCommand(
"INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`" "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`"
+ ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)"
@ -27,15 +58,35 @@ namespace SharpChat.EventStorage
+ ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)", + ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)",
new MySqlParameter("id", id), new MySqlParameter("id", id),
new MySqlParameter("type", type), new MySqlParameter("type", type),
new MySqlParameter("target", channel?.Name ?? null), new MySqlParameter("target", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
new MySqlParameter("flags", (byte)flags), new MySqlParameter("flags", (byte)flags),
new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)), new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)),
new MySqlParameter("sender", user?.UserId < 1 ? null : (long?)user.UserId), new MySqlParameter("sender", senderId < 1 ? null : senderId),
new MySqlParameter("sender_name", user?.UserName), new MySqlParameter("sender_name", string.IsNullOrWhiteSpace(senderName) ? null : senderName),
new MySqlParameter("sender_colour", user?.Colour.ToMisuzu()), new MySqlParameter("sender_colour", senderColour.ToMisuzu()),
new MySqlParameter("sender_rank", user?.Rank), new MySqlParameter("sender_rank", senderRank),
new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(user?.NickName) ? null : user.NickName), new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
new MySqlParameter("sender_perms", user?.Permissions) new MySqlParameter("sender_perms", senderPerms)
);
}
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null)
throw new ArgumentNullException(nameof(type));
long id = SharpId.Next();
AddEvent(
id, type,
channel?.Name,
user?.UserId ?? 0,
user?.UserName,
user?.Colour ?? ChatColour.None,
user?.Rank ?? 0,
user?.NickName,
user?.Permissions ?? 0,
data,
flags
); );
return id; return id;
@ -95,7 +146,7 @@ namespace SharpChat.EventStorage
+ ", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
+ ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`" + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
+ " FROM `sqc_events`" + " FROM `sqc_events`"
+ " WHERE `event_deleted` IS NULL AND `event_target` = @target" + " WHERE `event_deleted` IS NULL AND (`event_target` = @target OR `event_target` IS NULL)"
+ " AND `event_id` > @offset" + " AND `event_id` > @offset"
+ " ORDER BY `event_id` DESC" + " ORDER BY `event_id` DESC"
+ " LIMIT @amount", + " LIMIT @amount",

View file

@ -29,6 +29,14 @@ namespace SharpChat.EventStorage {
); );
DoMigration("create_events_table", CreateEventsTable); DoMigration("create_events_table", CreateEventsTable);
DoMigration("allow_null_target", AllowNullTarget);
}
private void AllowNullTarget() {
RunCommand(
"ALTER TABLE `sqc_events`"
+ " CHANGE COLUMN `event_target` `event_target` VARBINARY(255) NULL AFTER `event_type`;"
);
} }
private void CreateEventsTable() { private void CreateEventsTable() {

View file

@ -3,20 +3,76 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
namespace SharpChat.EventStorage namespace SharpChat.EventStorage {
{
public class VirtualEventStorage : IEventStorage { public class VirtualEventStorage : IEventStorage {
private readonly Dictionary<long, StoredEventInfo> Events = new(); private readonly Dictionary<long, StoredEventInfo> Events = new();
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) { public void AddEvent(
long id, string type,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
string channelName,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
public void AddEvent(
long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags);
}
public void AddEvent(
long id, string type,
string channelName,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
if(type == null) if(type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
// VES is meant as an emergency fallback but this is something else // VES is meant as an emergency fallback but this is something else
JsonDocument hack = JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data)); JsonDocument hack = JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data));
Events.Add(id, new(id, type, senderId < 1 ? null : new ChatUser(
senderId,
senderName,
senderColour,
senderRank,
senderPerms,
senderNick
), DateTimeOffset.Now, null, channelName, hack, flags));
}
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null)
throw new ArgumentNullException(nameof(type));
long id = SharpId.Next(); long id = SharpId.Next();
Events.Add(id, new(id, type, user, DateTimeOffset.Now, null, channel?.Name ?? null, hack, flags));
AddEvent(
id, type,
channel?.Name,
user?.UserId ?? 0,
user?.UserName,
user?.Colour ?? ChatColour.None,
user?.Rank ?? 0,
user?.NickName,
user?.Permissions ?? 0,
data,
flags
);
return id; return id;
} }

View file

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpChat.Events {
public interface IChatEvent {
}
}

View file

@ -0,0 +1,95 @@
using System;
namespace SharpChat.Events {
public class MessageCreateEvent : IChatEvent {
public long MessageId { get; }
public string ChannelName { get; }
public long SenderId { get; }
public string SenderName { get; }
public ChatColour SenderColour { get; }
public int SenderRank { get; }
public string SenderNickName { get; }
public ChatUserPermissions SenderPerms { get; }
public DateTimeOffset MessageCreated { get; }
public string MessageChannel { get; }
public string MessageText { get; }
public bool IsPrivate { get; }
public bool IsAction { get; }
public bool IsBroadcast { get; }
public MessageCreateEvent(
long msgId,
string channelName,
long senderId,
string senderName,
ChatColour senderColour,
int senderRank,
string senderNickName,
ChatUserPermissions senderPerms,
DateTimeOffset msgCreated,
string msgText,
bool isPrivate,
bool isAction,
bool isBroadcast
) {
MessageId = msgId;
ChannelName = channelName;
SenderId = senderId;
SenderName = senderName;
SenderColour = senderColour;
SenderRank = senderRank;
SenderNickName = senderNickName;
SenderPerms = senderPerms;
MessageCreated = msgCreated;
MessageText = msgText;
IsPrivate = isPrivate;
IsAction = isAction;
IsBroadcast = isBroadcast;
}
public MessageCreateEvent(
long msgId,
string channelName,
ChatUser sender,
DateTimeOffset msgCreated,
string msgText,
bool isPrivate,
bool isAction,
bool isBroadcast
) : this(
msgId,
channelName,
sender?.UserId ?? -1,
sender?.UserName ?? null,
sender?.Colour ?? ChatColour.None,
sender?.Rank ?? 0,
sender?.NickName ?? null,
sender?.Permissions ?? 0,
msgCreated,
msgText,
isPrivate,
isAction,
isBroadcast
) { }
public MessageCreateEvent(
long msgId,
ChatChannel channel,
ChatUser sender,
DateTimeOffset msgCreated,
string msgText,
bool isPrivate,
bool isAction,
bool isBroadcast
) : this(
msgId,
channel?.Name ?? null,
sender,
msgCreated,
msgText,
isPrivate,
isAction,
isBroadcast
) { }
}
}

View file

@ -29,8 +29,14 @@ namespace SharpChat.Packet
switch(Event.Type) { switch(Event.Type) {
case "msg:add": case "msg:add":
case "SharpChat.Events.ChatMessage": case "SharpChat.Events.ChatMessage":
sb.Append(Event.Sender.Pack()); if((Event.Flags & StoredEventFlags.Broadcast) > 0)
sb.Append('\t'); sb.Append(V1_CHATBOT);
else {
sb.Append(Event.Sender.Pack());
sb.Append('\t');
}
if((Event.Flags & StoredEventFlags.Broadcast) > 0)
sb.Append("0\fsay\f");
sb.Append( sb.Append(
Event.Data.RootElement.GetProperty("text").GetString() Event.Data.RootElement.GetProperty("text").GetString()
.Replace("<", "&lt;") .Replace("<", "&lt;")

View file

@ -1,5 +1,6 @@
using SharpChat.Commands; using SharpChat.Commands;
using SharpChat.Config; using SharpChat.Config;
using SharpChat.Events;
using SharpChat.EventStorage; using SharpChat.EventStorage;
using SharpChat.Packet; using SharpChat.Packet;
using System; using System;
@ -64,8 +65,6 @@ namespace SharpChat.PacketHandlers
Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}"); Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
#endif #endif
(string text, bool isAction)? messageInfo = null;
if(messageText.StartsWith("/")) { if(messageText.StartsWith("/")) {
ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel); ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
@ -78,30 +77,18 @@ namespace SharpChat.PacketHandlers
} }
if(command != null) { if(command != null) {
if(command is ActionCommand actionCommand) command.Dispatch(context);
messageInfo = actionCommand.ActionDispatch(context); return;
else {
command.Dispatch(context);
return;
}
} }
} }
messageInfo ??= (messageText, false); ctx.Chat.DispatchEvent(new MessageCreateEvent(
SharpId.Next(),
long msgId = ctx.Chat.Events.AddEvent( channel,
"msg:add", user,
user, channel,
new { messageInfo.Value.text },
messageInfo.Value.isAction ? StoredEventFlags.Action : StoredEventFlags.None
);
ctx.Chat.SendTo(channel, new ChatMessageAddPacket(
msgId,
DateTimeOffset.Now, DateTimeOffset.Now,
user.UserId, messageText,
messageInfo.Value.text, false, false, false
messageInfo.Value.isAction,
false
)); ));
} finally { } finally {
ctx.Chat.ContextAccess.Release(); ctx.Chat.ContextAccess.Release();

0
start.sh Normal file → Executable file
View file