@ -19,6 +19,7 @@ namespace SharpChat {
public IEventStorage Events { get ; }
public HashSet < ChannelUserAssoc > ChannelUsers { get ; } = new ( ) ;
public Dictionary < long , RateLimiter > UserRateLimiters { get ; } = new ( ) ;
public Dictionary < long , ChatChannel > UserLastChannel { get ; } = new ( ) ;
public ChatContext ( IEventStorage evtStore ) {
Events = evtStore ? ? throw new ArgumentNullException ( nameof ( evtStore ) ) ;
@ -71,6 +72,65 @@ namespace SharpChat {
return Users . Where ( u = > ids . Contains ( u . UserId ) ) . ToArray ( ) ;
}
public void UpdateUser (
ChatUser user ,
string userName = null ,
string nickName = null ,
ChatColour ? colour = null ,
ChatUserStatus ? status = null ,
string statusText = null ,
int? rank = null ,
ChatUserPermissions ? perms = null ,
bool silent = false
) {
if ( user = = null )
throw new ArgumentNullException ( nameof ( user ) ) ;
bool hasChanged = false ;
string previousName = null ;
if ( userName ! = null & & ! user . UserName . Equals ( userName ) ) {
user . UserName = userName ;
hasChanged = true ;
}
if ( nickName ! = null & & ! user . NickName . Equals ( nickName ) ) {
if ( ! silent )
previousName = string . IsNullOrWhiteSpace ( user . NickName ) ? user . UserName : user . NickName ;
user . NickName = nickName ;
hasChanged = true ;
}
if ( colour . HasValue & & user . Colour ! = colour . Value ) {
user . Colour = colour . Value ;
hasChanged = true ;
}
if ( status . HasValue & & user . Status ! = status . Value ) {
user . Status = status . Value ;
hasChanged = true ;
}
if ( statusText ! = null & & ! user . StatusText . Equals ( statusText ) ) {
user . StatusText = statusText ;
hasChanged = true ;
}
if ( rank ! = null & & user . Rank ! = rank ) {
user . Rank = ( int ) rank ;
hasChanged = true ;
}
if ( perms . HasValue & & user . Permissions ! = perms ) {
user . Permissions = perms . Value ;
hasChanged = true ;
}
if ( hasChanged )
SendToUserChannels ( user , new UserUpdatePacket ( user , previousName ) ) ;
}
public void BanUser ( ChatUser user , TimeSpan duration , UserDisconnectReason reason = UserDisconnectReason . Kicked ) {
if ( duration > TimeSpan . Zero )
SendTo ( user , new ForceDisconnectPacket ( ForceDisconnectReason . Banned , DateTimeOffset . Now + duration ) ) ;
@ -102,12 +162,13 @@ namespace SharpChat {
Users . Add ( user ) ;
ChannelUsers . Add ( new ChannelUserAssoc ( user . UserId , chan . Name ) ) ;
user. CurrentChannel = chan ;
UserLastChannel[ user . UserId ] = chan ;
}
public void HandleDisconnect ( ChatUser user , UserDisconnectReason reason = UserDisconnectReason . Leave ) {
user. Status = ChatUserStatus . Offline ;
UpdateUser( user , status : ChatUserStatus . Offline ) ;
Users . Remove ( user ) ;
UserLastChannel . Remove ( user . UserId ) ;
ChatChannel [ ] channels = GetUserChannels ( user ) ;
@ -117,25 +178,25 @@ namespace SharpChat {
SendTo ( chan , new UserDisconnectPacket ( DateTimeOffset . Now , user , reason ) ) ;
Events . AddEvent ( new UserDisconnectEvent ( DateTimeOffset . Now , user , chan , reason ) ) ;
if ( chan . IsTemporary & & chan . Owner = = user )
if ( chan . IsTemporary & & chan . IsOwner( user ) )
RemoveChannel ( chan ) ;
}
}
public void SwitchChannel ( ChatUser user , ChatChannel chan , string password ) {
if ( user. CurrentChannel = = chan ) {
if ( UserLastChannel. TryGetValue ( user . UserId , out ChatChannel ulc ) & & chan = = ulc ) {
ForceChannel ( user ) ;
return ;
}
if ( ! user . Can ( ChatUserPermissions . JoinAnyChannel ) & & chan . Owner ! = user ) {
if ( ! user . Can ( ChatUserPermissions . JoinAnyChannel ) & & chan . IsOwner( user ) ) {
if ( chan . Rank > user . Rank ) {
SendTo ( user , new LegacyCommandResponse ( LCR . CHANNEL_INSUFFICIENT_HIERARCHY , true , chan . Name ) ) ;
ForceChannel ( user ) ;
return ;
}
if ( chan . Password ! = password ) {
if ( ! string . IsNullOrEmpty ( chan . Password ) & & chan . Password ! = password ) {
SendTo ( user , new LegacyCommandResponse ( LCR . CHANNEL_INVALID_PASSWORD , true , chan . Name ) ) ;
ForceChannel ( user ) ;
return ;
@ -149,7 +210,7 @@ namespace SharpChat {
if ( ! Channels . Contains ( chan ) )
return ;
ChatChannel oldChan = user. CurrentChannel ;
ChatChannel oldChan = UserLastChannel[ user . UserId ] ;
SendTo ( oldChan , new UserChannelLeavePacket ( user ) ) ;
Events . AddEvent ( new UserChannelLeaveEvent ( DateTimeOffset . Now , user , oldChan ) ) ;
@ -166,9 +227,9 @@ namespace SharpChat {
ChannelUsers . Remove ( new ChannelUserAssoc ( user . UserId , oldChan . Name ) ) ;
ChannelUsers . Add ( new ChannelUserAssoc ( user . UserId , chan . Name ) ) ;
user. CurrentChannel = chan ;
UserLastChannel[ user . UserId ] = chan ;
if ( oldChan . IsTemporary & & oldChan . Owner = = user )
if ( oldChan . IsTemporary & & oldChan . IsOwner( user ) )
RemoveChannel ( oldChan ) ;
}
@ -204,6 +265,18 @@ namespace SharpChat {
conn . Send ( packet ) ;
}
public void SendToUserChannels ( ChatUser user , IServerPacket packet ) {
if ( user = = null )
throw new ArgumentNullException ( nameof ( user ) ) ;
if ( packet = = null )
throw new ArgumentNullException ( nameof ( packet ) ) ;
IEnumerable < ChatChannel > chans = Channels . Where ( c = > IsInChannel ( user , c ) ) ;
IEnumerable < ChatConnection > conns = Connections . Where ( conn = > conn . IsAuthed & & ChannelUsers . Any ( cu = > cu . UserId = = conn . User . UserId & & chans . Any ( chan = > chan . NameEquals ( cu . ChannelName ) ) ) ) ;
foreach ( ChatConnection conn in conns )
conn . Send ( packet ) ;
}
public IPAddress [ ] GetRemoteAddresses ( ChatUser user ) {
return Connections . Where ( c = > c . IsAlive & & c . User = = user ) . Select ( c = > c . RemoteAddress ) . Distinct ( ) . ToArray ( ) ;
}
@ -212,26 +285,18 @@ namespace SharpChat {
if ( user = = null )
throw new ArgumentNullException ( nameof ( user ) ) ;
SendTo ( user , new UserChannelForceJoinPacket ( chan ? ? user . CurrentChannel ) ) ;
if ( chan = = null & & ! UserLastChannel . TryGetValue ( user . UserId , out chan ) )
throw new ArgumentException ( "no channel???" ) ;
SendTo ( user , new UserChannelForceJoinPacket ( chan ) ) ;
}
public void UpdateChannel ( ChatChannel channel , string name = null , bool? temporary = null , int? hierarchy = null , string password = null ) {
public void UpdateChannel ( ChatChannel channel , 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 ) ) ;
string prevName = channel . Name ;
int prevHierarchy = channel . Rank ;
bool nameUpdated = ! string . IsNullOrWhiteSpace ( name ) & & name ! = prevName ;
if ( nameUpdated ) {
if ( ! ChatChannel . CheckName ( name ) )
throw new ArgumentException ( "Name contains invalid characters." , nameof ( name ) ) ;
channel . Name = name ;
}
if ( temporary . HasValue )
channel . IsTemporary = temporary . Value ;
@ -241,12 +306,9 @@ namespace SharpChat {
if ( password ! = null )
channel . Password = password ;
// 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
// TODO: 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
foreach ( ChatUser user in Users . Where ( u = > u . Rank > = channel . Rank ) ) {
SendTo ( user , new ChannelUpdatePacket ( prevName , channel ) ) ;
if ( nameUpdated )
ForceChannel ( user ) ;
SendTo ( user , new ChannelUpdatePacket ( channel . Name , channel ) ) ;
}
}