Revised event storage to use less magic classes.

This commit is contained in:
flash 2023-02-23 22:46:49 +01:00
parent 82973f7a33
commit 4e0def980f
18 changed files with 250 additions and 325 deletions

View file

@ -1,13 +1,14 @@
using SharpChat.Events; using SharpChat.EventStorage;
using SharpChat.EventStorage;
using SharpChat.Packet; using SharpChat.Packet;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
namespace SharpChat { namespace SharpChat
{
public class ChatContext { public class ChatContext {
public record ChannelUserAssoc(long UserId, string ChannelName); public record ChannelUserAssoc(long UserId, string ChannelName);
@ -148,13 +149,13 @@ namespace SharpChat {
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) { public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
if(!IsInChannel(user, chan)) { if(!IsInChannel(user, chan)) {
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user)); SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user));
Events.AddEvent(new UserConnectEvent(DateTimeOffset.Now, user, chan)); Events.AddEvent("user:connect", user, chan, flags: StoredEventFlags.Log);
} }
conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength)); conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength));
conn.Send(new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank))); conn.Send(new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name)) foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
conn.Send(new ContextMessagePacket(msg)); conn.Send(new ContextMessagePacket(msg));
conn.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank))); conn.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank)));
@ -176,7 +177,7 @@ namespace SharpChat {
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name)); ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason)); SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason)); Events.AddEvent("user:disconnect", user, chan, new { reason = (int)reason }, StoredEventFlags.Log);
if(chan.IsTemporary && chan.IsOwner(user)) if(chan.IsTemporary && chan.IsOwner(user))
RemoveChannel(chan); RemoveChannel(chan);
@ -213,14 +214,14 @@ namespace SharpChat {
ChatChannel oldChan = UserLastChannel[user.UserId]; ChatChannel oldChan = UserLastChannel[user.UserId];
SendTo(oldChan, new UserChannelLeavePacket(user)); SendTo(oldChan, new UserChannelLeavePacket(user));
Events.AddEvent(new UserChannelLeaveEvent(DateTimeOffset.Now, user, oldChan)); Events.AddEvent("chan:leave", user, oldChan, flags: StoredEventFlags.Log);
SendTo(chan, new UserChannelJoinPacket(user)); SendTo(chan, new UserChannelJoinPacket(user));
Events.AddEvent(new UserChannelJoinEvent(DateTimeOffset.Now, user, chan)); Events.AddEvent("chan:join", user, oldChan, flags: StoredEventFlags.Log);
SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers)); SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers));
SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank))); SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name)) foreach(StoredEventInfo msg in Events.GetChannelEventLog(chan.Name))
SendTo(user, new ContextMessagePacket(msg)); SendTo(user, new ContextMessagePacket(msg));
ForceChannel(user, chan); ForceChannel(user, chan);

View file

@ -1,8 +1,9 @@
using SharpChat.Events; using SharpChat.EventStorage;
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")
@ -13,17 +14,8 @@ namespace SharpChat.Commands {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public ChatMessage ActionDispatch(ChatCommandContext ctx) { public (string, bool)? ActionDispatch(ChatCommandContext ctx) {
if(!ctx.Args.Any()) return ctx.Args.Any() ? (string.Join(' ', ctx.Args), true) : null;
return null;
return new ChatMessage {
ChannelName = ctx.Channel.Name,
DateTime = DateTimeOffset.UtcNow,
Sender = ctx.User,
Text = string.Join(' ', ctx.Args),
Flags = ChatMessageFlags.Action,
};
} }
} }
} }

View file

@ -1,8 +1,9 @@
using SharpChat.Events; using SharpChat.EventStorage;
using SharpChat.Packet; using SharpChat.Packet;
using System.Linq; using System.Linq;
namespace SharpChat.Commands { namespace SharpChat.Commands
{
public class DeleteMessageCommand : IChatCommand { public class DeleteMessageCommand : IChatCommand {
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("delmsg") || ( return ctx.NameEquals("delmsg") || (
@ -26,7 +27,7 @@ namespace SharpChat.Commands {
return; return;
} }
IChatEvent delMsg = ctx.Chat.Events.GetEvent(delSeqId); StoredEventInfo delMsg = ctx.Chat.Events.GetEvent(delSeqId);
if(delMsg == null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) { if(delMsg == null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR));
@ -34,7 +35,7 @@ namespace SharpChat.Commands {
} }
ctx.Chat.Events.RemoveEvent(delMsg); ctx.Chat.Events.RemoveEvent(delMsg);
ctx.Chat.Send(new ChatMessageDeletePacket(delMsg.SequenceId)); ctx.Chat.Send(new ChatMessageDeletePacket(delMsg.Id));
} }
} }
} }

View file

@ -1,9 +1,10 @@
using SharpChat.Events; using SharpChat.EventStorage;
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")
@ -31,20 +32,22 @@ namespace SharpChat.Commands {
string whisperChan = ChatUser.GetDMChannelName(ctx.User, whisperUser); string whisperChan = ChatUser.GetDMChannelName(ctx.User, whisperUser);
DateTimeOffset dateTime = DateTimeOffset.Now; DateTimeOffset dateTime = DateTimeOffset.Now;
ctx.Chat.SendTo(whisperUser, new ChatMessageAddPacket(new ChatMessage { ctx.Chat.SendTo(whisperUser, new ChatMessageAddPacket(
DateTime = dateTime, SharpId.Next(),
ChannelName = whisperChan, dateTime,
Sender = ctx.User, ctx.User.UserId,
Text = whisperStr, whisperStr,
Flags = ChatMessageFlags.Private, false,
})); true
ctx.Chat.SendTo(ctx.User, new ChatMessageAddPacket(new ChatMessage { ));
DateTime = dateTime, ctx.Chat.SendTo(ctx.User, new ChatMessageAddPacket(
ChannelName = whisperChan, SharpId.Next(),
Sender = ctx.User, dateTime,
Text = $"{whisperUser.LegacyName} {whisperStr}", ctx.User.UserId,
Flags = ChatMessageFlags.Private, $"{whisperUser.LegacyName} {whisperStr}",
})); false,
true
));
} }
} }
} }

View file

@ -1,12 +1,11 @@
using SharpChat.Events; using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage
{
public interface IEventStorage { public interface IEventStorage {
void AddEvent(IChatEvent evt); long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None);
void RemoveEvent(IChatEvent evt); void RemoveEvent(StoredEventInfo evt);
IChatEvent GetEvent(long seqId); StoredEventInfo GetEvent(long seqId);
IEnumerable<IChatEvent> GetChannelEventLog(string channelName, int amount = 20, int offset = 0); IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0);
} }
} }

View file

@ -1,11 +1,12 @@
using MySqlConnector; using MySqlConnector;
using SharpChat.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage
{
public partial class MariaDBEventStorage : IEventStorage { public partial class MariaDBEventStorage : IEventStorage {
private string ConnectionString { get; } private string ConnectionString { get; }
@ -13,46 +14,47 @@ namespace SharpChat.EventStorage {
ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString)); ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString));
} }
public void AddEvent(IChatEvent evt) { public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(evt == null) if(type == null)
throw new ArgumentNullException(nameof(evt)); throw new ArgumentNullException(nameof(type));
if(evt.SequenceId < 1) long id = SharpId.Next();
evt.SequenceId = 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`)"
+ " VALUES (@id, FROM_UNIXTIME(@created), @type, @target, @flags, @data" + " VALUES (@id, NOW(), @type, @target, @flags, @data"
+ ", @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", evt.SequenceId), new MySqlParameter("id", id),
new MySqlParameter("created", evt.DateTime.ToUnixTimeSeconds()), new MySqlParameter("type", type),
new MySqlParameter("type", evt.GetType().FullName), new MySqlParameter("target", channel?.Name ?? null),
new MySqlParameter("target", evt.ChannelName), new MySqlParameter("flags", (byte)flags),
new MySqlParameter("flags", (byte)evt.Flags), new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)),
new MySqlParameter("data", JsonSerializer.SerializeToUtf8Bytes(evt, evt.GetType())), new MySqlParameter("sender", user?.UserId < 1 ? null : (long?)user.UserId),
new MySqlParameter("sender", evt.Sender?.UserId < 1 ? null : (long?)evt.Sender.UserId), new MySqlParameter("sender_name", user?.UserName),
new MySqlParameter("sender_name", evt.Sender?.UserName), new MySqlParameter("sender_colour", user?.Colour.ToMisuzu()),
new MySqlParameter("sender_colour", evt.Sender?.Colour.ToMisuzu()), new MySqlParameter("sender_rank", user?.Rank),
new MySqlParameter("sender_rank", evt.Sender?.Rank), new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(user?.NickName) ? null : user.NickName),
new MySqlParameter("sender_nick", evt.Sender?.NickName), new MySqlParameter("sender_perms", user?.Permissions)
new MySqlParameter("sender_perms", evt.Sender?.Permissions)
); );
return id;
} }
public IChatEvent GetEvent(long seqId) { public StoredEventInfo GetEvent(long seqId) {
try { try {
using MySqlDataReader reader = RunQuery( using MySqlDataReader reader = RunQuery(
"SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`" "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
+ ", `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`"
+ ", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
+ ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
+ " FROM `sqc_events`" + " FROM `sqc_events`"
+ " WHERE `event_id` = @id", + " WHERE `event_id` = @id",
new MySqlParameter("id", seqId) new MySqlParameter("id", seqId)
); );
while(reader.Read()) { while(reader.Read()) {
IChatEvent evt = ReadEvent(reader); StoredEventInfo evt = ReadEvent(reader);
if(evt != null) if(evt != null)
return evt; return evt;
} }
@ -63,36 +65,35 @@ namespace SharpChat.EventStorage {
return null; return null;
} }
private static IChatEvent ReadEvent(MySqlDataReader reader) { private static StoredEventInfo ReadEvent(MySqlDataReader reader) {
Type evtType = Type.GetType(Encoding.ASCII.GetString((byte[])reader["event_type"])); return new StoredEventInfo(
IChatEvent evt = JsonSerializer.Deserialize(Encoding.ASCII.GetString((byte[])reader["event_data"]), evtType) as IChatEvent; reader.GetInt64("event_id"),
evt.SequenceId = reader.GetInt64("event_id"); Encoding.ASCII.GetString((byte[])reader["event_type"]),
evt.ChannelName = Encoding.ASCII.GetString((byte[])reader["event_target"]); reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : new ChatUser(
evt.Flags = (ChatMessageFlags)reader.GetByte("event_flags");
evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created"));
if(!reader.IsDBNull(reader.GetOrdinal("event_sender"))) {
evt.Sender = new ChatUser(
reader.GetInt64("event_sender"), reader.GetInt64("event_sender"),
reader.GetString("event_sender_name"), reader.GetString("event_sender_name"),
ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")),
reader.GetInt32("event_sender_rank"), reader.GetInt32("event_sender_rank"),
(ChatUserPermissions)reader.GetInt32("event_sender_perms"), (ChatUserPermissions)reader.GetInt32("event_sender_perms"),
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick") reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick")
); ),
} DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
return evt; reader.IsDBNull(reader.GetOrdinal("event_target")) ? null : Encoding.ASCII.GetString((byte[])reader["event_target"]),
JsonDocument.Parse(Encoding.ASCII.GetString((byte[])reader["event_data"])),
(StoredEventFlags)reader.GetByte("event_flags")
);
} }
public IEnumerable<IChatEvent> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) { public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
List<IChatEvent> events = new(); List<StoredEventInfo> events = new();
try { try {
using MySqlDataReader reader = RunQuery( using MySqlDataReader reader = RunQuery(
"SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`" "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
+ ", `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`"
+ ", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
+ ", 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"
+ " AND `event_id` > @offset" + " AND `event_id` > @offset"
@ -104,7 +105,7 @@ namespace SharpChat.EventStorage {
); );
while(reader.Read()) { while(reader.Read()) {
IChatEvent evt = ReadEvent(reader); StoredEventInfo evt = ReadEvent(reader);
if(evt != null) if(evt != null)
events.Add(evt); events.Add(evt);
} }
@ -117,12 +118,12 @@ namespace SharpChat.EventStorage {
return events; return events;
} }
public void RemoveEvent(IChatEvent evt) { public void RemoveEvent(StoredEventInfo evt) {
if(evt == null) if(evt == null)
throw new ArgumentNullException(nameof(evt)); throw new ArgumentNullException(nameof(evt));
RunCommand( RunCommand(
"UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL", "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL",
new MySqlParameter("id", evt.SequenceId) new MySqlParameter("id", evt.Id)
); );
} }
} }

View file

@ -0,0 +1,14 @@
using System;
namespace SharpChat.EventStorage
{
[Flags]
public enum StoredEventFlags
{
None = 0,
Action = 1,
Broadcast = 1 << 1,
Log = 1 << 2,
Private = 1 << 3,
}
}

View file

@ -0,0 +1,35 @@
using System;
using System.Text.Json;
namespace SharpChat.EventStorage {
public class StoredEventInfo {
public long Id { get; set; }
public string Type { get; set; }
public ChatUser Sender { get; set; }
public DateTimeOffset Created { get; set; }
public DateTimeOffset? Deleted { get; set; }
public string ChannelName { get; set; }
public StoredEventFlags Flags { get; set; }
public JsonDocument Data { get; set; }
public StoredEventInfo(
long id,
string type,
ChatUser sender,
DateTimeOffset created,
DateTimeOffset? deleted,
string channelName,
JsonDocument data,
StoredEventFlags flags
) {
Id = id;
Type = type;
Sender = sender;
Created = created;
Deleted = deleted;
ChannelName = channelName;
Data = data;
Flags = flags;
}
}
}

View file

@ -1,30 +1,38 @@
using SharpChat.Events; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage
{
public class VirtualEventStorage : IEventStorage { public class VirtualEventStorage : IEventStorage {
private readonly Dictionary<long, IChatEvent> Events = new(); private readonly Dictionary<long, StoredEventInfo> Events = new();
public void AddEvent(IChatEvent evt) { public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null)
throw new ArgumentNullException(nameof(type));
// VES is meant as an emergency fallback but this is something else
JsonDocument hack = JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data));
long id = SharpId.Next();
Events.Add(id, new(id, type, user, DateTimeOffset.Now, null, channel?.Name ?? null, hack, flags));
return id;
}
public StoredEventInfo GetEvent(long seqId) {
return Events.TryGetValue(seqId, out StoredEventInfo evt) ? evt : null;
}
public void RemoveEvent(StoredEventInfo evt) {
if(evt == null) if(evt == null)
throw new ArgumentNullException(nameof(evt)); throw new ArgumentNullException(nameof(evt));
Events.Add(evt.SequenceId, evt); Events.Remove(evt.Id);
} }
public IChatEvent GetEvent(long seqId) { public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
return Events.TryGetValue(seqId, out IChatEvent evt) ? evt : null; IEnumerable<StoredEventInfo> subset = Events.Values.Where(ev => ev.ChannelName == channelName);
}
public void RemoveEvent(IChatEvent evt) {
if(evt == null)
throw new ArgumentNullException(nameof(evt));
Events.Remove(evt.SequenceId);
}
public IEnumerable<IChatEvent> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
IEnumerable<IChatEvent> subset = Events.Values.Where(ev => ev.ChannelName == channelName);
int start = subset.Count() - offset - amount; int start = subset.Count() - offset - amount;
if(start < 0) { if(start < 0) {

View file

@ -1,28 +0,0 @@
using System;
using System.Text.Json.Serialization;
namespace SharpChat.Events {
public class ChatMessage : IChatMessage {
[JsonIgnore]
public ChatUser Sender { get; set; }
[JsonIgnore]
public string ChannelName { get; set; }
[JsonIgnore]
public DateTimeOffset DateTime { get; set; }
[JsonIgnore]
public ChatMessageFlags Flags { get; set; } = ChatMessageFlags.None;
[JsonIgnore]
public long SequenceId { get; set; }
[JsonPropertyName("text")]
public string Text { get; set; }
public static string PackBotMessage(int type, string id, params string[] parts) {
return type.ToString() + '\f' + id + '\f' + string.Join('\f', parts);
}
}
}

View file

@ -1,24 +0,0 @@
using System;
namespace SharpChat.Events {
[Flags]
public enum ChatMessageFlags {
None = 0,
Action = 1,
Broadcast = 1 << 1,
Log = 1 << 2,
Private = 1 << 3,
}
public interface IChatEvent {
DateTimeOffset DateTime { get; set; }
ChatUser Sender { get; set; }
string ChannelName { get; set; }
ChatMessageFlags Flags { get; set; }
long SequenceId { get; set; }
}
public interface IChatMessage : IChatEvent {
string Text { get; }
}
}

View file

@ -1,28 +0,0 @@
using System;
using System.Text.Json.Serialization;
namespace SharpChat.Events {
public class UserChannelJoinEvent : IChatEvent {
[JsonIgnore]
public DateTimeOffset DateTime { get; set; }
[JsonIgnore]
public ChatUser Sender { get; set; }
[JsonIgnore]
public string ChannelName { get; set; }
[JsonIgnore]
public ChatMessageFlags Flags { get; set; } = ChatMessageFlags.Log;
[JsonIgnore]
public long SequenceId { get; set; }
public UserChannelJoinEvent() { }
public UserChannelJoinEvent(DateTimeOffset joined, ChatUser user, ChatChannel channel) {
DateTime = joined;
Sender = user;
ChannelName = channel.Name;
}
}
}

View file

@ -1,28 +0,0 @@
using System;
using System.Text.Json.Serialization;
namespace SharpChat.Events {
public class UserChannelLeaveEvent : IChatEvent {
[JsonIgnore]
public DateTimeOffset DateTime { get; set; }
[JsonIgnore]
public ChatUser Sender { get; set; }
[JsonIgnore]
public string ChannelName { get; set; }
[JsonIgnore]
public ChatMessageFlags Flags { get; set; } = ChatMessageFlags.Log;
[JsonIgnore]
public long SequenceId { get; set; }
public UserChannelLeaveEvent() { }
public UserChannelLeaveEvent(DateTimeOffset parted, ChatUser user, ChatChannel channel) {
DateTime = parted;
Sender = user;
ChannelName = channel.Name;
}
}
}

View file

@ -1,28 +0,0 @@
using System;
using System.Text.Json.Serialization;
namespace SharpChat.Events {
public class UserConnectEvent : IChatEvent {
[JsonIgnore]
public DateTimeOffset DateTime { get; set; }
[JsonIgnore]
public ChatUser Sender { get; set; }
[JsonIgnore]
public string ChannelName { get; set; }
[JsonIgnore]
public ChatMessageFlags Flags { get; set; } = ChatMessageFlags.Log;
[JsonIgnore]
public long SequenceId { get; set; }
public UserConnectEvent() { }
public UserConnectEvent(DateTimeOffset joined, ChatUser user, ChatChannel channel) {
DateTime = joined;
Sender = user;
ChannelName = channel.Name;
}
}
}

View file

@ -1,34 +0,0 @@
using SharpChat.Packet;
using System;
using System.Text.Json.Serialization;
namespace SharpChat.Events {
public class UserDisconnectEvent : IChatEvent {
[JsonIgnore]
public DateTimeOffset DateTime { get; set; }
[JsonIgnore]
public ChatUser Sender { get; set; }
[JsonIgnore]
public string ChannelName { get; set; }
[JsonIgnore]
public ChatMessageFlags Flags { get; set; } = ChatMessageFlags.Log;
[JsonIgnore]
public long SequenceId { get; set; }
[JsonPropertyName("reason")]
public UserDisconnectReason Reason { get; set; }
public UserDisconnectEvent() { }
public UserDisconnectEvent(DateTimeOffset parted, ChatUser user, ChatChannel channel, UserDisconnectReason reason) {
DateTime = parted;
Sender = user;
ChannelName = channel.Name;
Reason = reason;
}
}
}

View file

@ -1,17 +1,46 @@
using SharpChat.Events; using SharpChat.EventStorage;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet
{
public class ChatMessageAddPacket : ServerPacket { public class ChatMessageAddPacket : ServerPacket {
public IChatMessage Message { get; private set; } public DateTimeOffset Created { get; }
public long UserId { get; }
public string Text { get; }
public bool IsAction { get; }
public bool IsPrivate { get; }
public ChatMessageAddPacket(IChatMessage message) : base(message?.SequenceId ?? 0) { public ChatMessageAddPacket(
Message = message ?? throw new ArgumentNullException(nameof(message)); long msgId,
DateTimeOffset created,
long userId,
string text,
bool isAction,
bool isPrivate
) : base(msgId) {
Created = created;
UserId = userId < 0 ? -1 : userId;
Text = text;
IsAction = isAction;
IsPrivate = isPrivate;
}
if(Message.SequenceId < 1) public static ChatMessageAddPacket FromStoredEvent(StoredEventInfo sei) {
Message.SequenceId = SequenceId; if(sei == null)
throw new ArgumentNullException(nameof(sei));
if(sei.Type is not "msg:add" and not "SharpChat.Events.ChatMessage")
throw new ArgumentException("Wrong event type.", nameof(sei));
return new ChatMessageAddPacket(
sei.Id,
sei.Created,
sei.Sender?.UserId ?? -1,
string.Empty, // todo: this
(sei.Flags & StoredEventFlags.Action) > 0,
(sei.Flags & StoredEventFlags.Private) > 0
);
} }
public override IEnumerable<string> Pack() { public override IEnumerable<string> Pack() {
@ -20,33 +49,32 @@ namespace SharpChat.Packet {
sb.Append('2'); sb.Append('2');
sb.Append('\t'); sb.Append('\t');
sb.Append(Message.DateTime.ToUnixTimeSeconds()); sb.Append(Created.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
sb.Append(Message.Sender?.UserId ?? -1); sb.Append(UserId);
sb.Append('\t'); sb.Append('\t');
if(Message.Flags.HasFlag(ChatMessageFlags.Action)) if(IsAction)
sb.Append("<i>"); sb.Append("<i>");
sb.Append( sb.Append(
Message.Text Text.Replace("<", "&lt;")
.Replace("<", "&lt;")
.Replace(">", "&gt;") .Replace(">", "&gt;")
.Replace("\n", " <br/> ") .Replace("\n", " <br/> ")
.Replace("\t", " ") .Replace("\t", " ")
); );
if(Message.Flags.HasFlag(ChatMessageFlags.Action)) if(IsAction)
sb.Append("</i>"); sb.Append("</i>");
sb.Append('\t'); sb.Append('\t');
sb.Append(SequenceId); sb.Append(SequenceId);
sb.AppendFormat( sb.AppendFormat(
"\t1{0}0{1}{2}", "\t1{0}0{1}{2}",
Message.Flags.HasFlag(ChatMessageFlags.Action) ? '1' : '0', IsAction ? '1' : '0',
Message.Flags.HasFlag(ChatMessageFlags.Action) ? '0' : '1', IsAction ? '0' : '1',
Message.Flags.HasFlag(ChatMessageFlags.Private) ? '1' : '0' IsPrivate ? '1' : '0'
); );
yield return sb.ToString(); yield return sb.ToString();

View file

@ -1,14 +1,15 @@
using SharpChat.Events; using SharpChat.EventStorage;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace SharpChat.Packet { namespace SharpChat.Packet
{
public class ContextMessagePacket : ServerPacket { public class ContextMessagePacket : ServerPacket {
public IChatEvent Event { get; private set; } public StoredEventInfo Event { get; private set; }
public bool Notify { get; private set; } public bool Notify { get; private set; }
public ContextMessagePacket(IChatEvent evt, bool notify = false) { public ContextMessagePacket(StoredEventInfo evt, bool notify = false) {
Event = evt ?? throw new ArgumentNullException(nameof(evt)); Event = evt ?? throw new ArgumentNullException(nameof(evt));
Notify = notify; Notify = notify;
} }
@ -22,15 +23,16 @@ namespace SharpChat.Packet {
sb.Append('\t'); sb.Append('\t');
sb.Append('1'); sb.Append('1');
sb.Append('\t'); sb.Append('\t');
sb.Append(Event.DateTime.ToUnixTimeSeconds()); sb.Append(Event.Created.ToUnixTimeSeconds());
sb.Append('\t'); sb.Append('\t');
switch(Event) { switch(Event.Type) {
case IChatMessage msg: case "msg:add":
case "SharpChat.Events.ChatMessage":
sb.Append(Event.Sender.Pack()); sb.Append(Event.Sender.Pack());
sb.Append('\t'); sb.Append('\t');
sb.Append( sb.Append(
msg.Text Event.Data.RootElement.GetProperty("text").GetString()
.Replace("<", "&lt;") .Replace("<", "&lt;")
.Replace(">", "&gt;") .Replace(">", "&gt;")
.Replace("\n", " <br/> ") .Replace("\n", " <br/> ")
@ -38,29 +40,33 @@ namespace SharpChat.Packet {
); );
break; break;
case UserConnectEvent _: case "user:connect":
case "SharpChat.Events.UserConnectEvent":
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\fjoin\f"); sb.Append("0\fjoin\f");
sb.Append(Event.Sender.UserName); sb.Append(Event.Sender.UserName);
break; break;
case UserChannelJoinEvent _: case "chan:join":
case "SharpChat.Events.UserChannelJoinEvent":
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\fjchan\f"); sb.Append("0\fjchan\f");
sb.Append(Event.Sender.UserName); sb.Append(Event.Sender.UserName);
break; break;
case UserChannelLeaveEvent _: case "chan:leave":
case "SharpChat.Events.UserChannelLeaveEvent":
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\flchan\f"); sb.Append("0\flchan\f");
sb.Append(Event.Sender.UserName); sb.Append(Event.Sender.UserName);
break; break;
case UserDisconnectEvent ude: case "user:disconnect":
case "SharpChat.Events.UserDisconnectEvent":
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\f"); sb.Append("0\f");
switch(ude.Reason) { switch((UserDisconnectReason)Event.Data.RootElement.GetProperty("reason").GetByte()) {
case UserDisconnectReason.Flood: case UserDisconnectReason.Flood:
sb.Append("flood"); sb.Append("flood");
break; break;
@ -81,16 +87,15 @@ namespace SharpChat.Packet {
break; break;
} }
sb.Append('\t'); sb.Append('\t');
sb.Append(Event.SequenceId < 1 ? SequenceId : Event.SequenceId); sb.Append(Event.Id < 1 ? SequenceId : Event.Id);
sb.Append('\t'); sb.Append('\t');
sb.Append(Notify ? '1' : '0'); sb.Append(Notify ? '1' : '0');
sb.AppendFormat( sb.AppendFormat(
"\t1{0}0{1}{2}", "\t1{0}0{1}{2}",
Event.Flags.HasFlag(ChatMessageFlags.Action) ? '1' : '0', Event.Flags.HasFlag(StoredEventFlags.Action) ? '1' : '0',
Event.Flags.HasFlag(ChatMessageFlags.Action) ? '0' : '1', Event.Flags.HasFlag(StoredEventFlags.Action) ? '0' : '1',
Event.Flags.HasFlag(ChatMessageFlags.Private) ? '1' : '0' Event.Flags.HasFlag(StoredEventFlags.Private) ? '1' : '0'
); );
yield return sb.ToString(); yield return sb.ToString();

View file

@ -1,12 +1,13 @@
using SharpChat.Commands; using SharpChat.Commands;
using SharpChat.Config; using SharpChat.Config;
using SharpChat.Events; using SharpChat.EventStorage;
using SharpChat.Packet; using SharpChat.Packet;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace SharpChat.PacketHandlers { namespace SharpChat.PacketHandlers
{
public class SendMessageHandler : IChatPacketHandler { public class SendMessageHandler : IChatPacketHandler {
private readonly CachedValue<int> MaxMessageLength; private readonly CachedValue<int> MaxMessageLength;
@ -63,7 +64,7 @@ namespace SharpChat.PacketHandlers {
Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}"); Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
#endif #endif
IChatMessage message = null; (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,7 +79,7 @@ namespace SharpChat.PacketHandlers {
if(command != null) { if(command != null) {
if(command is ActionCommand actionCommand) if(command is ActionCommand actionCommand)
message = actionCommand.ActionDispatch(context); messageInfo = actionCommand.ActionDispatch(context);
else { else {
command.Dispatch(context); command.Dispatch(context);
return; return;
@ -86,15 +87,22 @@ namespace SharpChat.PacketHandlers {
} }
} }
message ??= new ChatMessage { messageInfo ??= (messageText, false);
ChannelName = channel.Name,
DateTime = DateTimeOffset.UtcNow,
Sender = user,
Text = messageText,
};
ctx.Chat.Events.AddEvent(message); long msgId = ctx.Chat.Events.AddEvent(
ctx.Chat.SendTo(channel, new ChatMessageAddPacket(message)); "msg:add",
user, channel,
new { messageInfo.Value.text },
messageInfo.Value.isAction ? StoredEventFlags.Action : StoredEventFlags.None
);
ctx.Chat.SendTo(channel, new ChatMessageAddPacket(
msgId,
DateTimeOffset.Now,
user.UserId,
messageInfo.Value.text,
messageInfo.Value.isAction,
false
));
} finally { } finally {
ctx.Chat.ContextAccess.Release(); ctx.Chat.ContextAccess.Release();
} }