Friends List

Ignore List
Instant Messaging
This commit is contained in:
HikikoMarmy
2025-07-17 21:45:13 +01:00
parent 3696c6f36f
commit 0ddf027fa4
18 changed files with 586 additions and 64 deletions

View File

@@ -16,7 +16,7 @@ public:
bool operator==( const RealmUser &other ) const
{
return m_sessionId == other.m_sessionId || m_accountId == other.m_accountId;
return m_accountId == other.m_accountId || sock->fd == other.sock->fd;
}
bool operator<( const RealmUser &other ) const
@@ -26,18 +26,28 @@ public:
return m_accountId < other.m_accountId;
}
bool IsFriend( const std::wstring &handle ) const
{
return std::find( m_friendList.begin(), m_friendList.end(), handle ) != m_friendList.end();
}
bool IsIgnored( const std::wstring &handle ) const
{
return std::find( m_ignoreList.begin(), m_ignoreList.end(), handle ) != m_ignoreList.end();
}
public:
sptr_socket sock; // For Realm Lobby
sptr_socket sock; // For Realm Lobby
RealmGameType m_gameType; // Champions of Norrath or Return to Arms
int64_t m_accountId; // Unique ID of the account
std::wstring m_sessionId; // Temporary Session ID
std::wstring m_username; // Username of the user
std::wstring m_chatHandle;
RealmGameType m_gameType; // Champions of Norrath or Return to Arms
int64_t m_accountId; // Unique ID of the account
std::wstring m_sessionId; // Temporary Session ID
std::wstring m_username; // Username of the user
std::wstring m_chatHandle; // Chat handle for the user, used in chat rooms
bool m_isLoggedIn; // True if the user has successfully authenticated and logged in
bool m_isHost; // True if this user is the host of a realm
int32_t m_gameId; // Unique ID of the realm
bool m_isLoggedIn; // True if the user has successfully authenticated and logged in
bool m_isHost; // True if this user is the host of a realm
int32_t m_gameId; // Unique ID of the realm
int32_t m_publicRoomId; // Used for public chat rooms
int32_t m_privateRoomId; // Used for private chat rooms
@@ -48,7 +58,10 @@ public:
int32_t m_discoveryPort;
int32_t m_characterId;
sptr_realm_character m_character;
sptr_realm_character m_character;
std::vector< std::wstring > m_friendList; // List of friends for this user
std::vector< std::wstring > m_ignoreList; // List of ignored users
};
using sptr_user = std::shared_ptr< RealmUser >;

View File

@@ -3,8 +3,9 @@
#include "ChatRoomManager.h"
#include "../Network/Event/NotifyForcedLogout.h"
#include "../Common/Constant.h"
#include "../Network/Event/NotifyFriendStatus.h"
#include "../Database/Database.h"
#include "../Common/Constant.h"
#include "../logging.h"
UserManager::UserManager()
@@ -59,10 +60,11 @@ void UserManager::RemoveUser( sptr_user user )
return;
}
Database::Get().DeleteSession( user->m_sessionId );
GameSessionManager::Get().OnDisconnectUser( user );
ChatRoomManager::Get().OnDisconnectUser( user );
NotifyFriendsOnlineStatus( user, false );
Log::Debug( "RemoveUser : [{}][{}]", user->m_username, user->m_sessionId );
std::lock_guard< std::mutex > lock( m_mutex );
@@ -71,26 +73,18 @@ void UserManager::RemoveUser( sptr_user user )
void UserManager::RemoveUser( const std::wstring &sessionId )
{
auto user = FindUserBySessionId( sessionId );
if( !user )
if( auto user = FindUserBySessionId( sessionId ) )
{
Log::Error( "RemoveUser : [{}] not found", sessionId );
return;
RemoveUser( user );
}
RemoveUser( user );
}
void UserManager::RemoveUser( const sptr_socket socket )
{
auto user = FindUserBySocket( socket );
if( !user )
if( auto user = FindUserBySocket( socket ) )
{
Log::Error( "RemoveUser : [{}] not found", socket->remote_ip );
return;
RemoveUser( user );
}
RemoveUser( user );
}
void UserManager::Disconnect( sptr_socket socket, const std::string reason )
@@ -146,37 +140,14 @@ sptr_user UserManager::FindUserBySocket( const sptr_socket &socket )
return ( it != m_users.end() ) ? *it : nullptr;
}
sptr_user UserManager::RecoverUserBySession( const std::wstring &sessionId, const sptr_socket &socket )
sptr_user UserManager::FindUserByChatHandle( const std::wstring &handle )
{
auto user = FindUserBySocket( socket );
if( user == nullptr )
std::lock_guard<std::mutex> lock( m_mutex );
auto it = std::find_if( m_users.begin(), m_users.end(), [ & ]( const sptr_user &user )
{
Log::Error( "RecoverUserBySession: User not found for socket: {}", socket->remote_ip );
return nullptr;
}
if( sessionId.empty() )
{
Log::Error( "RecoverUserBySession: Empty session ID provided." );
return nullptr;
}
const auto [account_id, character_id] = Database::Get().GetSession( sessionId, socket->remote_ip );
if( account_id < 0 )
{
Log::Error( "RecoverUserBySession: Failed to get session for account ID: {}, session ID: {}", account_id, sessionId );
return nullptr;
}
user->m_accountId = account_id;
user->m_sessionId = sessionId;
user->m_character = Database::Get().LoadCharacterData( account_id, character_id );
Log::Debug( "RecoverUserBySession: User recovered with session ID: {}, account ID: {}", sessionId, account_id );
return user;
return user->m_chatHandle == handle;
} );
return ( it != m_users.end() ) ? *it : nullptr;
}
int32_t UserManager::GetUserCount() const
@@ -189,3 +160,21 @@ std::vector<sptr_user> UserManager::GetUserList()
std::lock_guard<std::mutex> lock( m_mutex );
return m_users;
}
void UserManager::NotifyFriendsOnlineStatus( const sptr_user &user, bool onlineStatus )
{
if( !user || user->m_friendList.empty() )
{
return;
}
const auto notifyFriend = NotifyFriendStatus( user->m_chatHandle, onlineStatus );
for( const auto &friendHandle : user->m_friendList )
{
auto friendUser = FindUserByChatHandle( friendHandle );
if( friendUser && friendUser->sock )
{
friendUser->sock->send( notifyFriend );
}
}
}

View File

@@ -31,13 +31,14 @@ public:
void Disconnect( sptr_socket socket, const std::string reason );
void Disconnect( sptr_user user, const std::string reason );
sptr_user FindUserBySessionId( const std::wstring &sessionId );
sptr_user FindUserBySocket( const sptr_socket &socket );
sptr_user RecoverUserBySession( const std::wstring &sessionId, const sptr_socket& socket );
sptr_user FindUserByChatHandle( const std::wstring &handle );
int32_t GetUserCount() const;
std::vector< sptr_user > GetUserList();
void NotifyFriendsOnlineStatus( const sptr_user &user, bool onlineStatus );
private:
std::mutex m_mutex;
std::vector< sptr_user > m_users;

View File

@@ -0,0 +1,16 @@
#include "NotifyFriendStatus.h"
NotifyFriendStatus::NotifyFriendStatus( std::wstring handle, bool status ) : GenericMessage( 0x2F )
{
m_handle = handle;
m_status = status;
}
void NotifyFriendStatus::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_utf16( m_handle );
out.write_u8( m_status ? 1 : 0 );
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "../GenericNetMessage.h"
class NotifyFriendStatus : public GenericMessage {
private:
std::wstring m_handle;
bool m_status;
public:
NotifyFriendStatus( std::wstring handle, bool status );
void Serialize( ByteBuffer &out ) const override;
};

View File

@@ -0,0 +1,16 @@
#include "NotifyInstantMessage.h"
NotifyInstantMessage::NotifyInstantMessage( std::wstring chatHandle, std::wstring message ) : GenericMessage( 0x30 )
{
m_chatHandle = chatHandle;
m_message = message;
}
void NotifyInstantMessage::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_utf16( m_chatHandle );
out.write_utf16( m_message );
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "../GenericNetMessage.h"
class NotifyInstantMessage : public GenericMessage {
private:
std::wstring m_message;
std::wstring m_chatHandle;
public:
NotifyInstantMessage( std::wstring chatHandle, std::wstring message );
void Serialize(ByteBuffer &out) const override;
};

View File

@@ -0,0 +1,61 @@
#include "RequestAddFriend.h"
#include "../../Game/RealmUserManager.h"
#include "../../Database/Database.h"
#include "../../logging.h"
void RequestAddFriend::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
}
sptr_generic_response RequestAddFriend::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultAddFriend >( this, FATAL_ERROR );
}
if( user->IsFriend( m_chatHandle ) )
{
return std::make_shared< ResultAddFriend >( this, FRIEND_DUPLICATE );
}
auto targetUser = UserManager::Get().FindUserByChatHandle( m_chatHandle );
if( targetUser == nullptr )
{
return std::make_shared< ResultAddFriend >( this, FRIEND_INVALID );
}
if( targetUser->IsIgnored( user->m_chatHandle ) )
{
return std::make_shared< ResultAddFriend >( this, FRIEND_IGNORING );
}
if( !Database::Get().SaveFriend( user->m_accountId, targetUser->m_chatHandle ) )
{
return std::make_shared< ResultAddFriend >( this, DATABASE_ERROR );
}
user->m_friendList.push_back( targetUser->m_chatHandle );
return std::make_shared< ResultAddFriend >( this, SUCCESS );
}
ResultAddFriend::ResultAddFriend( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultAddFriend::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <memory>
#include <string>
#include "../GenericNetRequest.h"
#include "../GenericNetResponse.h"
class RequestAddFriend : public GenericRequest
{
private:
std::wstring m_sessionId;
std::wstring m_chatHandle;
enum ERROR_CODE {
SUCCESS = 0,
FATAL_ERROR,
DATABASE_ERROR = 2,
FRIEND_IGNORING = 19,
FRIEND_INVALID = 20,
FRIEND_DUPLICATE = 21,
};
public:
static std::unique_ptr< RequestAddFriend > Create()
{
return std::make_unique< RequestAddFriend >();
}
sptr_generic_response ProcessRequest( sptr_socket socket, sptr_byte_stream stream ) override;
void Deserialize( sptr_byte_stream stream ) override;
};
class ResultAddFriend : public GenericResponse {
private:
int32_t m_reply;
public:
ResultAddFriend( GenericRequest *request, int32_t reply );
void Serialize( ByteBuffer &out ) const;
};

View File

@@ -0,0 +1,61 @@
#include "RequestAddIgnore.h"
#include "../../Game/RealmUserManager.h"
#include "../../Database/Database.h"
#include "../../logging.h"
void RequestAddIgnore::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
}
sptr_generic_response RequestAddIgnore::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultAddIgnore >( this, FATAL_ERROR );
}
if( user->IsFriend( m_chatHandle ) )
{
return std::make_shared< ResultAddIgnore >( this, IGNORE_FRIEND );
}
if( user->IsIgnored( m_chatHandle ) )
{
return std::make_shared< ResultAddIgnore >( this, IGNORE_DUPLICATE );
}
auto targetUser = UserManager::Get().FindUserByChatHandle( m_chatHandle );
if( targetUser == nullptr )
{
return std::make_shared< ResultAddIgnore >( this, IGNORE_INVALID );
}
if( !Database::Get().SaveIgnore( user->m_accountId, targetUser->m_chatHandle ) )
{
return std::make_shared< ResultAddIgnore >( this, DATABASE_ERROR );
}
user->m_ignoreList.push_back( targetUser->m_chatHandle );
return std::make_shared< ResultAddIgnore >( this, SUCCESS );
}
ResultAddIgnore::ResultAddIgnore( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultAddIgnore::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <memory>
#include <string>
#include "../GenericNetRequest.h"
#include "../GenericNetResponse.h"
class RequestAddIgnore : public GenericRequest
{
private:
std::wstring m_sessionId;
std::wstring m_chatHandle;
enum ERROR_CODE {
SUCCESS = 0,
FATAL_ERROR,
DATABASE_ERROR = 2,
IGNORE_INVALID = 20,
IGNORE_FRIEND = 24,
IGNORE_DUPLICATE = 25,
};
public:
static std::unique_ptr< RequestAddIgnore > Create()
{
return std::make_unique< RequestAddIgnore >();
}
sptr_generic_response ProcessRequest( sptr_socket socket, sptr_byte_stream stream ) override;
void Deserialize( sptr_byte_stream stream ) override;
};
class ResultAddIgnore : public GenericResponse {
private:
int32_t m_reply;
public:
ResultAddIgnore( GenericRequest *request, int32_t reply );
void Serialize( ByteBuffer &out ) const;
};

View File

@@ -0,0 +1,54 @@
#include "RequestGetSocialListInitial.h"
#include "../../Game/RealmUserManager.h"
#include "../../Game/RealmUser.h"
#include "../../logging.h"
void RequestGetSocialListInitial::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetSocialListInitial::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "RequestGetFriendList::ProcessRequest() - User not found" );
return std::make_shared< ResultGetSocialListInitial >( this );
}
return std::make_shared< ResultGetSocialListInitial >( this, user );
}
ResultGetSocialListInitial::ResultGetSocialListInitial( GenericRequest *request, sptr_user user ) : GenericResponse( *request )
{
m_user = user;
}
void ResultGetSocialListInitial::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
// Friends
out.write_u32( static_cast< uint32_t >( m_user->m_friendList.size() ) );
for( const auto &friendHandle : m_user->m_friendList )
{
out.write_utf16( friendHandle );
}
// ?
out.write_u32(1);
out.write_utf16(L"String_2");
// Ignore
out.write_u32( static_cast< uint32_t >( m_user->m_ignoreList.size() ) );
for( const auto &ignoreHandle : m_user->m_ignoreList )
{
out.write_utf16( ignoreHandle );
}
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <string>
#include "../GenericNetRequest.h"
#include "../GenericNetResponse.h"
#include "../../Common/ForwardDecl.h"
class RequestGetSocialListInitial : public GenericRequest
{
public:
static std::unique_ptr< RequestGetSocialListInitial > Create()
{
return std::make_unique< RequestGetSocialListInitial >();
}
sptr_generic_response ProcessRequest( sptr_socket socket, sptr_byte_stream stream ) override;
void Deserialize( sptr_byte_stream stream ) override;
};
class ResultGetSocialListInitial : public GenericResponse {
private:
int32_t m_reply;
sptr_user m_user;
public:
ResultGetSocialListInitial( GenericRequest *request, sptr_user user = nullptr );
void Serialize( ByteBuffer &out ) const;
};

View File

@@ -0,0 +1,50 @@
#include "RequestGetSocialListUpdate.h"
#include "../../Game/RealmUserManager.h"
#include "../../Game/RealmUser.h"
#include "../../logging.h"
void RequestGetSocialListUpdate::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetSocialListUpdate::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "RequestGetFriendList::ProcessRequest() - User not found" );
return std::make_shared< ResultGetSocialListUpdate >( this );
}
return std::make_shared< ResultGetSocialListUpdate >( this, user );
}
ResultGetSocialListUpdate::ResultGetSocialListUpdate( GenericRequest *request, sptr_user user ) : GenericResponse( *request )
{
m_user = user;
}
void ResultGetSocialListUpdate::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
// Friends
out.write_u32( static_cast< uint32_t >( m_user->m_friendList.size() ) );
for( const auto &friendHandle : m_user->m_friendList )
{
out.write_utf16( friendHandle );
}
// Ignore
out.write_u32( static_cast< uint32_t >( m_user->m_ignoreList.size() ) );
for( const auto &ignoreHandle : m_user->m_ignoreList )
{
out.write_utf16( ignoreHandle );
}
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <string>
#include "../GenericNetRequest.h"
#include "../GenericNetResponse.h"
#include "../../Common/ForwardDecl.h"
class RequestGetSocialListUpdate : public GenericRequest
{
public:
static std::unique_ptr< RequestGetSocialListUpdate > Create()
{
return std::make_unique< RequestGetSocialListUpdate >();
}
sptr_generic_response ProcessRequest( sptr_socket socket, sptr_byte_stream stream ) override;
void Deserialize( sptr_byte_stream stream ) override;
};
class ResultGetSocialListUpdate : public GenericResponse {
private:
int32_t m_reply;
sptr_user m_user;
public:
ResultGetSocialListUpdate( GenericRequest *request, sptr_user user = nullptr );
void Serialize( ByteBuffer &out ) const;
};

View File

@@ -42,7 +42,7 @@ sptr_generic_response RequestLogin::ProcessLoginRTA( sptr_user user )
auto &Database = Database::Get();
// Verify the account exists
auto accountId = Database.VerifyAccount( m_username, m_password );
auto [ result, accountId, chatHandle ] = Database.VerifyAccount( m_username, m_password );
if( accountId < 0 )
{
@@ -59,19 +59,21 @@ sptr_generic_response RequestLogin::ProcessLoginRTA( sptr_user user )
}
}
auto [result, chatHandle] = Database.LoadAccount( accountId );
// Login Success
user->m_isLoggedIn = true;
user->m_username = m_username;
user->m_accountId = accountId;
user->m_chatHandle = chatHandle;
user->m_sessionId = UserManager.GenerateSessionId();
Database.CreateSession(
user->m_accountId,
user->m_sessionId,
user->sock->remote_ip );
// Load Friend List
user->m_friendList = Database.LoadFriends( accountId );
// Load Ignore List
user->m_ignoreList = Database.LoadIgnores( accountId );
// Notify friends about the user's online status
UserManager.NotifyFriendsOnlineStatus( user, true );
return std::make_shared< ResultLogin >( this, SUCCESS, user->m_sessionId );
}

View File

@@ -0,0 +1,54 @@
#include "RequestSendInstantMessage.h"
#include "NotifyInstantMessage.h"
#include "../../Game/RealmUserManager.h"
#include "../../Game/ChatRoomManager.h"
#include "../../logging.h"
void RequestSendInstantMessage::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
m_message = stream->read_utf16();
}
sptr_generic_response RequestSendInstantMessage::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
const auto user = UserManager::Get().FindUserBySocket( socket );
if( !user )
{
Log::Error( "User not found for socket!" );
return std::make_shared< ResultSendInstantMessage >( this, GENERAL_ERROR );
}
auto targetUser = UserManager::Get().FindUserByChatHandle( m_chatHandle );
if( !targetUser )
{
return std::make_shared< ResultSendInstantMessage >( this, GENERAL_ERROR );
}
if( targetUser->IsIgnored( user->m_chatHandle ) )
{
return std::make_shared< ResultSendInstantMessage >( this, USER_IGNORED );
}
targetUser->sock->send( NotifyInstantMessage( user->m_chatHandle, m_message ) );
return std::make_shared< ResultSendInstantMessage >( this, SUCCESS );
}
ResultSendInstantMessage::ResultSendInstantMessage( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultSendInstantMessage::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <memory>
#include <string>
#include "../GenericNetRequest.h"
#include "../GenericNetResponse.h"
class RequestSendInstantMessage : public GenericRequest
{
private:
std::wstring m_chatHandle;
std::wstring m_message;
enum ERROR_CODE {
SUCCESS = 0,
GENERAL_ERROR,
USER_IGNORED = 19,
};
public:
static std::unique_ptr< RequestSendInstantMessage > Create()
{
return std::make_unique< RequestSendInstantMessage >();
}
sptr_generic_response ProcessRequest( sptr_socket socket, sptr_byte_stream stream ) override;
void Deserialize( sptr_byte_stream stream ) override;
};
class ResultSendInstantMessage : public GenericResponse {
private:
int32_t m_reply;
public:
ResultSendInstantMessage( GenericRequest *request, int32_t reply );
void Serialize( ByteBuffer &out ) const;
};