2023-07-23 21:31:13 +00:00
using SharpChat.Events ;
using SharpChat.EventStorage ;
2024-05-20 23:40:34 +00:00
using SharpChat.SockChat ;
using SharpChat.SockChat.PacketsS2C ;
2022-08-30 15:00:58 +00:00
using System ;
using System.Collections.Generic ;
2023-02-10 06:07:59 +00:00
using System.Linq ;
2023-02-19 22:27:08 +00:00
using System.Threading ;
2022-08-30 15:00:58 +00:00
2024-05-09 21:31:19 +00:00
namespace SharpChat {
2024-05-24 03:44:20 +00:00
public class SockChatContext : IChatEventHandler {
2023-02-19 22:27:08 +00:00
public readonly SemaphoreSlim ContextAccess = new ( 1 , 1 ) ;
2022-08-30 15:00:58 +00:00
2024-05-19 21:02:17 +00:00
public ChannelsContext Channels { get ; } = new ( ) ;
2024-05-21 20:08:23 +00:00
public ConnectionsContext Connections { get ; } = new ( ) ;
2024-05-19 21:02:17 +00:00
public UsersContext Users { get ; } = new ( ) ;
2024-05-24 03:44:20 +00:00
public IEventStorage EventStorage { get ; }
public ChatEventDispatcher Events { get ; } = new ( ) ;
2024-05-19 21:02:17 +00:00
public ChannelsUsersContext ChannelsUsers { get ; } = new ( ) ;
2023-02-19 22:27:08 +00:00
public Dictionary < long , RateLimiter > UserRateLimiters { get ; } = new ( ) ;
2023-02-17 19:02:35 +00:00
2024-05-20 23:40:34 +00:00
public SockChatContext ( IEventStorage evtStore ) {
2024-05-24 03:44:20 +00:00
EventStorage = evtStore ;
Events . Subscribe ( evtStore ) ;
Events . Subscribe ( this ) ;
2022-08-30 15:00:58 +00:00
}
2024-05-24 03:44:20 +00:00
public void HandleEvent ( ChatEventInfo info ) {
// user status should be stored outside of the UserInfo class so we don't need to do this:
UserInfo ? userInfo = Users . Get ( info . SenderId ) ;
switch ( info . Type ) {
case "user:connect" :
SendTo ( info . ChannelName , new UserConnectS2CPacket (
info . Id ,
info . Created ,
info . SenderId ,
userInfo = = null ? SockChatUtility . GetUserName ( info ) : SockChatUtility . GetUserNameWithStatus ( userInfo ) ,
info . SenderColour ,
info . SenderRank ,
info . SenderPerms
) ) ;
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
) ) ;
break ;
case "chan:join" :
SendTo ( info . ChannelName , new UserChannelJoinS2CPacket (
info . SenderId ,
userInfo = = null ? SockChatUtility . GetUserName ( info ) : SockChatUtility . GetUserNameWithStatus ( userInfo ) ,
info . SenderColour ,
info . SenderRank ,
info . SenderPerms
) ) ;
break ;
case "chan:leave" :
SendTo ( info . ChannelName , new UserChannelLeaveS2CPacket ( info . SenderId ) ) ;
break ;
case "msg:delete" :
if ( info . Data is not MessageDeleteEventData msgDelete )
break ;
2023-07-23 21:31:13 +00:00
2024-05-24 03:44:20 +00:00
MessageDeleteS2CPacket msgDelPacket = new ( msgDelete . MessageId ) ;
if ( info . IsBroadcast ) {
Send ( msgDelPacket ) ;
} else if ( info . ChannelName . StartsWith ( '@' ) ) {
long [ ] targetIds = info . ChannelName [ 1. . ] . Split ( '-' , 3 ) . Select ( u = > long . TryParse ( u , out long up ) ? up : - 1 ) . ToArray ( ) ;
if ( targetIds . Length ! = 2 )
return ;
UserInfo [ ] users = Users . GetMany ( targetIds ) ;
UserInfo ? target = users . FirstOrDefault ( u = > u . UserId ! = info . SenderId ) ;
if ( target = = null )
return ;
foreach ( UserInfo user in users )
SendTo ( user , msgDelPacket ) ;
} else {
SendTo ( info . ChannelName , msgDelPacket ) ;
}
break ;
case "msg:add" :
if ( info . Data is not MessageAddEventData msgAdd )
break ;
if ( info . IsBroadcast ) {
Send ( new MessageBroadcastS2CPacket ( info . Id , info . Created , msgAdd . Text ) ) ;
} else if ( info . ChannelName . StartsWith ( '@' ) ) {
// 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
long [ ] targetIds = info . ChannelName [ 1. . ] . Split ( '-' , 3 ) . Select ( u = > long . TryParse ( u , out long up ) ? up : - 1 ) . ToArray ( ) ;
if ( targetIds . Length ! = 2 )
return ;
UserInfo [ ] users = Users . GetMany ( targetIds ) ;
UserInfo ? target = users . FirstOrDefault ( u = > u . UserId ! = info . SenderId ) ;
if ( target = = null )
return ;
foreach ( UserInfo user in users )
SendTo ( user , new MessageAddS2CPacket (
info . Id ,
DateTimeOffset . Now ,
info . SenderId ,
info . SenderId = = user . UserId ? $"{SockChatUtility.GetUserName(target)} {msgAdd.Text}" : msgAdd . Text ,
msgAdd . IsAction ,
true
) ) ;
} else {
ChannelInfo ? channel = Channels . Get ( info . ChannelName , SockChatUtility . SanitiseChannelName ) ;
if ( channel ! = null )
SendTo ( channel , new MessageAddS2CPacket (
info . Id ,
DateTimeOffset . Now ,
info . SenderId ,
msgAdd . Text ,
msgAdd . IsAction ,
false
) ) ;
}
break ;
2023-07-23 21:31:13 +00:00
}
}
2022-08-30 15:00:58 +00:00
public void Update ( ) {
2024-05-21 20:08:23 +00:00
ConnectionInfo [ ] timedOut = Connections . GetTimedOut ( ) ;
foreach ( ConnectionInfo conn in timedOut ) {
2024-05-20 16:16:32 +00:00
Connections . Remove ( conn ) ;
2024-05-21 20:08:23 +00:00
if ( conn is SockChatConnectionInfo scConn )
scConn . Close ( 1002 ) ;
2023-02-19 22:27:08 +00:00
2024-05-20 16:16:32 +00:00
Logger . Write ( $"<{conn.RemoteEndPoint}> Nuked timed out connection from user #{conn.UserId}." ) ;
}
2023-02-19 22:27:08 +00:00
2024-05-19 21:02:17 +00:00
foreach ( UserInfo user in Users . All )
2024-05-20 16:16:32 +00:00
if ( ! Connections . HasUser ( user ) ) {
2024-05-19 02:17:51 +00:00
HandleDisconnect ( user , UserDisconnectReason . TimeOut ) ;
2023-02-19 22:27:08 +00:00
Logger . Write ( $"Timed out {user} (no more connections)." ) ;
}
}
public void SafeUpdate ( ) {
ContextAccess . Wait ( ) ;
try {
Update ( ) ;
} finally {
ContextAccess . Release ( ) ;
2023-02-16 22:33:48 +00:00
}
2022-08-30 15:00:58 +00:00
}
2024-05-19 02:17:51 +00:00
public ChannelInfo [ ] GetUserChannels ( UserInfo user ) {
2024-05-19 21:02:17 +00:00
return Channels . GetMany ( ChannelsUsers . GetUserChannelNames ( user ) ) ;
2023-02-17 19:02:35 +00:00
}
2024-05-19 02:17:51 +00:00
public UserInfo [ ] GetChannelUsers ( ChannelInfo channel ) {
2024-05-19 21:02:17 +00:00
return Users . GetMany ( ChannelsUsers . GetChannelUserIds ( channel ) ) ;
2023-02-17 19:02:35 +00:00
}
2023-02-22 00:28:53 +00:00
public void UpdateUser (
2024-05-19 02:17:51 +00:00
UserInfo user ,
2024-05-10 19:18:55 +00:00
string? userName = null ,
string? nickName = null ,
2024-05-19 02:17:51 +00:00
Colour ? colour = null ,
UserStatus ? status = null ,
2024-05-10 19:18:55 +00:00
string? statusText = null ,
2023-02-22 00:28:53 +00:00
int? rank = null ,
2024-05-19 02:17:51 +00:00
UserPermissions ? perms = null ,
2023-11-07 14:49:12 +00:00
bool? isSuper = null ,
2023-02-22 00:28:53 +00:00
bool silent = false
) {
bool hasChanged = false ;
2024-05-10 19:18:55 +00:00
string? previousName = null ;
2023-02-22 00:28:53 +00:00
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 ;
}
2024-05-19 01:53:14 +00:00
if ( colour . HasValue & & user . Colour . Equals ( colour . Value ) ) {
2023-02-22 00:28:53 +00:00
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 ;
}
2023-11-07 14:49:12 +00:00
if ( isSuper . HasValue ) {
user . IsSuper = isSuper . Value ;
hasChanged = true ;
}
2024-05-10 15:07:56 +00:00
if ( hasChanged ) {
if ( previousName ! = null )
2024-05-20 23:00:47 +00:00
SendToUserChannels ( user , new UserUpdateNotificationS2CPacket ( previousName , SockChatUtility . GetUserNameWithStatus ( user ) ) ) ;
2024-05-10 15:07:56 +00:00
2024-05-20 23:00:47 +00:00
SendToUserChannels ( user , new UserUpdateS2CPacket (
2024-05-10 18:29:48 +00:00
user . UserId ,
2024-05-19 21:02:17 +00:00
SockChatUtility . GetUserNameWithStatus ( user ) ,
2024-05-10 18:29:48 +00:00
user . Colour ,
user . Rank ,
user . Permissions
) ) ;
2024-05-10 15:07:56 +00:00
}
2023-02-22 00:28:53 +00:00
}
2024-05-19 02:17:51 +00:00
public void BanUser ( UserInfo user , TimeSpan duration , UserDisconnectReason reason = UserDisconnectReason . Kicked ) {
2024-05-09 21:31:19 +00:00
if ( duration > TimeSpan . Zero ) {
2023-07-23 21:45:10 +00:00
DateTimeOffset expires = duration > = TimeSpan . MaxValue ? DateTimeOffset . MaxValue : DateTimeOffset . Now + duration ;
2024-05-20 23:00:47 +00:00
SendTo ( user , new ForceDisconnectS2CPacket ( expires ) ) ;
2023-07-23 21:45:10 +00:00
} else
2024-05-20 23:00:47 +00:00
SendTo ( user , new ForceDisconnectS2CPacket ( ) ) ;
2023-02-16 22:33:48 +00:00
2024-05-21 20:08:23 +00:00
ConnectionInfo [ ] conns = Connections . GetUser ( user ) ;
foreach ( ConnectionInfo conn in conns ) {
2024-05-20 16:16:32 +00:00
Connections . Remove ( conn ) ;
2024-05-21 20:08:23 +00:00
if ( conn is SockChatConnectionInfo scConn )
scConn . Close ( 1000 ) ;
2024-05-20 16:16:32 +00:00
Logger . Write ( $"<{conn.RemoteEndPoint}> Nuked connection from banned user #{conn.UserId}." ) ;
}
2022-08-30 15:00:58 +00:00
2023-02-17 19:02:35 +00:00
HandleDisconnect ( user , reason ) ;
2022-08-30 15:00:58 +00:00
}
2024-05-23 23:13:57 +00:00
public void HandleChannelEventLog ( string channelName , Action < ISockChatS2CPacket > handler ) {
2024-05-24 03:44:20 +00:00
foreach ( ChatEventInfo info in EventStorage . GetChannelEventLog ( channelName ) ) {
switch ( info . Type ) {
2024-05-24 00:23:31 +00:00
case "msg:add" :
2024-05-24 13:11:21 +00:00
if ( info . Data is not MessageAddEventData msgAdd )
break ;
2024-05-24 00:23:31 +00:00
2024-05-24 13:11:21 +00:00
handler ( new MessageAddLogS2CPacket (
2024-05-24 03:44:20 +00:00
info . Id ,
info . Created ,
info . SenderId ,
info . SenderName ,
info . SenderColour ,
info . SenderRank ,
info . SenderPerms ,
2024-05-24 13:11:21 +00:00
msgAdd . Text ,
msgAdd . IsAction ,
2024-05-24 03:44:20 +00:00
info . ChannelName . StartsWith ( '@' ) ,
info . IsBroadcast ,
2024-05-24 00:23:31 +00:00
false
2024-05-24 13:11:21 +00:00
) ) ;
2024-05-24 00:23:31 +00:00
break ;
case "user:connect" :
2024-05-24 13:11:21 +00:00
handler ( new UserConnectLogS2CPacket (
2024-05-24 03:44:20 +00:00
info . Id ,
info . Created ,
SockChatUtility . GetUserName ( info )
2024-05-24 13:11:21 +00:00
) ) ;
2024-05-24 00:23:31 +00:00
break ;
case "user:disconnect" :
2024-05-24 13:11:21 +00:00
handler ( new UserDisconnectLogS2CPacket (
2024-05-24 03:44:20 +00:00
info . Id ,
info . Created ,
SockChatUtility . GetUserName ( info ) ,
info . Data is UserDisconnectEventData userDisconnect ? userDisconnect . Reason : UserDisconnectReason . Leave
2024-05-24 13:11:21 +00:00
) ) ;
2024-05-24 00:23:31 +00:00
break ;
case "chan:join" :
2024-05-24 13:11:21 +00:00
handler ( new UserChannelJoinLogS2CPacket (
2024-05-24 03:44:20 +00:00
info . Id ,
info . Created ,
SockChatUtility . GetUserName ( info )
2024-05-24 13:11:21 +00:00
) ) ;
2024-05-24 00:23:31 +00:00
break ;
case "chan:leave" :
2024-05-24 13:11:21 +00:00
handler ( new UserChannelLeaveLogS2CPacket (
2024-05-24 03:44:20 +00:00
info . Id ,
info . Created ,
SockChatUtility . GetUserName ( info )
2024-05-24 13:11:21 +00:00
) ) ;
2024-05-24 03:44:20 +00:00
break ;
2024-05-24 00:23:31 +00:00
}
}
2024-05-14 22:56:56 +00:00
}
2024-05-20 23:40:34 +00:00
public void HandleJoin ( UserInfo user , ChannelInfo chan , SockChatConnectionInfo conn , int maxMsgLength ) {
2024-05-24 03:44:20 +00:00
if ( ! ChannelsUsers . Has ( chan , user ) )
Events . Dispatch ( "user:connect" , chan , user ) ;
2022-08-30 15:00:58 +00:00
2024-05-20 23:00:47 +00:00
conn . Send ( new AuthSuccessS2CPacket (
2024-05-10 18:29:48 +00:00
user . UserId ,
2024-05-19 21:02:17 +00:00
SockChatUtility . GetUserNameWithStatus ( user ) ,
2024-05-10 18:29:48 +00:00
user . Colour ,
user . Rank ,
user . Permissions ,
chan . Name ,
maxMsgLength
) ) ;
2024-05-20 23:00:47 +00:00
conn . Send ( new UsersPopulateS2CPacket ( GetChannelUsers ( chan ) . Except ( new [ ] { user } ) . Select (
user = > new UsersPopulateS2CPacket . ListEntry (
2024-05-19 21:02:17 +00:00
user . UserId ,
SockChatUtility . GetUserNameWithStatus ( user ) ,
user . Colour ,
user . Rank ,
user . Permissions ,
true
)
2024-05-10 18:29:48 +00:00
) . OrderByDescending ( user = > user . Rank ) . ToArray ( ) ) ) ;
2022-08-30 15:00:58 +00:00
2024-05-14 22:56:56 +00:00
HandleChannelEventLog ( chan . Name , p = > conn . Send ( p ) ) ;
2022-08-30 15:00:58 +00:00
2024-05-20 23:00:47 +00:00
conn . Send ( new ChannelsPopulateS2CPacket ( Channels . GetMany ( isPublic : true , minRank : user . Rank ) . Select (
channel = > new ChannelsPopulateS2CPacket . ListEntry ( channel . Name , channel . HasPassword , channel . IsTemporary )
2024-05-10 18:29:48 +00:00
) . ToArray ( ) ) ) ;
2022-08-30 15:00:58 +00:00
2024-05-19 21:02:17 +00:00
if ( Users . Get ( userId : user . UserId ) = = null )
Users . Add ( user ) ;
2023-02-17 19:02:35 +00:00
2024-05-19 21:02:17 +00:00
ChannelsUsers . Join ( chan . Name , user . UserId ) ;
2022-08-30 15:00:58 +00:00
}
2024-05-19 02:17:51 +00:00
public void HandleDisconnect ( UserInfo user , UserDisconnectReason reason = UserDisconnectReason . Leave ) {
UpdateUser ( user , status : UserStatus . Offline ) ;
2024-05-19 01:53:14 +00:00
Users . Remove ( user . UserId ) ;
2022-08-30 15:00:58 +00:00
2024-05-19 02:17:51 +00:00
ChannelInfo [ ] channels = GetUserChannels ( user ) ;
2024-05-19 21:02:17 +00:00
ChannelsUsers . DeleteUser ( user ) ;
2022-08-30 15:00:58 +00:00
2024-05-19 02:17:51 +00:00
foreach ( ChannelInfo chan in channels ) {
2024-05-24 03:44:20 +00:00
Events . Dispatch ( "user:disconnect" , chan , user , new UserDisconnectEventData ( reason ) ) ;
2023-02-17 19:02:35 +00:00
2023-02-22 00:28:53 +00:00
if ( chan . IsTemporary & & chan . IsOwner ( user ) )
2023-02-19 22:27:08 +00:00
RemoveChannel ( chan ) ;
2023-02-10 06:07:59 +00:00
}
2022-08-30 15:00:58 +00:00
}
2024-05-19 02:17:51 +00:00
public void SwitchChannel ( UserInfo user , ChannelInfo chan , string password ) {
2024-05-19 21:02:17 +00:00
if ( ChannelsUsers . IsUserLastChannel ( user , chan ) ) {
2023-02-16 22:33:48 +00:00
ForceChannel ( user ) ;
2022-08-30 15:00:58 +00:00
return ;
}
2024-05-19 02:17:51 +00:00
if ( ! user . Permissions . HasFlag ( UserPermissions . JoinAnyChannel ) & & chan . IsOwner ( user ) ) {
2023-02-07 15:01:56 +00:00
if ( chan . Rank > user . Rank ) {
2024-05-20 23:00:47 +00:00
SendTo ( user , new ChannelRankTooLowErrorS2CPacket ( chan . Name ) ) ;
2023-02-16 22:33:48 +00:00
ForceChannel ( user ) ;
2022-08-30 15:00:58 +00:00
return ;
}
2024-05-14 22:17:25 +00:00
if ( ! string . IsNullOrEmpty ( chan . Password ) & & chan . Password . Equals ( password ) ) {
2024-05-20 23:00:47 +00:00
SendTo ( user , new ChannelPasswordWrongErrorS2CPacket ( chan . Name ) ) ;
2023-02-16 22:33:48 +00:00
ForceChannel ( user ) ;
2022-08-30 15:00:58 +00:00
return ;
}
}
ForceChannelSwitch ( user , chan ) ;
}
2024-05-19 02:17:51 +00:00
public void ForceChannelSwitch ( UserInfo user , ChannelInfo chan ) {
2024-05-24 03:44:20 +00:00
DateTimeOffset now = DateTimeOffset . UtcNow ;
2024-05-19 21:02:17 +00:00
ChannelInfo ? oldChan = Channels . Get ( ChannelsUsers . GetUserLastChannel ( user ) ) ;
2022-08-30 15:00:58 +00:00
2024-05-19 21:02:17 +00:00
if ( oldChan ! = null )
2024-05-24 03:44:20 +00:00
Events . Dispatch ( "chan:leave" , now , oldChan , user ) ;
Events . Dispatch ( "chan:join" , now , chan , user ) ;
2022-08-30 15:00:58 +00:00
2024-05-20 23:00:47 +00:00
SendTo ( user , new ContextClearS2CPacket ( ContextClearS2CPacket . ClearMode . MessagesUsers ) ) ;
SendTo ( user , new UsersPopulateS2CPacket ( GetChannelUsers ( chan ) . Except ( new [ ] { user } ) . Select (
user = > new UsersPopulateS2CPacket . ListEntry (
2024-05-19 21:02:17 +00:00
user . UserId ,
SockChatUtility . GetUserNameWithStatus ( user ) ,
user . Colour ,
user . Rank ,
user . Permissions ,
true
)
2024-05-10 18:29:48 +00:00
) . OrderByDescending ( u = > u . Rank ) . ToArray ( ) ) ) ;
2022-08-30 15:00:58 +00:00
2024-05-14 22:56:56 +00:00
HandleChannelEventLog ( chan . Name , p = > SendTo ( user , p ) ) ;
2023-02-19 22:27:08 +00:00
ForceChannel ( user , chan ) ;
2023-02-17 19:02:35 +00:00
2024-05-19 21:02:17 +00:00
if ( oldChan ! = null )
ChannelsUsers . Leave ( oldChan , user ) ;
ChannelsUsers . Join ( chan , user ) ;
2022-08-30 15:00:58 +00:00
2024-05-19 21:02:17 +00:00
if ( oldChan ! = null & & oldChan . IsTemporary & & oldChan . IsOwner ( user ) )
2023-02-19 22:27:08 +00:00
RemoveChannel ( oldChan ) ;
2022-08-30 15:00:58 +00:00
}
2024-05-23 23:13:57 +00:00
public void Send ( ISockChatS2CPacket packet ) {
2024-05-20 16:24:14 +00:00
string data = packet . Pack ( ) ;
2024-05-21 20:08:23 +00:00
Connections . WithAuthed ( conn = > {
if ( conn is SockChatConnectionInfo scConn )
scConn . Send ( data ) ;
} ) ;
2023-02-16 22:33:48 +00:00
}
2024-05-23 23:13:57 +00:00
public void SendTo ( UserInfo user , ISockChatS2CPacket packet ) {
2024-05-20 16:24:14 +00:00
string data = packet . Pack ( ) ;
2024-05-21 20:08:23 +00:00
Connections . WithUser ( user , conn = > {
if ( conn is SockChatConnectionInfo scConn )
scConn . Send ( data ) ;
} ) ;
2023-02-16 22:33:48 +00:00
}
2024-05-23 23:13:57 +00:00
public void SendTo ( ChannelInfo channel , ISockChatS2CPacket packet ) {
2024-05-20 16:24:14 +00:00
SendTo ( channel , packet . Pack ( ) ) ;
}
public void SendTo ( ChannelInfo channel , string packet ) {
2024-05-24 03:44:20 +00:00
SendTo ( channel . Name , packet ) ;
}
public void SendTo ( string channelName , ISockChatS2CPacket packet ) {
SendTo ( channelName , packet . Pack ( ) ) ;
}
public void SendTo ( string channelName , string packet ) {
long [ ] userIds = ChannelsUsers . GetChannelUserIds ( channelName ) ;
2024-05-20 16:16:32 +00:00
foreach ( long userId in userIds )
2024-05-21 20:08:23 +00:00
Connections . WithUser ( userId , conn = > {
if ( conn is SockChatConnectionInfo scConn )
scConn . Send ( packet ) ;
} ) ;
2023-02-16 22:33:48 +00:00
}
2024-05-23 23:13:57 +00:00
public void SendToUserChannels ( UserInfo user , ISockChatS2CPacket packet ) {
2024-05-19 21:02:17 +00:00
ChannelInfo [ ] chans = GetUserChannels ( user ) ;
2024-05-20 16:24:14 +00:00
string data = packet . Pack ( ) ;
2024-05-19 21:02:17 +00:00
foreach ( ChannelInfo chan in chans )
2024-05-20 16:24:14 +00:00
SendTo ( chan , data ) ;
2023-02-22 00:28:53 +00:00
}
2024-05-19 02:17:51 +00:00
public void ForceChannel ( UserInfo user , ChannelInfo ? chan = null ) {
2024-05-19 21:02:17 +00:00
chan ? ? = Channels . Get ( ChannelsUsers . GetUserLastChannel ( user ) ) ;
if ( chan ! = null )
2024-05-20 23:00:47 +00:00
SendTo ( user , new UserChannelForceJoinS2CPacket ( chan . Name ) ) ;
2023-02-10 06:07:59 +00:00
}
2022-08-30 15:00:58 +00:00
2024-05-10 19:18:55 +00:00
public void UpdateChannel (
2024-05-19 02:17:51 +00:00
ChannelInfo channel ,
2024-05-10 19:18:55 +00:00
bool? temporary = null ,
int? minRank = null ,
string? password = null
) {
2024-05-10 18:29:48 +00:00
string prevName = channel . Name ;
2023-02-10 06:07:59 +00:00
if ( temporary . HasValue )
channel . IsTemporary = temporary . Value ;
2024-05-10 18:29:48 +00:00
if ( minRank . HasValue )
channel . Rank = minRank . Value ;
2023-02-10 06:07:59 +00:00
if ( password ! = null )
channel . Password = password ;
2022-08-30 15:00:58 +00:00
2024-05-10 18:29:48 +00:00
// TODO: Users that no longer have access to the channel/gained access to the channel by the rank change should receive delete and create packets respectively
2024-05-19 21:02:17 +00:00
// the server currently doesn't keep track of what channels a user is already aware of so can't really simulate this yet.
foreach ( UserInfo user in Users . GetMany ( minRank : channel . Rank ) )
2024-05-20 23:00:47 +00:00
SendTo ( user , new ChannelUpdateS2CPacket ( prevName , channel . Name , channel . HasPassword , channel . IsTemporary ) ) ;
2023-02-06 20:14:50 +00:00
}
2022-08-30 15:00:58 +00:00
2024-05-19 02:17:51 +00:00
public void RemoveChannel ( ChannelInfo channel ) {
2024-05-19 21:02:17 +00:00
if ( channel = = null | | Channels . PublicCount > 1 )
2023-02-10 06:07:59 +00:00
return ;
2024-05-19 21:02:17 +00:00
ChannelInfo ? defaultChannel = Channels . MainChannel ;
2023-02-10 06:07:59 +00:00
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
2024-05-19 21:02:17 +00:00
Channels . Remove ( channel ) ;
2023-02-10 06:07:59 +00:00
// 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.
2024-05-19 02:17:51 +00:00
foreach ( UserInfo user in GetChannelUsers ( channel ) )
2023-02-10 06:07:59 +00:00
SwitchChannel ( user , defaultChannel , string . Empty ) ;
// Broadcast deletion of channel
2024-05-19 21:02:17 +00:00
foreach ( UserInfo user in Users . GetMany ( minRank : channel . Rank ) )
2024-05-20 23:00:47 +00:00
SendTo ( user , new ChannelDeleteS2CPacket ( channel . Name ) ) ;
2022-08-30 15:00:58 +00:00
}
}
}