2023-02-16 20:34:59 +00:00
using Fleck ;
using SharpChat.Events ;
2023-02-10 06:07:59 +00:00
using SharpChat.EventStorage ;
2022-08-30 15:00:58 +00:00
using SharpChat.Packet ;
using System ;
using System.Collections.Generic ;
2023-02-10 06:07:59 +00:00
using System.Linq ;
2023-02-16 22:33:48 +00:00
using System.Net ;
2022-08-30 15:00:58 +00:00
namespace SharpChat {
2023-02-10 06:07:59 +00:00
public class ChatContext {
public HashSet < ChatChannel > Channels { get ; } = new ( ) ;
public readonly object ChannelsAccess = new ( ) ;
2022-08-30 15:00:58 +00:00
2023-02-16 21:25:41 +00:00
public HashSet < ChatConnection > Connections { get ; } = new ( ) ;
public readonly object ConnectionsAccess = new ( ) ;
2023-02-16 20:34:59 +00:00
2023-02-10 06:07:59 +00:00
public HashSet < ChatUser > Users { get ; } = new ( ) ;
public readonly object UsersAccess = new ( ) ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
public IEventStorage Events { get ; }
public readonly object EventsAccess = new ( ) ;
public ChatContext ( IEventStorage evtStore ) {
Events = evtStore ? ? throw new ArgumentNullException ( nameof ( evtStore ) ) ;
2022-08-30 15:00:58 +00:00
}
public void Update ( ) {
2023-02-16 22:33:48 +00:00
lock ( ConnectionsAccess ) {
foreach ( ChatConnection conn in Connections )
if ( ! conn . IsDisposed & & conn . HasTimedOut ) {
2023-02-16 21:25:41 +00:00
conn . Dispose ( ) ;
2023-02-16 22:33:48 +00:00
Logger . Write ( $"Nuked connection {conn.Id} associated with {conn.User}." ) ;
2023-02-10 06:07:59 +00:00
}
2023-02-16 22:33:48 +00:00
Connections . RemoveWhere ( conn = > conn . IsDisposed ) ;
lock ( UsersAccess )
foreach ( ChatUser user in Users )
if ( ! Connections . Any ( conn = > conn . User = = user ) ) {
UserLeave ( null , user , UserDisconnectReason . TimeOut ) ;
Logger . Write ( $"Timed out {user} (no more connections)." ) ;
}
}
2022-08-30 15:00:58 +00:00
}
2023-02-16 21:25:41 +00:00
public ChatConnection GetConnection ( IWebSocketConnection sock ) {
return Connections . FirstOrDefault ( s = > s . Socket = = sock ) ;
2023-02-16 20:34:59 +00:00
}
2023-02-07 22:28:06 +00:00
public void BanUser ( ChatUser user , TimeSpan duration , UserDisconnectReason reason = UserDisconnectReason . Kicked ) {
if ( duration > TimeSpan . Zero )
2023-02-16 22:33:48 +00:00
SendTo ( user , new ForceDisconnectPacket ( ForceDisconnectReason . Banned , DateTimeOffset . Now + duration ) ) ;
2023-02-07 22:28:06 +00:00
else
2023-02-16 22:33:48 +00:00
SendTo ( user , new ForceDisconnectPacket ( ForceDisconnectReason . Kicked ) ) ;
lock ( ConnectionsAccess ) {
foreach ( ChatConnection conn in Connections )
if ( conn . User = = user )
conn . Dispose ( ) ;
Connections . RemoveWhere ( conn = > conn . IsDisposed ) ;
}
2022-08-30 15:00:58 +00:00
UserLeave ( user . Channel , user , reason ) ;
}
2023-02-16 21:25:41 +00:00
public void HandleJoin ( ChatUser user , ChatChannel chan , ChatConnection conn , int maxMsgLength ) {
2023-02-10 06:07:59 +00:00
lock ( EventsAccess ) {
if ( ! chan . HasUser ( user ) ) {
2023-02-16 22:33:48 +00:00
SendTo ( chan , new UserConnectPacket ( DateTimeOffset . Now , user ) ) ;
2023-02-10 06:07:59 +00:00
Events . AddEvent ( new UserConnectEvent ( DateTimeOffset . Now , user , chan ) ) ;
}
2022-08-30 15:00:58 +00:00
2023-02-16 21:25:41 +00:00
conn . Send ( new AuthSuccessPacket ( user , chan , conn , maxMsgLength ) ) ;
conn . Send ( new ContextUsersPacket ( chan . GetUsers ( new [ ] { user } ) ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-16 22:47:30 +00:00
foreach ( IChatEvent msg in Events . GetChannelEventLog ( chan . Name ) )
2023-02-16 21:25:41 +00:00
conn . Send ( new ContextMessagePacket ( msg ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
lock ( ChannelsAccess )
2023-02-16 21:25:41 +00:00
conn . Send ( new ContextChannelsPacket ( Channels . Where ( c = > c . Rank < = user . Rank ) ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
if ( ! chan . HasUser ( user ) )
chan . UserJoin ( user ) ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
lock ( UsersAccess )
Users . Add ( user ) ;
}
2022-08-30 15:00:58 +00:00
}
public void UserLeave ( ChatChannel chan , ChatUser user , UserDisconnectReason reason = UserDisconnectReason . Leave ) {
user . Status = ChatUserStatus . Offline ;
2023-02-07 15:01:56 +00:00
if ( chan = = null ) {
2022-08-30 15:00:58 +00:00
foreach ( ChatChannel channel in user . GetChannels ( ) ) {
UserLeave ( channel , user , reason ) ;
}
return ;
}
2023-02-07 15:01:56 +00:00
if ( chan . IsTemporary & & chan . Owner = = user )
2023-02-10 06:07:59 +00:00
lock ( ChannelsAccess )
RemoveChannel ( chan ) ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
lock ( EventsAccess ) {
chan . UserLeave ( user ) ;
2023-02-16 22:33:48 +00:00
SendTo ( chan , new UserDisconnectPacket ( DateTimeOffset . Now , user , reason ) ) ;
2023-02-10 06:07:59 +00:00
Events . AddEvent ( new UserDisconnectEvent ( DateTimeOffset . Now , user , chan , reason ) ) ;
}
2022-08-30 15:00:58 +00:00
}
public void SwitchChannel ( ChatUser user , ChatChannel chan , string password ) {
2023-02-07 15:01:56 +00:00
if ( user . CurrentChannel = = chan ) {
2023-02-16 22:33:48 +00:00
ForceChannel ( user ) ;
2022-08-30 15:00:58 +00:00
return ;
}
2023-02-07 15:01:56 +00:00
if ( ! user . Can ( ChatUserPermissions . JoinAnyChannel ) & & chan . Owner ! = user ) {
if ( chan . Rank > user . Rank ) {
2023-02-16 22:33:48 +00:00
SendTo ( user , new LegacyCommandResponse ( LCR . CHANNEL_INSUFFICIENT_HIERARCHY , true , chan . Name ) ) ;
ForceChannel ( user ) ;
2022-08-30 15:00:58 +00:00
return ;
}
2023-02-07 15:01:56 +00:00
if ( chan . Password ! = password ) {
2023-02-16 22:33:48 +00:00
SendTo ( user , new LegacyCommandResponse ( LCR . CHANNEL_INVALID_PASSWORD , true , chan . Name ) ) ;
ForceChannel ( user ) ;
2022-08-30 15:00:58 +00:00
return ;
}
}
ForceChannelSwitch ( user , chan ) ;
}
public void ForceChannelSwitch ( ChatUser user , ChatChannel chan ) {
2023-02-10 06:07:59 +00:00
lock ( ChannelsAccess )
if ( ! Channels . Contains ( chan ) )
return ;
2022-08-30 15:00:58 +00:00
ChatChannel oldChan = user . CurrentChannel ;
2023-02-10 06:07:59 +00:00
lock ( EventsAccess ) {
2023-02-16 22:33:48 +00:00
SendTo ( oldChan , new UserChannelLeavePacket ( user ) ) ;
2023-02-10 06:07:59 +00:00
Events . AddEvent ( new UserChannelLeaveEvent ( DateTimeOffset . Now , user , oldChan ) ) ;
2023-02-16 22:33:48 +00:00
SendTo ( chan , new UserChannelJoinPacket ( user ) ) ;
2023-02-10 06:07:59 +00:00
Events . AddEvent ( new UserChannelJoinEvent ( DateTimeOffset . Now , user , chan ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-16 22:33:48 +00:00
SendTo ( user , new ContextClearPacket ( chan , ContextClearMode . MessagesUsers ) ) ;
SendTo ( user , new ContextUsersPacket ( chan . GetUsers ( new [ ] { user } ) ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-16 22:47:30 +00:00
foreach ( IChatEvent msg in Events . GetChannelEventLog ( chan . Name ) )
2023-02-16 22:33:48 +00:00
SendTo ( user , new ContextMessagePacket ( msg ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-16 22:33:48 +00:00
ForceChannel ( user , chan ) ;
2023-02-10 06:07:59 +00:00
oldChan . UserLeave ( user ) ;
chan . UserJoin ( user ) ;
}
2022-08-30 15:00:58 +00:00
2023-02-07 15:01:56 +00:00
if ( oldChan . IsTemporary & & oldChan . Owner = = user )
2023-02-10 06:07:59 +00:00
lock ( ChannelsAccess )
RemoveChannel ( oldChan ) ;
2022-08-30 15:00:58 +00:00
}
2023-02-10 06:07:59 +00:00
public void Send ( IServerPacket packet ) {
2023-02-16 22:33:48 +00:00
if ( packet = = null )
throw new ArgumentNullException ( nameof ( packet ) ) ;
lock ( ConnectionsAccess )
foreach ( ChatConnection conn in Connections )
if ( conn . IsAuthed )
conn . Send ( packet ) ;
}
public void SendTo ( ChatUser user , IServerPacket packet ) {
if ( user = = null )
throw new ArgumentNullException ( nameof ( user ) ) ;
if ( packet = = null )
throw new ArgumentNullException ( nameof ( packet ) ) ;
lock ( ConnectionsAccess )
foreach ( ChatConnection conn in Connections )
if ( conn . IsAlive & & conn . User = = user )
conn . Send ( packet ) ;
}
public void SendTo ( ChatChannel channel , IServerPacket packet ) {
if ( channel = = null )
throw new ArgumentNullException ( nameof ( channel ) ) ;
if ( packet = = null )
throw new ArgumentNullException ( nameof ( packet ) ) ;
lock ( ConnectionsAccess ) {
IEnumerable < ChatConnection > conns = Connections . Where ( c = > c . IsAuthed & & channel . HasUser ( c . User ) ) ;
foreach ( ChatConnection conn in conns )
conn . Send ( packet ) ;
}
}
public IPAddress [ ] GetRemoteAddresses ( ChatUser user ) {
lock ( ConnectionsAccess )
return Connections . Where ( c = > c . IsAlive & & c . User = = user ) . Select ( c = > c . RemoteAddress ) . Distinct ( ) . ToArray ( ) ;
}
public void ForceChannel ( ChatUser user , ChatChannel chan = null ) {
if ( user = = null )
throw new ArgumentNullException ( nameof ( user ) ) ;
SendTo ( user , new UserChannelForceJoinPacket ( chan ? ? user . CurrentChannel ) ) ;
2023-02-10 06:07:59 +00:00
}
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
public void UpdateChannel ( ChatChannel channel , string name = null , bool? temporary = null , int? hierarchy = null , string password = null ) {
if ( channel = = null )
throw new ArgumentNullException ( nameof ( channel ) ) ;
if ( ! Channels . Contains ( channel ) )
throw new ArgumentException ( "Provided channel is not registered with this manager." , nameof ( channel ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
string prevName = channel . Name ;
int prevHierarchy = channel . Rank ;
bool nameUpdated = ! string . IsNullOrWhiteSpace ( name ) & & name ! = prevName ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
if ( nameUpdated ) {
if ( ! ChatChannel . CheckName ( name ) )
throw new ArgumentException ( "Name contains invalid characters." , nameof ( name ) ) ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
channel . Name = name ;
}
if ( temporary . HasValue )
channel . IsTemporary = temporary . Value ;
if ( hierarchy . HasValue )
channel . Rank = hierarchy . Value ;
if ( password ! = null )
channel . Password = password ;
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
// Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively
lock ( UsersAccess )
foreach ( ChatUser user in Users . Where ( u = > u . Rank > = channel . Rank ) ) {
2023-02-16 22:33:48 +00:00
SendTo ( user , new ChannelUpdatePacket ( prevName , channel ) ) ;
2023-02-10 06:07:59 +00:00
if ( nameUpdated )
2023-02-16 22:33:48 +00:00
ForceChannel ( user ) ;
2023-02-10 06:07:59 +00:00
}
2023-02-06 20:14:50 +00:00
}
2022-08-30 15:00:58 +00:00
2023-02-10 06:07:59 +00:00
public void RemoveChannel ( ChatChannel channel ) {
if ( channel = = null | | ! Channels . Any ( ) )
return ;
ChatChannel defaultChannel = Channels . FirstOrDefault ( ) ;
if ( defaultChannel = = null )
2022-08-30 15:00:58 +00:00
return ;
2023-02-10 06:07:59 +00:00
// Remove channel from the listing
Channels . Remove ( channel ) ;
// Move all users back to the main channel
// TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
foreach ( ChatUser user in channel . GetUsers ( ) )
SwitchChannel ( user , defaultChannel , string . Empty ) ;
// Broadcast deletion of channel
lock ( UsersAccess )
foreach ( ChatUser user in Users . Where ( u = > u . Rank > = channel . Rank ) )
2023-02-16 22:33:48 +00:00
SendTo ( user , new ChannelDeletePacket ( channel ) ) ;
2022-08-30 15:00:58 +00:00
}
}
}