Compare commits

...

3 commits

Author SHA1 Message Date
flash 903e39ab76 Removed any remaining references to silencing. 2023-07-23 21:36:22 +00:00
flash 8c19c22736 Reworking event dispatching... I think?
I did this make in february but left it uncommitted. Hopefully it's stable!
2023-07-23 21:31:13 +00:00
flash 4e0def980f Revised event storage to use less magic classes. 2023-02-23 22:46:49 +01:00
29 changed files with 554 additions and 519 deletions

View file

@ -762,28 +762,6 @@ Just echo whatever is specified in the first argument.
- `string`: Message to be broadcast. - `string`: Message to be broadcast.
#### `silence`: Silence notice
Informs the client that they've been silenced.
#### `unsil`: Silence revocation notice
Informs the client that their silence has been revoked.
#### `silok`: Silence confirmation
Informs the client that they have successfully silenced another user.
##### Arguments
- `string`: Username of the user.
#### `usilok`: Silence revocation confirmation
Informs the client that they have successfully revoked another user's silence.
##### Arguments
- `string`: Username of the user.
#### `flwarn`: Flood protection warning #### `flwarn`: Flood protection warning
Informs the client that they are risking getting kicked for flood protection (spam) if they continue sending messages at the same rate. Informs the client that they are risking getting kicked for flood protection (spam) if they continue sending messages at the same rate.
@ -1027,26 +1005,6 @@ Informs the client that they are not allowed to edit a channel.
Informs the client that they are not allowed to delete a message. Informs the client that they are not allowed to delete a message.
#### `silerr`: Already silenced
Informs the client that the user they attempted to silence has already been silenced.
#### `usilerr`: Not silenced
Informs the client that the user whose silence they attempted to revoke has not been silenced.
#### `silperr`: Silence permission error
Informs the client that they are not allowed to silence the other user.
#### `usilperr`: Silence revocation permission error
Informs the client that they are not allowed to revoke the silence on the other user.
#### `silself`: Self silence
Informs the client that they are not allowed to silence themselves.
## Commands ## Commands
Actions sent through messages prefixed with `/`. Arguments are described as `[name]`, optional arguments as `[name?]`. The `.` character is ignored in command names (replaced by nothing). Actions sent through messages prefixed with `/`. Arguments are described as `[name]`, optional arguments as `[name?]`. The `.` character is ignored in command names (replaced by nothing).
@ -1330,42 +1288,6 @@ Retrieves a list of banned users and IP addresses.
- `banlist`: The list of banned users and IP addresses. - `banlist`: The list of banned users and IP addresses.
### `/silence`: Silence a user
Silences a user. If the time argument is not specified, the silence is indefinite.
#### Format
```
/silence [username] [time?]
```
#### Responses
- `cmdna`: The client is not allowed to silence users.
- `usernf`: The target user could not be found on the server.
- `silself`: The client tried to silence themselves.
- `silperr`: The target user has a higher rank that the client.
- `silerr`: The target user is already silenced.
- `cmderr`: The time argument is formatted incorrectly.
- `silence`: Informs the target user that they have been silenced.
- `silok`: The target has been successfully silenced.
### `/unsilence`: Revokes a user silence
Revokes a user's silenced status.
#### Format
```
/unsilence [username]
```
#### Responses
- `cmdna`: The client is not allowed to revoke silences.
- `usernf`: The target user could not be found.
- `usilperr`: The target user has a higher rank than the client.
- `usilerr`: The target user isn't silenced.
- `unsil`: Informs the target user that their silenced status has been revoked.
- `usilok`: The silenced status placed on the target has been successfully revoked.
### `/ip`: Retrieve IP addresses ### `/ip`: Retrieve IP addresses
Retrieves a user's IP addresses. If the user has multiple connections, multiple `ipaddr` responses may be sent. Retrieves a user's IP addresses. If the user has multiple connections, multiple `ipaddr` responses may be sent.

View file

@ -3,11 +3,13 @@ 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);
@ -25,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) {
@ -148,13 +205,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 +233,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 +270,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

@ -5,7 +5,7 @@ namespace SharpChat {
public enum ChatUserPermissions : int { public enum ChatUserPermissions : int {
KickUser = 0x00000001, KickUser = 0x00000001,
BanUser = 0x00000002, BanUser = 0x00000002,
SilenceUser = 0x00000004, //SilenceUser = 0x00000004,
Broadcast = 0x00000008, Broadcast = 0x00000008,
SetOwnNickname = 0x00000010, SetOwnNickname = 0x00000010,
SetOthersNickname = 0x00000020, SetOthersNickname = 0x00000020,

View file

@ -10,20 +10,21 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
throw new NotImplementedException();
}
public ChatMessage ActionDispatch(ChatCommandContext ctx) {
if(!ctx.Args.Any()) if(!ctx.Args.Any())
return null; return;
return new ChatMessage { string actionStr = string.Join(' ', ctx.Args);
ChannelName = ctx.Channel.Name, if(string.IsNullOrWhiteSpace(actionStr))
DateTime = DateTimeOffset.UtcNow, return;
Sender = ctx.User,
Text = string.Join(' ', ctx.Args), ctx.Chat.DispatchEvent(new MessageCreateEvent(
Flags = ChatMessageFlags.Action, 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,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,56 +0,0 @@
using SharpChat.Packet;
using System;
using System.Linq;
namespace SharpChat.Commands {
public class SilenceApplyCommand : IChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("silence");
}
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.SilenceUser)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string silUserStr = ctx.Args.FirstOrDefault();
ChatUser silUser;
if(string.IsNullOrWhiteSpace(silUserStr) || (silUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(silUserStr))) == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, silUserStr ?? "User"));
return;
}
if(silUser == ctx.User) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_SELF));
return;
}
if(silUser.Rank >= ctx.User.Rank) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_HIERARCHY));
return;
}
if(RNG.Next() > 1 /*silUser.IsSilenced*/) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.SILENCE_ALREADY));
return;
}
DateTimeOffset silenceUntil = DateTimeOffset.MaxValue;
if(ctx.Args.Length > 1) {
if(!double.TryParse(ctx.Args.ElementAt(1), out double silenceSeconds)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return;
}
silenceUntil = DateTimeOffset.UtcNow.AddSeconds(silenceSeconds);
}
//silUser.SilencedUntil = silenceUntil;
ctx.Chat.SendTo(silUser, new LegacyCommandResponse(LCR.SILENCED, false));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_SILENCED, false, silUser.LegacyName));
}
}
}

View file

@ -1,40 +0,0 @@
using SharpChat.Packet;
using System;
using System.Linq;
namespace SharpChat.Commands {
public class SilenceRevokeCommand : IChatCommand {
public bool IsMatch(ChatCommandContext ctx) {
return ctx.NameEquals("unsilence");
}
public void Dispatch(ChatCommandContext ctx) {
if(!ctx.User.Can(ChatUserPermissions.SilenceUser)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string unsilUserStr = ctx.Args.FirstOrDefault();
ChatUser unsilUser;
if(string.IsNullOrWhiteSpace(unsilUserStr) || (unsilUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unsilUserStr))) == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, unsilUserStr ?? "User"));
return;
}
if(unsilUser.Rank >= ctx.User.Rank) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.UNSILENCE_HIERARCHY));
return;
}
if(RNG.Next() > 1 /*!unsilUser.IsSilenced*/) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.NOT_SILENCED));
return;
}
//unsilUser.SilencedUntil = DateTimeOffset.MinValue;
ctx.Chat.SendTo(unsilUser, new LegacyCommandResponse(LCR.UNSILENCED, false));
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.TARGET_UNSILENCED, false, unsilUser.LegacyName));
}
}
}

View file

@ -27,24 +27,14 @@ 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); SharpId.Next(),
DateTimeOffset dateTime = DateTimeOffset.Now; ChatUser.GetDMChannelName(ctx.User, whisperUser),
ctx.User,
ctx.Chat.SendTo(whisperUser, new ChatMessageAddPacket(new ChatMessage { DateTimeOffset.Now,
DateTime = dateTime, string.Join(' ', ctx.Args.Skip(1)),
ChannelName = whisperChan, true, false, false
Sender = ctx.User, ));
Text = whisperStr,
Flags = ChatMessageFlags.Private,
}));
ctx.Chat.SendTo(ctx.User, new ChatMessageAddPacket(new ChatMessage {
DateTime = dateTime,
ChannelName = whisperChan,
Sender = ctx.User,
Text = $"{whisperUser.LegacyName} {whisperStr}",
Flags = ChatMessageFlags.Private,
}));
} }
} }
} }

View file

@ -1,12 +1,36 @@
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); void AddEvent(
void RemoveEvent(IChatEvent evt); long id, string type,
IChatEvent GetEvent(long seqId); object data = null,
IEnumerable<IChatEvent> GetChannelEventLog(string channelName, int amount = 20, int offset = 0); 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);
void RemoveEvent(StoredEventInfo evt);
StoredEventInfo GetEvent(long seqId);
IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0);
} }
} }

View file

@ -1,11 +1,13 @@
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;
using System.Threading.Channels;
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 +15,97 @@ namespace SharpChat.EventStorage {
ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString)); ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString));
} }
public void AddEvent(IChatEvent evt) { public void AddEvent(
if(evt == null) long id, string type,
throw new ArgumentNullException(nameof(evt)); object data = null,
StoredEventFlags flags = StoredEventFlags.None
) {
AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags);
}
if(evt.SequenceId < 1) public void AddEvent(
evt.SequenceId = SharpId.Next(); 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)
throw new ArgumentNullException(nameof(type));
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", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
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", senderId < 1 ? null : senderId),
new MySqlParameter("sender", evt.Sender?.UserId < 1 ? null : (long?)evt.Sender.UserId), new MySqlParameter("sender_name", string.IsNullOrWhiteSpace(senderName) ? null : senderName),
new MySqlParameter("sender_name", evt.Sender?.UserName), new MySqlParameter("sender_colour", senderColour.ToMisuzu()),
new MySqlParameter("sender_colour", evt.Sender?.Colour.ToMisuzu()), new MySqlParameter("sender_rank", senderRank),
new MySqlParameter("sender_rank", evt.Sender?.Rank), new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
new MySqlParameter("sender_nick", evt.Sender?.NickName), new MySqlParameter("sender_perms", senderPerms)
new MySqlParameter("sender_perms", evt.Sender?.Permissions)
); );
} }
public IChatEvent GetEvent(long seqId) { 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;
}
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,38 +116,37 @@ 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 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",
@ -104,7 +156,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 +169,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

@ -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

@ -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,94 @@
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 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)
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));
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();
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;
}
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 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpChat.Events { namespace SharpChat.Events {
[Flags]
public enum ChatMessageFlags {
None = 0,
Action = 1,
Broadcast = 1 << 1,
Log = 1 << 2,
Private = 1 << 3,
}
public interface IChatEvent { 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

@ -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

@ -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

@ -23,9 +23,6 @@ namespace SharpChat.Misuzu {
[JsonPropertyName("hierarchy")] [JsonPropertyName("hierarchy")]
public int Rank { get; set; } public int Rank { get; set; }
[JsonPropertyName("is_silenced")]
public DateTimeOffset SilencedUntil { get; set; }
[JsonPropertyName("perms")] [JsonPropertyName("perms")]
public ChatUserPermissions Permissions { get; set; } public ChatUserPermissions Permissions { get; set; }
} }

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,22 @@ 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":
sb.Append(Event.Sender.Pack()); case "SharpChat.Events.ChatMessage":
sb.Append('\t'); if((Event.Flags & StoredEventFlags.Broadcast) > 0)
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(
msg.Text Event.Data.RootElement.GetProperty("text").GetString()
.Replace("<", "&lt;") .Replace("<", "&lt;")
.Replace(">", "&gt;") .Replace(">", "&gt;")
.Replace("\n", " <br/> ") .Replace("\n", " <br/> ")
@ -38,29 +46,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 +93,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

@ -76,15 +76,6 @@ namespace SharpChat.Packet {
public const string BROADCAST = "say"; public const string BROADCAST = "say";
public const string IP_ADDRESS = "ipaddr"; public const string IP_ADDRESS = "ipaddr";
public const string USER_NOT_FOUND = "usernf"; public const string USER_NOT_FOUND = "usernf";
public const string SILENCE_SELF = "silself";
public const string SILENCE_HIERARCHY = "silperr";
public const string SILENCE_ALREADY = "silerr";
public const string TARGET_SILENCED = "silok";
public const string SILENCED = "silence";
public const string UNSILENCED = "unsil";
public const string TARGET_UNSILENCED = "usilok";
public const string NOT_SILENCED = "usilerr";
public const string UNSILENCE_HIERARCHY = "usilperr";
public const string NAME_IN_USE = "nameinuse"; public const string NAME_IN_USE = "nameinuse";
public const string CHANNEL_INSUFFICIENT_HIERARCHY = "ipchan"; public const string CHANNEL_INSUFFICIENT_HIERARCHY = "ipchan";
public const string CHANNEL_INVALID_PASSWORD = "ipwchan"; public const string CHANNEL_INVALID_PASSWORD = "ipwchan";

View file

@ -1,12 +1,14 @@
using SharpChat.Commands; using SharpChat.Commands;
using SharpChat.Config; using SharpChat.Config;
using SharpChat.Events; 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;
@ -46,8 +48,7 @@ namespace SharpChat.PacketHandlers {
ctx.Chat.ContextAccess.Wait(); ctx.Chat.ContextAccess.Wait();
try { try {
if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel channel) if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel channel)
&& !ctx.Chat.IsInChannel(user, channel) && !ctx.Chat.IsInChannel(user, channel))
/*|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser))*/)
return; return;
if(user.Status != ChatUserStatus.Online) if(user.Status != ChatUserStatus.Online)
@ -63,8 +64,6 @@ namespace SharpChat.PacketHandlers {
Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}"); Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
#endif #endif
IChatMessage message = 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);
@ -77,24 +76,19 @@ namespace SharpChat.PacketHandlers {
} }
if(command != null) { if(command != null) {
if(command is ActionCommand actionCommand) command.Dispatch(context);
message = actionCommand.ActionDispatch(context); return;
else {
command.Dispatch(context);
return;
}
} }
} }
message ??= new ChatMessage { ctx.Chat.DispatchEvent(new MessageCreateEvent(
ChannelName = channel.Name, SharpId.Next(),
DateTime = DateTimeOffset.UtcNow, channel,
Sender = user, user,
Text = messageText, DateTimeOffset.Now,
}; messageText,
false, false, false
ctx.Chat.Events.AddEvent(message); ));
ctx.Chat.SendTo(channel, new ChatMessageAddPacket(message));
} finally { } finally {
ctx.Chat.ContextAccess.Release(); ctx.Chat.ContextAccess.Release();
} }

View file

@ -94,8 +94,6 @@ namespace SharpChat {
new PardonUserCommand(msz), new PardonUserCommand(msz),
new PardonAddressCommand(msz), new PardonAddressCommand(msz),
new BanListCommand(msz), new BanListCommand(msz),
//new SilenceApplyCommand(),
//new SilenceRevokeCommand(),
new RemoteAddressCommand(), new RemoteAddressCommand(),
}); });

0
start.sh Normal file → Executable file
View file