Issue user disconnected and kick/ban as events and restructure table.
This commit is contained in:
parent
09d5bfef82
commit
2eae48325a
11 changed files with 176 additions and 98 deletions
|
@ -1,4 +1,5 @@
|
|||
using SharpChat.Misuzu;
|
||||
using SharpChat.Events;
|
||||
using SharpChat.Misuzu;
|
||||
using SharpChat.SockChat.PacketsS2C;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
@ -53,7 +54,7 @@ namespace SharpChat.SockChat.Commands {
|
|||
}
|
||||
|
||||
if(duration <= TimeSpan.Zero) {
|
||||
ctx.Chat.BanUser(banUser, duration);
|
||||
ctx.Chat.Events.Dispatch("user:kickban", banUser, new UserKickBanEventData(UserDisconnectReason.Kicked, TimeSpan.Zero));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -78,7 +79,7 @@ namespace SharpChat.SockChat.Commands {
|
|||
duration, banReason
|
||||
);
|
||||
|
||||
ctx.Chat.BanUser(banUser, duration);
|
||||
ctx.Chat.Events.Dispatch("user:kickban", banUser, new UserKickBanEventData(UserDisconnectReason.Kicked, duration));
|
||||
}).Wait();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,9 @@ namespace SharpChat.SockChat.PacketsS2C {
|
|||
public class ForceDisconnectS2CPacket : ISockChatS2CPacket {
|
||||
private readonly long Expires;
|
||||
|
||||
public ForceDisconnectS2CPacket() {
|
||||
}
|
||||
|
||||
public ForceDisconnectS2CPacket(DateTimeOffset expires) {
|
||||
Expires = expires.Year >= 2100 ? -1 : expires.ToUnixTimeSeconds();
|
||||
Expires = expires <= DateTimeOffset.UtcNow
|
||||
? 0 : (expires.Year >= 2100 ? -1 : expires.ToUnixTimeSeconds());
|
||||
}
|
||||
|
||||
public string Pack() {
|
||||
|
|
|
@ -29,6 +29,11 @@ namespace SharpChat {
|
|||
// user status should be stored outside of the UserInfo class so we don't need to do this:
|
||||
UserInfo? userInfo = Users.Get(info.SenderId);
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(info.ChannelName))
|
||||
ChannelsUsers.SetUserLastChannel(info.SenderId, info.ChannelName);
|
||||
|
||||
// TODO: should user:connect and user:disconnect be channel agnostic?
|
||||
|
||||
switch(info.Type) {
|
||||
case "user:connect":
|
||||
SendTo(info.ChannelName, new UserConnectS2CPacket(
|
||||
|
@ -43,13 +48,61 @@ namespace SharpChat {
|
|||
break;
|
||||
|
||||
case "user:disconnect":
|
||||
SendTo(info.ChannelName, new UserDisconnectS2CPacket(
|
||||
info.Id,
|
||||
info.Created,
|
||||
info.SenderId,
|
||||
userInfo == null ? SockChatUtility.GetUserName(info) : SockChatUtility.GetUserNameWithStatus(userInfo),
|
||||
info.Data is UserDisconnectEventData userDisconnect ? userDisconnect.Reason : UserDisconnectReason.Leave
|
||||
));
|
||||
if(userInfo != null)
|
||||
UpdateUser(userInfo, status: UserStatus.Offline);
|
||||
Users.Remove(info.SenderId);
|
||||
|
||||
ChannelInfo[] channels = Channels.GetMany(ChannelsUsers.GetUserChannelNames(info.SenderId));
|
||||
ChannelsUsers.DeleteUser(info.SenderId);
|
||||
|
||||
if(channels.Length > 0) {
|
||||
UserDisconnectS2CPacket udPacket = new(
|
||||
info.Id,
|
||||
info.Created,
|
||||
info.SenderId,
|
||||
userInfo == null ? SockChatUtility.GetUserName(info) : SockChatUtility.GetUserNameWithStatus(userInfo),
|
||||
info.Data is UserDisconnectEventData userDisconnect ? userDisconnect.Reason : UserDisconnectReason.Leave
|
||||
);
|
||||
|
||||
foreach(ChannelInfo chan in channels) {
|
||||
if(chan.IsTemporary && chan.IsOwner(info.SenderId))
|
||||
RemoveChannel(chan);
|
||||
else
|
||||
SendTo(chan, udPacket);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "user:kickban":
|
||||
if(info.Data is not UserKickBanEventData userBaka)
|
||||
break;
|
||||
|
||||
SendTo(info.SenderId, new ForceDisconnectS2CPacket(userBaka.Expires));
|
||||
|
||||
ConnectionInfo[] conns = Connections.GetUser(info.SenderId);
|
||||
foreach(ConnectionInfo conn in conns) {
|
||||
Connections.Remove(conn);
|
||||
if(conn is SockChatConnectionInfo scConn)
|
||||
scConn.Close(1000);
|
||||
|
||||
Logger.Write($"<{conn.RemoteEndPoint}> Nuked connection from banned user #{conn.UserId}.");
|
||||
}
|
||||
|
||||
string bakaChannelName = ChannelsUsers.GetUserLastChannel(info.SenderId);
|
||||
if(!string.IsNullOrWhiteSpace(bakaChannelName))
|
||||
Events.Dispatch(new ChatEventInfo(
|
||||
SharpId.Next(),
|
||||
"user:disconnect",
|
||||
info.Created,
|
||||
bakaChannelName,
|
||||
info.SenderId,
|
||||
info.SenderName,
|
||||
info.SenderColour,
|
||||
info.SenderRank,
|
||||
info.SenderNickName,
|
||||
info.SenderPerms,
|
||||
new UserDisconnectEventData(userBaka.Reason)
|
||||
));
|
||||
break;
|
||||
|
||||
case "chan:join":
|
||||
|
@ -147,7 +200,12 @@ namespace SharpChat {
|
|||
|
||||
foreach(UserInfo user in Users.All)
|
||||
if(!Connections.HasUser(user)) {
|
||||
HandleDisconnect(user, UserDisconnectReason.TimeOut);
|
||||
Events.Dispatch(
|
||||
"user:disconnect",
|
||||
ChannelsUsers.GetUserLastChannel(user),
|
||||
user,
|
||||
new UserDisconnectEventData(UserDisconnectReason.TimeOut)
|
||||
);
|
||||
Logger.Write($"Timed out {user} (no more connections).");
|
||||
}
|
||||
}
|
||||
|
@ -241,25 +299,6 @@ namespace SharpChat {
|
|||
}
|
||||
}
|
||||
|
||||
public void BanUser(UserInfo user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
||||
if(duration > TimeSpan.Zero) {
|
||||
DateTimeOffset expires = duration >= TimeSpan.MaxValue ? DateTimeOffset.MaxValue : DateTimeOffset.Now + duration;
|
||||
SendTo(user, new ForceDisconnectS2CPacket(expires));
|
||||
} else
|
||||
SendTo(user, new ForceDisconnectS2CPacket());
|
||||
|
||||
ConnectionInfo[] conns = Connections.GetUser(user);
|
||||
foreach(ConnectionInfo conn in conns) {
|
||||
Connections.Remove(conn);
|
||||
if(conn is SockChatConnectionInfo scConn)
|
||||
scConn.Close(1000);
|
||||
|
||||
Logger.Write($"<{conn.RemoteEndPoint}> Nuked connection from banned user #{conn.UserId}.");
|
||||
}
|
||||
|
||||
HandleDisconnect(user, reason);
|
||||
}
|
||||
|
||||
public void HandleChannelEventLog(string channelName, Action<ISockChatS2CPacket> handler) {
|
||||
foreach(ChatEventInfo info in EventStorage.GetChannelEventLog(channelName)) {
|
||||
switch(info.Type) {
|
||||
|
@ -355,21 +394,6 @@ namespace SharpChat {
|
|||
ChannelsUsers.Join(chan.Name, user.UserId);
|
||||
}
|
||||
|
||||
public void HandleDisconnect(UserInfo user, UserDisconnectReason reason = UserDisconnectReason.Leave) {
|
||||
UpdateUser(user, status: UserStatus.Offline);
|
||||
Users.Remove(user.UserId);
|
||||
|
||||
ChannelInfo[] channels = GetUserChannels(user);
|
||||
ChannelsUsers.DeleteUser(user);
|
||||
|
||||
foreach(ChannelInfo chan in channels) {
|
||||
Events.Dispatch("user:disconnect", chan, user, new UserDisconnectEventData(reason));
|
||||
|
||||
if(chan.IsTemporary && chan.IsOwner(user))
|
||||
RemoveChannel(chan);
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchChannel(UserInfo user, ChannelInfo chan, string password) {
|
||||
if(ChannelsUsers.IsUserLastChannel(user, chan)) {
|
||||
ForceChannel(user);
|
||||
|
@ -434,15 +458,22 @@ namespace SharpChat {
|
|||
}
|
||||
|
||||
public void SendTo(UserInfo user, ISockChatS2CPacket packet) {
|
||||
string data = packet.Pack();
|
||||
Connections.WithUser(user, conn => {
|
||||
SendTo(user.UserId, packet.Pack());
|
||||
}
|
||||
|
||||
public void SendTo(long userId, ISockChatS2CPacket packet) {
|
||||
SendTo(userId, packet.Pack());
|
||||
}
|
||||
|
||||
public void SendTo(long userId, string packet) {
|
||||
Connections.WithUser(userId, conn => {
|
||||
if(conn is SockChatConnectionInfo scConn)
|
||||
scConn.Send(data);
|
||||
scConn.Send(packet);
|
||||
});
|
||||
}
|
||||
|
||||
public void SendTo(ChannelInfo channel, ISockChatS2CPacket packet) {
|
||||
SendTo(channel, packet.Pack());
|
||||
SendTo(channel.Name, packet.Pack());
|
||||
}
|
||||
|
||||
public void SendTo(ChannelInfo channel, string packet) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Fleck;
|
||||
using SharpChat.Config;
|
||||
using SharpChat.Events;
|
||||
using SharpChat.EventStorage;
|
||||
using SharpChat.Misuzu;
|
||||
using SharpChat.SockChat.Commands;
|
||||
|
@ -156,7 +157,12 @@ namespace SharpChat.SockChat {
|
|||
if(!Context.Connections.HasUser(conn.UserId)) {
|
||||
UserInfo? userInfo = Context.Users.Get(conn.UserId);
|
||||
if(userInfo != null)
|
||||
Context.HandleDisconnect(userInfo);
|
||||
Context.Events.Dispatch(
|
||||
"user:disconnect",
|
||||
Context.ChannelsUsers.GetUserLastChannel(userInfo),
|
||||
userInfo,
|
||||
new UserDisconnectEventData(UserDisconnectReason.Leave)
|
||||
);
|
||||
}
|
||||
|
||||
Context.Update();
|
||||
|
@ -199,7 +205,7 @@ namespace SharpChat.SockChat {
|
|||
if(banDuration == TimeSpan.MinValue) {
|
||||
Context.SendTo(userInfo, new FloodWarningS2CPacket());
|
||||
} else {
|
||||
Context.BanUser(userInfo, banDuration, UserDisconnectReason.Flood);
|
||||
Context.Events.Dispatch("user:kickban", userInfo, new UserKickBanEventData(UserDisconnectReason.Flood, banDuration));
|
||||
|
||||
if(banDuration > TimeSpan.Zero)
|
||||
Misuzu.CreateBanAsync(
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
OwnerId = ownerId;
|
||||
}
|
||||
|
||||
public bool IsOwner(long userId) {
|
||||
return OwnerId > 0
|
||||
&& OwnerId == userId;
|
||||
}
|
||||
public bool IsOwner(UserInfo user) {
|
||||
return OwnerId > 0
|
||||
&& user != null
|
||||
|
|
|
@ -4,6 +4,6 @@ using System.Collections.Generic;
|
|||
namespace SharpChat.EventStorage {
|
||||
public interface IEventStorage : IChatEventHandler {
|
||||
ChatEventInfo? GetEvent(long eventId);
|
||||
IEnumerable<ChatEventInfo> GetChannelEventLog(string channelName, int amount = 20, int startAt = 0);
|
||||
IEnumerable<ChatEventInfo> GetChannelEventLog(string channelName, int amount = 20, int after = 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,30 +33,27 @@ namespace SharpChat.EventStorage {
|
|||
}
|
||||
|
||||
public void HandleEvent(ChatEventInfo info) {
|
||||
MySqlParameter dataParam = new("data", MySqlDbType.Blob) {
|
||||
Value = JsonSerializer.SerializeToUtf8Bytes(info.Data, info.Data.GetType(), jsonOpts)
|
||||
};
|
||||
|
||||
RunCommand(
|
||||
"INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_data`"
|
||||
+ ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)"
|
||||
+ " VALUES (@id, NOW(), @type, @target, @data"
|
||||
+ ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)",
|
||||
"INSERT INTO sqc_events (event_id, event_type, event_created, event_channel, event_data"
|
||||
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms"
|
||||
+ ") VALUES (@id, @type, FROM_UNIXTIME(@created), @channel, @data"
|
||||
+ ", @senderId, @senderName, @senderColour, @senderRank, @senderNickName, @senderPerms)",
|
||||
new MySqlParameter("id", info.Id),
|
||||
new MySqlParameter("type", info.Type),
|
||||
new MySqlParameter("target", string.IsNullOrWhiteSpace(info.ChannelName) ? null : info.ChannelName),
|
||||
dataParam,
|
||||
new MySqlParameter("sender", info.SenderId < 1 ? null : info.SenderId),
|
||||
new MySqlParameter("sender_name", info.SenderName),
|
||||
new MySqlParameter("sender_colour", info.SenderColour.ToMisuzu()),
|
||||
new MySqlParameter("sender_rank", info.SenderRank),
|
||||
new MySqlParameter("sender_nick", info.SenderNickName),
|
||||
new MySqlParameter("sender_perms", info.SenderPerms)
|
||||
new MySqlParameter("created", info.Created.ToUnixTimeSeconds()),
|
||||
new MySqlParameter("channel", string.IsNullOrWhiteSpace(info.ChannelName) ? null : info.ChannelName),
|
||||
new MySqlParameter("data", JsonSerializer.Serialize(info.Data, info.Data.GetType(), jsonOpts)),
|
||||
new MySqlParameter("senderId", info.SenderId < 1 ? null : info.SenderId),
|
||||
new MySqlParameter("senderName", info.SenderName),
|
||||
new MySqlParameter("senderColour", info.SenderColour.ToMisuzu()),
|
||||
new MySqlParameter("senderRank", info.SenderRank),
|
||||
new MySqlParameter("senderNickName", string.IsNullOrWhiteSpace(info.SenderNickName) ? null : info.SenderNickName),
|
||||
new MySqlParameter("senderPerms", info.SenderPerms)
|
||||
);
|
||||
|
||||
if(info.Type.Equals("msg:delete") && info.Data is MessageDeleteEventData msgDelete)
|
||||
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", msgDelete.MessageId)
|
||||
);
|
||||
}
|
||||
|
@ -64,12 +61,10 @@ namespace SharpChat.EventStorage {
|
|||
public ChatEventInfo? GetEvent(long eventId) {
|
||||
try {
|
||||
using MySqlDataReader? reader = RunQuery(
|
||||
"SELECT `event_id`, `event_type`, `event_data`, `event_target`"
|
||||
+ ", `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_deleted`) AS `event_deleted`"
|
||||
+ " FROM `sqc_events`"
|
||||
+ " WHERE `event_id` = @id",
|
||||
"SELECT event_id, event_type, event_channel, event_data"
|
||||
+ ", 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_deleted) AS event_deleted"
|
||||
+ " FROM sqc_events WHERE event_id = @id",
|
||||
new MySqlParameter("id", eventId)
|
||||
);
|
||||
|
||||
|
@ -87,43 +82,42 @@ namespace SharpChat.EventStorage {
|
|||
}
|
||||
|
||||
private static ChatEventInfo ReadEvent(MySqlDataReader reader) {
|
||||
string eventType = Encoding.ASCII.GetString((byte[])reader["event_type"]);
|
||||
string eventType = reader.GetString("event_type");
|
||||
ChatEventData eventData = EventDataTypes.ContainsKey(eventType)
|
||||
? (ChatEventData)(JsonSerializer.Deserialize((byte[])reader["event_data"], EventDataTypes[eventType]) ?? ChatEventData.EmptyInstance)
|
||||
? (ChatEventData)(JsonSerializer.Deserialize(reader.GetString("event_data"), EventDataTypes[eventType]) ?? ChatEventData.EmptyInstance)
|
||||
: ChatEventData.EmptyInstance;
|
||||
|
||||
return new ChatEventInfo(
|
||||
reader.GetInt64("event_id"),
|
||||
eventType,
|
||||
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_target")) ? string.Empty : Encoding.ASCII.GetString((byte[])reader["event_target"]),
|
||||
reader.GetString("event_channel"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? -1 : reader.GetInt64("event_sender"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_colour")) ? Colour.None : Colour.FromMisuzu(reader.GetInt32("event_sender_colour")),
|
||||
reader.GetInt32("event_sender_rank"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_rank")) ? 0 : reader.GetInt32("event_sender_rank"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick"),
|
||||
(UserPermissions)reader.GetInt32("event_sender_perms"),
|
||||
(UserPermissions)(reader.IsDBNull(reader.GetOrdinal("event_sender_perms")) ? 0 : reader.GetInt32("event_sender_perms")),
|
||||
eventData
|
||||
);
|
||||
}
|
||||
|
||||
public IEnumerable<ChatEventInfo> GetChannelEventLog(string channelName, int amount = 20, int startAt = 0) {
|
||||
public IEnumerable<ChatEventInfo> GetChannelEventLog(string channelName, int amount = 20, int after = 0) {
|
||||
List<ChatEventInfo> events = new();
|
||||
|
||||
try {
|
||||
using MySqlDataReader? reader = RunQuery(
|
||||
"SELECT `event_id`, `event_type`, `event_data`, `event_target`"
|
||||
+ ", `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_deleted`) AS `event_deleted`"
|
||||
+ " FROM `sqc_events`"
|
||||
+ " WHERE `event_deleted` IS NULL AND (`event_target` = @target OR `event_target` IS NULL)"
|
||||
+ " AND `event_id` > @offset"
|
||||
+ " ORDER BY `event_id` DESC"
|
||||
"SELECT event_id, event_type, event_channel, event_data"
|
||||
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms"
|
||||
+ ", UNIX_TIMESTAMP(event_created) AS event_created"
|
||||
+ " FROM sqc_events WHERE event_deleted IS NULL"
|
||||
+ " AND (event_channel IS NULL OR event_channel = @channel)"
|
||||
+ " AND event_id > @after"
|
||||
+ " ORDER BY event_id DESC"
|
||||
+ " LIMIT @amount",
|
||||
new MySqlParameter("target", channelName),
|
||||
new MySqlParameter("channel", channelName),
|
||||
new MySqlParameter("amount", amount),
|
||||
new MySqlParameter("offset", startAt)
|
||||
new MySqlParameter("after", after)
|
||||
);
|
||||
|
||||
if(reader != null)
|
||||
|
|
|
@ -32,6 +32,23 @@ namespace SharpChat.EventStorage {
|
|||
DoMigration("allow_null_target", AllowNullTarget);
|
||||
DoMigration("update_event_type_names", UpdateEventTypeNames);
|
||||
DoMigration("deprecate_event_flags", DeprecateEventFlags);
|
||||
DoMigration("update_collations_and_use_json_type", UpdateCollationsAndUseJsonType);
|
||||
}
|
||||
|
||||
private void UpdateCollationsAndUseJsonType() {
|
||||
RunCommand("UPDATE sqc_events SET event_target = LOWER(event_target)");
|
||||
RunCommand("UPDATE sqc_events SET event_sender_nick = NULL WHERE event_sender_nick = ''");
|
||||
RunCommand(
|
||||
"ALTER TABLE `sqc_events` COLLATE='utf8mb4_unicode_520_ci',"
|
||||
+ " CHANGE COLUMN `event_type` `event_type` VARCHAR(255) NOT NULL COLLATE 'ascii_general_ci' AFTER `event_id`,"
|
||||
+ " CHANGE COLUMN `event_created` `event_created` TIMESTAMP NOT NULL DEFAULT current_timestamp() AFTER `event_type`,"
|
||||
+ " CHANGE COLUMN `event_deleted` `event_deleted` TIMESTAMP NULL DEFAULT NULL AFTER `event_created`,"
|
||||
+ " CHANGE COLUMN `event_target` `event_channel` VARCHAR(255) NULL DEFAULT NULL COLLATE 'ascii_general_ci' AFTER `event_deleted`,"
|
||||
+ " CHANGE COLUMN `event_sender_name` `event_sender_name` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER `event_sender`,"
|
||||
+ " CHANGE COLUMN `event_sender_nick` `event_sender_nick` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER `event_sender_rank`,"
|
||||
+ " CHANGE COLUMN `event_data` `event_data` JSON NOT NULL DEFAULT '{}' AFTER `event_sender_perms`,"
|
||||
+ " DROP INDEX `event_target`, ADD INDEX `event_channel` (`event_channel`)"
|
||||
);
|
||||
}
|
||||
|
||||
private void DeprecateEventFlags() {
|
||||
|
|
|
@ -17,10 +17,10 @@ namespace SharpChat.EventStorage {
|
|||
return Events.TryGetValue(eventId.ToString(), out ChatEventInfo? evt) ? evt : null;
|
||||
}
|
||||
|
||||
public IEnumerable<ChatEventInfo> GetChannelEventLog(string channelName, int amount = 20, int startAt = 0) {
|
||||
public IEnumerable<ChatEventInfo> GetChannelEventLog(string channelName, int amount = 20, int after = 0) {
|
||||
IEnumerable<ChatEventInfo> subset = Events.Values.Where(ev => ev.ChannelName == channelName);
|
||||
|
||||
int start = subset.Count() - startAt - amount;
|
||||
int start = subset.Count() - after - amount;
|
||||
if(start < 0) {
|
||||
amount += start;
|
||||
start = 0;
|
||||
|
|
26
SharpChatCommon/Events/UserKickBanEventData.cs
Normal file
26
SharpChatCommon/Events/UserKickBanEventData.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharpChat.Events {
|
||||
[ChatEventDataFor("user:kickban")]
|
||||
public class UserKickBanEventData : ChatEventData {
|
||||
[JsonPropertyName("reason")]
|
||||
public UserDisconnectReason Reason { get; }
|
||||
|
||||
[JsonPropertyName("expires")]
|
||||
public DateTimeOffset Expires { get; }
|
||||
|
||||
public UserKickBanEventData(
|
||||
UserDisconnectReason reason,
|
||||
DateTimeOffset expires
|
||||
) {
|
||||
Reason = reason;
|
||||
Expires = expires;
|
||||
}
|
||||
|
||||
public UserKickBanEventData(
|
||||
UserDisconnectReason reason,
|
||||
TimeSpan duration
|
||||
) : this(reason, DateTimeOffset.UtcNow.Add(duration)) {}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using SharpChat.SockChat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
|
Loading…
Reference in a new issue