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-24 03:44:20 +00:00
public IEventStorage EventStorage { get ; }
public ChatEventDispatcher Events { get ; } = new ( ) ;
2024-05-24 19:17:12 +00:00
public ConnectionsContext Connections { get ; } = new ( ) ;
public ChannelsContext Channels { get ; } = new ( ) ;
public UsersContext Users { get ; } = new ( ) ;
public UserStatusContext UserStatuses { 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 ) ;
2024-05-24 19:17:12 +00:00
UserStatusInfo userStatusInfo = UserStatuses . Get ( info . SenderId ) ;
2024-05-24 03:44:20 +00:00
2024-05-24 16:01:11 +00:00
if ( ! string . IsNullOrWhiteSpace ( info . ChannelName ) )
ChannelsUsers . SetUserLastChannel ( info . SenderId , info . ChannelName ) ;
// TODO: should user:connect and user:disconnect be channel agnostic?
2024-05-24 03:44:20 +00:00
switch ( info . Type ) {
case "user:connect" :
SendTo ( info . ChannelName , new UserConnectS2CPacket (
info . Id ,
info . Created ,
info . SenderId ,
2024-05-24 19:17:12 +00:00
SockChatUtility . GetUserName ( info , userStatusInfo ) ,
2024-05-24 03:44:20 +00:00
info . SenderColour ,
info . SenderRank ,
info . SenderPerms
) ) ;
break ;
case "user:disconnect" :
2024-05-24 16:01:11 +00:00
if ( userInfo ! = null )
2024-05-24 19:17:12 +00:00
Events . Dispatch ( "user:status" , userInfo , new UserStatusUpdateEventData ( UserStatus . Offline ) ) ;
UserStatuses . Clear ( info . SenderId ) ;
2024-05-24 16:01:11 +00:00
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 ,
2024-05-24 19:17:12 +00:00
SockChatUtility . GetUserName ( info , userStatusInfo ) ,
2024-05-24 16:01:11 +00:00
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 ;
2024-05-24 19:17:12 +00:00
case "user:status" :
if ( info . Data is not UserStatusUpdateEventData userStatusUpdate )
break ;
if ( userStatusInfo . Status = = userStatusUpdate . Status
& & userStatusInfo . Text . Equals ( userStatusUpdate . Text ) )
break ;
userStatusInfo = UserStatuses . Set (
info . SenderId ,
userStatusUpdate . Status ,
userStatusUpdate . Text ? ? string . Empty
) ;
SendToUserChannels ( info . SenderId , new UserUpdateS2CPacket (
info . SenderId ,
SockChatUtility . GetUserName ( info , userStatusInfo ) ,
info . SenderColour ,
info . SenderRank ,
info . SenderPerms
) ) ;
break ;
case "user:update" :
if ( info . Data is not UserUpdateEventData userUpdate | | userInfo is null )
break ;
bool uuHasChanged = false ;
string? uuPrevName = null ;
if ( userUpdate . Name ! = null & & ! userUpdate . Name . Equals ( userInfo . UserName ) ) {
userInfo . UserName = userUpdate . Name ;
uuHasChanged = true ;
}
if ( userUpdate . NickName ! = null & & ! userUpdate . NickName . Equals ( userInfo . NickName ) ) {
if ( userUpdate . Notify )
uuPrevName = string . IsNullOrWhiteSpace ( userInfo . NickName ) ? userInfo . UserName : userInfo . NickName ;
userInfo . NickName = userUpdate . NickName ;
uuHasChanged = true ;
}
if ( userUpdate . Colour . HasValue & & userUpdate . Colour ! = userInfo . Colour . ToMisuzu ( ) ) {
userInfo . Colour = Colour . FromMisuzu ( userUpdate . Colour . Value ) ;
uuHasChanged = true ;
}
if ( userUpdate . Rank ! = null & & userUpdate . Rank ! = userInfo . Rank ) {
userInfo . Rank = userUpdate . Rank . Value ;
uuHasChanged = true ;
}
if ( userUpdate . Perms . HasValue & & userUpdate . Perms ! = userInfo . Permissions ) {
userInfo . Permissions = userUpdate . Perms . Value ;
uuHasChanged = true ;
}
if ( userUpdate . IsSuper . HasValue & & userUpdate . IsSuper ! = userInfo . IsSuper )
userInfo . IsSuper = userUpdate . IsSuper . Value ;
if ( uuHasChanged ) {
if ( uuPrevName ! = null )
SendToUserChannels ( info . SenderId , new UserNickChangeS2CPacket (
info . Id ,
info . Created ,
string . IsNullOrWhiteSpace ( info . SenderNickName ) ? uuPrevName : $"~{info.SenderNickName}" ,
SockChatUtility . GetUserName ( userInfo , userStatusInfo )
) ) ;
SendToUserChannels ( info . SenderId , new UserUpdateS2CPacket (
userInfo . UserId ,
SockChatUtility . GetUserName ( userInfo , userStatusInfo ) ,
userInfo . Colour ,
userInfo . Rank ,
userInfo . Permissions
) ) ;
}
break ;
2024-05-24 16:01:11 +00:00
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 )
) ) ;
2024-05-24 03:44:20 +00:00
break ;
case "chan:join" :
SendTo ( info . ChannelName , new UserChannelJoinS2CPacket (
info . SenderId ,
2024-05-24 19:17:12 +00:00
SockChatUtility . GetUserName ( info , userStatusInfo ) ,
2024-05-24 03:44:20 +00:00
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-24 16:01:11 +00:00
Events . Dispatch (
"user:disconnect" ,
ChannelsUsers . GetUserLastChannel ( user ) ,
user ,
new UserDisconnectEventData ( 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 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
}
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 ;
2024-05-24 19:17:12 +00:00
case "user:update" :
if ( info . Data is UserUpdateEventData userUpdate & & userUpdate . Notify )
handler ( new UserNickChangeLogS2CPacket (
info . Id ,
info . Created ,
info . SenderNickName = = null ? info . SenderName : $"~{info.SenderNickName}" ,
userUpdate . NickName = = null ? info . SenderName : $"~{userUpdate.NickName}"
) ) ;
break ;
2024-05-24 00:23:31 +00:00
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-24 19:17:12 +00:00
UserStatusInfo statusInfo = UserStatuses . Get ( user ) ;
2024-05-20 23:00:47 +00:00
conn . Send ( new AuthSuccessS2CPacket (
2024-05-10 18:29:48 +00:00
user . UserId ,
2024-05-24 19:17:12 +00:00
SockChatUtility . GetUserName ( user , statusInfo ) ,
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 ,
2024-05-24 19:17:12 +00:00
SockChatUtility . GetUserName ( user , statusInfo ) ,
2024-05-19 21:02:17 +00:00
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 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 ,
2024-05-24 19:17:12 +00:00
SockChatUtility . GetUserName ( user , UserStatuses . Get ( user ) ) ,
2024-05-19 21:02:17 +00:00
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-24 16:01:11 +00:00
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 = > {
2024-05-21 20:08:23 +00:00
if ( conn is SockChatConnectionInfo scConn )
2024-05-24 16:01:11 +00:00
scConn . Send ( packet ) ;
2024-05-21 20:08:23 +00:00
} ) ;
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-24 16:01:11 +00:00
SendTo ( channel . Name , packet . Pack ( ) ) ;
2024-05-20 16:24:14 +00:00
}
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-24 19:17:12 +00:00
SendToUserChannels ( user . UserId , packet ) ;
}
public void SendToUserChannels ( long userId , ISockChatS2CPacket packet ) {
ChannelInfo [ ] chans = Channels . GetMany ( ChannelsUsers . GetUserChannelNames ( userId ) ) ;
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
}
}
}