Files
UnrealEngine/Engine/Source/Runtime/Online/XMPP/Private/XmppStrophe/XmppMultiUserChatStrophe.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1383 lines
44 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "XmppStrophe/XmppMultiUserChatStrophe.h"
#include "XmppStrophe/XmppConnectionStrophe.h"
#include "XmppStrophe/XmppStrophe.h"
#include "XmppStrophe/StropheStanza.h"
#include "XmppStrophe/StropheStanzaConstants.h"
#include "XmppLog.h"
#include "Logging/LogScopedVerbosityOverride.h"
#include "Misc/Guid.h"
#include "Misc/EmbeddedCommunication.h"
#include "Containers/BackgroundableTicker.h"
#include "Stats/Stats.h"
#if WITH_XMPP_STROPHE
#define TickRequesterId FName("StropheMultichat")
FXmppMultiUserChatStrophe::FXmppMultiUserChatStrophe(FXmppConnectionStrophe& InConnectionManager)
: FTSTickerObjectBase(0.0f, FTSBackgroundableTicker::GetCoreTicker())
, ConnectionManager(InConnectionManager)
{
}
FXmppMultiUserChatStrophe::~FXmppMultiUserChatStrophe()
{
CleanupMessages();
}
void FXmppMultiUserChatStrophe::OnDisconnect()
{
CleanupMessages();
Chatrooms.Empty();
PendingRoomCreateConfigs.Empty();
}
void FXmppMultiUserChatStrophe::OnReconnect()
{
}
bool FXmppMultiUserChatStrophe::ReceiveStanza(const FStropheStanza& IncomingStanza)
{
// MUC presence are from our MUC domain
const FString StanzaName = IncomingStanza.GetName();
if (StanzaName == Strophe::SN_PRESENCE &&
IncomingStanza.GetFrom().Domain.Equals(ConnectionManager.GetMucDomain(), ESearchCase::CaseSensitive))
{
if (IncomingStanza.GetType() != Strophe::ST_ERROR)
{
return HandlePresenceStanza(IncomingStanza);
}
else
{
return HandlePresenceErrorStanza(IncomingStanza);
}
}
else if (StanzaName == Strophe::SN_MESSAGE)
{
const FString StanzaType = IncomingStanza.GetType();
if (StanzaType == Strophe::ST_GROUPCHAT)
{
return HandleGroupChatStanza(IncomingStanza);
}
else if (StanzaType == Strophe::ST_ERROR)
{
return HandleGroupChatErrorStanza(IncomingStanza);
}
}
else if (StanzaName == Strophe::SN_IQ)
{
// Ignore pings
if (IncomingStanza.HasChildByNameAndNamespace(Strophe::SN_PING, Strophe::SNS_PING))
{
return false;
}
// Config sets/gets (what we're caring about here) don't have queries in the "muc owner" namespace, so filter out those who do
TOptional<const FStropheStanza> QueryStanza = IncomingStanza.GetChildByNameAndNamespace(Strophe::SN_QUERY, Strophe::SNS_MUC_OWNER);
if (!QueryStanza.IsSet())
{
const FString StanzaType = IncomingStanza.GetType();
if (StanzaType == Strophe::ST_RESULT)
{
// If this is a result type, check the FromJid for JUST a domain (i.e. the server), and drop it if it is
// These are replies to pings (pongs), and until we keep track of what IDs we use for sent-pings, this filter
// method will have to do
const FXmppUserJid FromUser(IncomingStanza.GetFrom());
const bool bMessageFromOnlyDomain = FromUser.Id.IsEmpty() && !FromUser.Domain.IsEmpty() && FromUser.Resource.IsEmpty();
if (bMessageFromOnlyDomain)
{
// Ignore pongs
return false;
}
}
if (StanzaType != Strophe::ST_ERROR)
{
return HandleRoomConfigStanza(IncomingStanza);
}
else
{
return HandleRoomConfigErrorStanza(IncomingStanza);
}
}
}
return false;
}
bool FXmppMultiUserChatStrophe::HandlePresenceStanza(const FStropheStanza& IncomingStanza)
{
FXmppMucPresence Presence;
Presence.bIsAvailable = IncomingStanza.GetType() != Strophe::ST_UNAVAILABLE;
Presence.UserJid = IncomingStanza.GetFrom();
TOptional<const FStropheStanza> UserStanza = IncomingStanza.GetChildByNameAndNamespace(Strophe::SN_X, Strophe::SNS_MUC_USER);
if (UserStanza.IsSet())
{
TOptional<const FStropheStanza> UserItemStanza = UserStanza->GetChildStropheStanza(Strophe::SN_ITEM);
if (UserItemStanza.IsSet())
{
Presence.MemberJid = FXmppStrophe::JidFromString(UserItemStanza->GetAttribute(Strophe::SA_JID));
Presence.Role = UserItemStanza->GetAttribute(Strophe::SA_ROLE);
Presence.Affiliation = UserItemStanza->GetAttribute(Strophe::SA_AFFILIATION);
}
}
FEmbeddedCommunication::KeepAwake(TickRequesterId, false);
IncomingMucPresenceUpdates.Enqueue(MakeUnique<FXmppMucPresence>(MoveTemp(Presence)));
return true;
}
bool FXmppMultiUserChatStrophe::HandlePresenceErrorStanza(const FStropheStanza& IncomingStanza)
{
FXmppStropheErrorPair OutError;
OutError.RoomId = IncomingStanza.GetFrom().Id;
TOptional<const FStropheStanza> Error = IncomingStanza.GetChildStropheStanza(Strophe::SN_ERROR);
if (Error.IsSet())
{
const TArray<FStropheStanza> ErrorList = Error->GetChildren();
for (const FStropheStanza& ErrorItem : ErrorList)
{
const FString ErrorName = ErrorItem.GetName();
if (ErrorName == Strophe::SN_NOT_AUTHORIZED)
{
OutError.ErrorMessage += TEXT("A password is required to join this room");
}
else if (ErrorName == Strophe::SN_FORBIDDEN)
{
OutError.ErrorMessage += TEXT("You are not allowed to join this room");
}
else if (ErrorName == Strophe::SN_ITEM_NOT_FOUND)
{
OutError.ErrorMessage += TEXT("That room does not exist");
}
else if (ErrorName == Strophe::SN_NOT_ALLOWED)
{
OutError.ErrorMessage += TEXT("You are unable to create rooms");
}
else if (ErrorName == Strophe::SN_NOT_ACCEPTABLE)
{
OutError.ErrorMessage += TEXT("You may not change your nickname");
}
else if (ErrorName == Strophe::SN_REGISTRATION_REQUIRED)
{
OutError.ErrorMessage += TEXT("You are not a member of this room");
}
else if (ErrorName == Strophe::SN_CONFLICT)
{
OutError.ErrorMessage += TEXT("Your nickname is already in use in this room");
}
else if (ErrorName == Strophe::SN_SERVICE_UNAVAILABLE)
{
OutError.ErrorMessage += TEXT("The requested room is full");
}
else
{
OutError.ErrorMessage += FString::Printf(TEXT("Unknown Error %s. "), *ErrorName);
}
}
}
if (OutError.ErrorMessage.IsEmpty())
{
OutError.ErrorMessage = TEXT("Unknown error");
}
UE_LOG(LogXmpp, Warning, TEXT("MUC: Received error %s"), *OutError.ErrorMessage);
if (!OutError.ErrorMessage.IsEmpty())
{
FEmbeddedCommunication::KeepAwake(TickRequesterId, false);
IncomingMucPresenceErrors.Enqueue(MoveTemp(OutError));
}
return true;
}
bool FXmppMultiUserChatStrophe::HandleGroupChatStanza(const FStropheStanza& IncomingStanza)
{
TOptional<const FStropheStanza> SubjectStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_SUBJECT);
if (SubjectStanza.IsSet())
{
FXmppStropheSubjectUpdate SubjectUpdate;
SubjectUpdate.NewSubject = SubjectStanza->GetText();
SubjectUpdate.RoomId = IncomingStanza.GetFrom().Id;
FEmbeddedCommunication::KeepAwake(TickRequesterId, false);
IncomingRoomSubjects.Enqueue(MoveTemp(SubjectUpdate));
return true;
}
// Check for room settings update (status code 104)
TOptional<const FStropheStanza> XStanza = IncomingStanza.GetChildByNameAndNamespace(Strophe::SN_X, Strophe::SNS_MUC_USER);
if (XStanza.IsSet())
{
// We're looking for exactly 1 'status' child
const TArray<FStropheStanza> XChildren = XStanza->GetChildren();
if (XChildren.Num() == 1)
{
const FStropheStanza& XChild = XChildren[0];
if (XChild.GetName() == Strophe::SN_STATUS)
{
FString Code = XChild.GetAttribute(Strophe::SA_CODE);
if (Code == Strophe::SC_104)
{
FEmbeddedCommunication::KeepAwake(TickRequesterId, false);
IncomingRoomInfoUpdates.Enqueue(FXmppRoomId(IncomingStanza.GetFrom().Id));
}
}
}
return true;
}
TOptional<FString> BodyText = IncomingStanza.GetBodyText();
if (!BodyText.IsSet())
{
// Bad data, no body
return true;
}
FXmppChatMessage ChatMessage;
ChatMessage.ToJid = IncomingStanza.GetTo();
ChatMessage.FromJid = IncomingStanza.GetFrom();
ChatMessage.Body = MoveTemp(BodyText.GetValue());
// Parse Timezone
TOptional<const FStropheStanza> StanzaDelay = IncomingStanza.GetChildStropheStanza(Strophe::SN_DELAY);
if (StanzaDelay.IsSet())
{
if (StanzaDelay->HasAttribute(Strophe::SA_STAMP))
{
FString Timestamp = StanzaDelay->GetAttribute(Strophe::SA_STAMP);
FDateTime::ParseIso8601(*Timestamp, ChatMessage.Timestamp);
}
}
if (ChatMessage.Timestamp == 0)
{
ChatMessage.Timestamp = FDateTime::UtcNow();
}
FEmbeddedCommunication::KeepAwake(TickRequesterId, false);
IncomingGroupChatMessages.Enqueue(MakeUnique<FXmppChatMessage>(MoveTemp(ChatMessage)));
return true;
}
bool FXmppMultiUserChatStrophe::HandleGroupChatErrorStanza(const FStropheStanza& IncomingStanza)
{
FString ErrorMessage;
TOptional<const FStropheStanza> Error = IncomingStanza.GetChildStropheStanza(Strophe::SN_ERROR);
if (Error.IsSet())
{
const TArray<FStropheStanza> ErrorList = Error->GetChildren();
for (const FStropheStanza& ErrorItem : ErrorList)
{
const FString ErrorName = ErrorItem.GetName();
if (ErrorName == Strophe::SN_FORBIDDEN)
{
ErrorMessage += TEXT("Unable to send message to room. ");
}
else if (ErrorName == Strophe::SN_BAD_REQUEST)
{
ErrorMessage += TEXT("Unable to send groupchat message to an individual. ");
}
else if (ErrorName == Strophe::SN_NOT_ACCEPTABLE)
{
ErrorMessage += TEXT("You may not send messages to rooms you have not joined. ");
}
else
{
ErrorMessage += FString::Printf(TEXT("%s. "), *ErrorName);
}
}
}
if (ErrorMessage.IsEmpty())
{
ErrorMessage = TEXT("Unknown error");
}
UE_LOG(LogXmpp, Warning, TEXT("MUC: Received GroupChat error %s"), *ErrorMessage);
return true;
}
bool FXmppMultiUserChatStrophe::HandleRoomConfigStanza(const FStropheStanza& IncomingStanza)
{
// There are four possible outputs from this that mean success:
// a) No children in iq stanza; this means we successfully set the configuration
// for the channel!
// b) The iq stanza has a query stanza with no children; this means we requested
// the config options or the config option defaults and there are none
// c) The query stanza has children, but those childen have no value children;
// this means we got the list of possible config options
// d) The query stanza has children and those children have value stanzas; this
// means the channel already exists and we're querying the options for it
// Check for config write case (No Query child)
TOptional<const FStropheStanza> QueryStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_QUERY);
if (!QueryStanza.IsSet())
{
FEmbeddedCommunication::KeepAwake(TickRequesterId, false);
IncomingRoomConfigWriteSuccesses.Enqueue(IncomingStanza.GetFrom().Id);
return true;
}
// Right now we only care about the successful write case, but you could totally
// write a config parser here and pass back the config values for a room to the
// game thread if you wanted.
return true;
}
bool FXmppMultiUserChatStrophe::HandleRoomConfigErrorStanza(const FStropheStanza& IncomingStanza)
{
FXmppStropheErrorPair OutError;
OutError.RoomId = IncomingStanza.GetFrom().Id;
TOptional<const FStropheStanza> ErrorStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_ERROR);
if (ErrorStanza.IsSet())
{
const FString ErrorType = ErrorStanza->GetType();
if (ErrorType == Strophe::ST_AUTH && ErrorStanza->HasChild(Strophe::SN_FORBIDDEN))
{
OutError.ErrorMessage = TEXT("Only the room owner may modify the room configuration");
}
// Don't log the error message, as we may not care about failures depending on the CallbackType
}
if (!OutError.ErrorMessage.IsEmpty())
{
FEmbeddedCommunication::KeepAwake(TickRequesterId, false);
IncomingRoomConfigErrors.Enqueue(MoveTemp(OutError));
}
return true;
}
bool FXmppMultiUserChatStrophe::CreateRoom(const FXmppRoomId& RoomId, const FString& Nickname, const FXmppRoomConfig& RoomConfig)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: CreateRoom=%s Nickname=%s"), *RoomId, *Nickname);
bool bSuccess = false;
FString ErrorStr;
if (RoomId.IsEmpty())
{
ErrorStr = TEXT("Room ID Invalid");
}
else if (Nickname.IsEmpty())
{
ErrorStr = TEXT("Nickname is Invalid");
}
else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn)
{
ErrorStr = TEXT("Not currently connected");
}
else
{
FXmppRoomStrophe& Room = Chatrooms.FindOrAdd(RoomId);
// Set the Room's ID if we just created it
if (Room.Info.Id.IsEmpty() || !Room.RoomJid.IsValid())
{
Room.RoomJid = FXmppUserJid(RoomId, ConnectionManager.GetMucDomain(), Nickname);
Room.Info.Id = Room.RoomJid.Id;
}
if (Room.Status == ERoomStatusStrophe::Joined)
{
ErrorStr = FString::Printf(TEXT("Already in room %s"), *RoomId);
}
else if (Room.Status != ERoomStatusStrophe::NotJoined)
{
ErrorStr = FString::Printf(TEXT("Another operation already pending for room %s"), *RoomId);
}
else
{
Room.Status = ERoomStatusStrophe::CreatePending;
// cache off the config for use after the room is created & ready to be configured
PendingRoomCreateConfigs.Emplace(RoomId, RoomConfig);
bSuccess = SendJoinRoomStanza(Room);
}
}
if (!bSuccess)
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: CreateRoom failed. %s"), *ErrorStr);
OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr);
}
return bSuccess;
}
bool FXmppMultiUserChatStrophe::ConfigureRoom(const FXmppRoomId& RoomId, const FXmppRoomConfig& RoomConfig)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: ConfigureRoom RoomId=%s"), *RoomId);
bool bSuccess = false;
FString ErrorStr;
if (RoomId.IsEmpty())
{
ErrorStr = TEXT("Room ID Invalid");
}
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr == nullptr)
{
ErrorStr = FString::Printf(TEXT("Could not find room %s"), *RoomId);
}
else if (RoomPtr->Status != ERoomStatusStrophe::Joined)
{
ErrorStr = FString::Printf(TEXT("You must be in room %s to configure it."), *RoomId);
}
else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn)
{
ErrorStr = TEXT("You are not currently connected to the server");
}
else if (RoomPtr->Info.OwnerId != RoomPtr->GetNickname())
{
ErrorStr = FString::Printf(TEXT("You must be the owner of room %s to configure it. The current owner is %s"), *RoomId, *RoomPtr->Info.OwnerId);
}
else
{
bSuccess = InternalConfigureRoom(*RoomPtr, RoomConfig, EConfigureRoomTypeStrophe::UseConfigCallback);
if (!bSuccess)
{
ErrorStr = TEXT("Failed to configure room");
}
}
if (!bSuccess)
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: Failed to configure Room=%s Error=%s"), *RoomId, *ErrorStr);
OnXmppRoomConfiguredDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr);
}
return bSuccess;
}
bool FXmppMultiUserChatStrophe::RefreshRoomInfo(const FXmppRoomId& RoomId)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: RefreshRoomInfo RoomId=%s"), *RoomId);
// This just prints a bunch of info to the console in our libjingle module, so we're going to
// skip writing a bunch of code that doesn't do anything and just call our delegate instead
if (Chatrooms.Find(RoomId) != nullptr)
{
OnXmppRoomInfoRefreshedDelegate.Broadcast(ConnectionManager.AsShared(), true, RoomId, FString());
return true;
}
else
{
OnXmppRoomInfoRefreshedDelegate.Broadcast(ConnectionManager.AsShared(), false, RoomId, TEXT("Room does not exist"));
return false;
}
}
bool FXmppMultiUserChatStrophe::JoinPublicRoom(const FXmppRoomId& RoomId, const FString& Nickname)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: JoinPublicRoom RoomId=%s Nickname=%s"), *RoomId, *Nickname);
bool bSuccess = false;
FString ErrorStr;
if (RoomId.IsEmpty())
{
ErrorStr = TEXT("Room ID Invalid");
}
else if (Nickname.IsEmpty())
{
ErrorStr = TEXT("Nickname is Invalid");
}
else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn)
{
ErrorStr = TEXT("Not currently connected");
}
else
{
FXmppRoomStrophe& Room = Chatrooms.FindOrAdd(RoomId);
// Set the Room's ID if we just created it
if (Room.Info.Id.IsEmpty() || !Room.RoomJid.IsValid())
{
Room.RoomJid = FXmppUserJid(RoomId, ConnectionManager.GetMucDomain(), Nickname);
Room.Info.Id = Room.RoomJid.Id;
}
if (Room.Status == ERoomStatusStrophe::Joined)
{
ErrorStr = FString::Printf(TEXT("Already in room %s"), *RoomId);
}
else if (Room.Status != ERoomStatusStrophe::NotJoined)
{
ErrorStr = FString::Printf(TEXT("Another operation already pending for room %s"), *RoomId);
}
else
{
Room.Status = ERoomStatusStrophe::JoinPublicPending;
bSuccess = SendJoinRoomStanza(Room);
}
}
if (!bSuccess)
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: JoinPublicRoom failed. %s"), *ErrorStr);
OnXmppRoomJoinPublicCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr);
}
return bSuccess;
}
bool FXmppMultiUserChatStrophe::JoinPrivateRoom(const FXmppRoomId& RoomId, const FString& Nickname, const FString& Password)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: JoinPrivateRoom RoomId=%s Nickname=%s"), *RoomId, *Nickname);
bool bSuccess = false;
FString ErrorStr;
if (RoomId.IsEmpty())
{
ErrorStr = TEXT("Room ID Invalid");
}
else if (Nickname.IsEmpty())
{
ErrorStr = TEXT("Nickname is Invalid");
}
else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn)
{
ErrorStr = TEXT("Not currently connected");
}
else
{
FXmppRoomStrophe& Room = Chatrooms.FindOrAdd(RoomId);
// Set the Room's ID if we just created it
if (Room.Info.Id.IsEmpty() || !Room.RoomJid.IsValid())
{
Room.RoomJid = FXmppUserJid(RoomId, ConnectionManager.GetMucDomain(), Nickname);
Room.Info.Id = Room.RoomJid.Id;
}
if (Room.Status == ERoomStatusStrophe::Joined)
{
ErrorStr = FString::Printf(TEXT("Already in room %s"), *RoomId);
}
else if (Room.Status != ERoomStatusStrophe::NotJoined)
{
ErrorStr = FString::Printf(TEXT("Another operation already pending for room %s"), *RoomId);
}
else
{
Room.Status = ERoomStatusStrophe::JoinPrivatePending;
bSuccess = SendJoinRoomStanza(Room, Password);
}
}
if (!bSuccess)
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: JoinPrivateRoom failed. %s"), *ErrorStr);
// trigger delegates on error
OnXmppRoomJoinPrivateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr);
}
return bSuccess;
}
bool FXmppMultiUserChatStrophe::RegisterMember(const FXmppRoomId& RoomId, const FString& Nickname)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: RegisterMember RoomId=%s Nickname=%s"), *RoomId, *Nickname);
// No-op currently in libjingle version
return false;
}
bool FXmppMultiUserChatStrophe::UnregisterMember(const FXmppRoomId& RoomId, const FString& Nickname)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: UnregisterMember RoomId=%s Nickname=%s"), *RoomId, *Nickname);
// No-op currently in libjingle version
return false;
}
bool FXmppMultiUserChatStrophe::ExitRoom(const FXmppRoomId& RoomId)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: ExitRoom RoomId=%s"), *RoomId);
bool bSuccess = false;
FString ErrorStr;
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn)
{
// Not logged in, so cannot leave the chat room
ErrorStr = TEXT("User not logged in");
bSuccess = false;
}
else if (RoomPtr == nullptr)
{
ErrorStr = TEXT("Room was never created.");
bSuccess = true; // Returning true since we can't be in the room at all.
}
else
{
switch (RoomPtr->Status)
{
case ERoomStatusStrophe::ExitPending:
{
// We've already queued an exit, so do not queue again or trigger delegates
bSuccess = true;
}
break;
case ERoomStatusStrophe::Joined:
{
ERoomStatusStrophe OldStatus = RoomPtr->Status;
// Queue our exit
RoomPtr->Status = ERoomStatusStrophe::ExitPending;
bSuccess = SendExitRoomStanza(*RoomPtr);
if (!bSuccess)
{
ErrorStr = TEXT("Could not send ExitRoom stanza");
RoomPtr->Status = OldStatus; // Restore old room status if we could not queue the exit operation
}
}
break;
case ERoomStatusStrophe::NotJoined:
case ERoomStatusStrophe::CreatePending:
case ERoomStatusStrophe::JoinPublicPending:
case ERoomStatusStrophe::JoinPrivatePending:
default:
{
// We're (probably) not in this room, so we don't need to exit
ErrorStr = FString::Printf(TEXT("User not in room: status is %s"), LexToString(RoomPtr->Status));
bSuccess = false;
}
break;
}
}
if (!bSuccess || RoomPtr == nullptr)
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: Cannot leave room %s: %s"), *RoomId, *ErrorStr);
OnXmppRoomExitCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr);
}
return bSuccess;
}
bool FXmppMultiUserChatStrophe::SendChat(const FXmppRoomId& RoomId, const FString& MsgBody, const FString& ChatInfo)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: SendChat RoomId=%s"), *RoomId);
if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn)
{
return false;
}
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr == nullptr)
{
return false;
}
FStropheStanza MessageStanza(ConnectionManager, Strophe::SN_MESSAGE);
{
MessageStanza.SetId(FGuid::NewGuid().ToString());
MessageStanza.SetType(Strophe::ST_GROUPCHAT);
MessageStanza.SetTo(RoomPtr->GetRoomJid().GetBareId());
MessageStanza.AddBodyWithText(MsgBody);
}
return ConnectionManager.SendStanza(MoveTemp(MessageStanza));
}
void FXmppMultiUserChatStrophe::GetJoinedRooms(TArray<FXmppRoomId>& OutRooms)
{
OutRooms.Empty(Chatrooms.Num());
for (const TMap<FXmppRoomId, FXmppRoomStrophe>::ElementType& Pair : Chatrooms)
{
OutRooms.Emplace(Pair.Key);
}
}
bool FXmppMultiUserChatStrophe::GetRoomInfo(const FXmppRoomId& RoomId, FXmppRoomInfo& OutRoomInfo)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr != nullptr)
{
OutRoomInfo = RoomPtr->Info;
return true;
}
return false;
}
bool FXmppMultiUserChatStrophe::GetMembers(const FXmppRoomId& RoomId, TArray<FXmppChatMemberRef>& OutMembers)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr != nullptr)
{
OutMembers = RoomPtr->Members;
return true;
}
return false;
}
FXmppChatMemberPtr FXmppMultiUserChatStrophe::GetMember(const FXmppRoomId& RoomId, const FXmppUserJid& MemberJid)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr != nullptr)
{
FXmppChatMemberPtr FoundMember = RoomPtr->GetMember(MemberJid);
return FoundMember;
}
return nullptr;
}
bool FXmppMultiUserChatStrophe::GetLastMessages(const FXmppRoomId& RoomId, int32 NumMessages, TArray< TSharedRef<FXmppChatMessage> >& OutMessages)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr != nullptr)
{
const int32 MessagesToFetch = FMath::Max(FMath::Min(NumMessages, RoomPtr->LastMessages.Num()), 0);
OutMessages.Empty(MessagesToFetch);
for (int32 Index = 0; Index < MessagesToFetch; ++Index)
{
check(RoomPtr->LastMessages.IsValidIndex(Index));
OutMessages.Emplace(RoomPtr->LastMessages[Index]);
}
return true;
}
OutMessages.Empty();
return false;
}
void FXmppMultiUserChatStrophe::HandleMucPresence(const FXmppMucPresence& MemberPresence)
{
// We don't use this, but it's built into the interface for reasons?
}
void FXmppMultiUserChatStrophe::DumpMultiUserChatState() const
{
LOG_SCOPE_VERBOSITY_OVERRIDE(LogXmpp, ELogVerbosity::Display);
for (const TMap<FXmppRoomId, FXmppRoomStrophe>::ElementType& Room : Chatrooms)
{
const FXmppRoomId& RoomId = Room.Key;
const FXmppRoomStrophe& XmppRoom = Room.Value;
UE_LOG(LogXmpp, Display, TEXT("RoomId: %s"), *RoomId);
UE_LOG(LogXmpp, Display, TEXT(" Owner: %s Subj: %s Priv: %d"), *XmppRoom.Info.OwnerId, *XmppRoom.Info.Subject, XmppRoom.Info.bIsPrivate);
UE_LOG(LogXmpp, Display, TEXT(" Status: %d"), (int32)XmppRoom.Status);
UE_LOG(LogXmpp, Display, TEXT(" Members: %d"), XmppRoom.Members.Num());
for (const FXmppChatMemberRef& Member : XmppRoom.Members)
{
UE_LOG(LogXmpp, Display, TEXT(" %s"), *Member->ToDebugString());
}
}
}
bool FXmppMultiUserChatStrophe::Tick(float DeltaTime)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FXmppMultiUserChatStrophe_Tick);
while (!IncomingMucPresenceUpdates.IsEmpty())
{
TUniquePtr<FXmppMucPresence> MucPresence;
if (IncomingMucPresenceUpdates.Dequeue(MucPresence))
{
FEmbeddedCommunication::AllowSleep(TickRequesterId);
check(MucPresence.IsValid());
OnReceiveMucPresence(MoveTemp(*MucPresence));
}
}
while (!IncomingMucPresenceErrors.IsEmpty())
{
FXmppStropheErrorPair ErrorInfo;
if (IncomingMucPresenceErrors.Dequeue(ErrorInfo))
{
FEmbeddedCommunication::AllowSleep(TickRequesterId);
OnReceiveMucPresenceError(MoveTemp(ErrorInfo));
}
}
while (!IncomingGroupChatMessages.IsEmpty())
{
TUniquePtr<FXmppChatMessage> GroupChatMessage;
if (IncomingGroupChatMessages.Dequeue(GroupChatMessage))
{
FEmbeddedCommunication::AllowSleep(TickRequesterId);
OnReceiveGroupChatMessage(MoveTemp(GroupChatMessage));
}
}
while (!IncomingRoomSubjects.IsEmpty())
{
FXmppStropheSubjectUpdate NewSubject;
if (IncomingRoomSubjects.Dequeue(NewSubject))
{
FEmbeddedCommunication::AllowSleep(TickRequesterId);
OnReceiveGroupChatSubject(MoveTemp(NewSubject));
}
}
while (!IncomingRoomConfigErrors.IsEmpty())
{
FXmppStropheErrorPair RoomConfigError;
if (IncomingRoomConfigErrors.Dequeue(RoomConfigError))
{
FEmbeddedCommunication::AllowSleep(TickRequesterId);
OnReceiveRoomConfigError(MoveTemp(RoomConfigError));
}
}
while (!IncomingRoomConfigWriteSuccesses.IsEmpty())
{
FXmppRoomId RoomId;
if (IncomingRoomConfigWriteSuccesses.Dequeue(RoomId))
{
FEmbeddedCommunication::AllowSleep(TickRequesterId);
OnReceiveRoomConfigSuccess(MoveTemp(RoomId));
}
}
while (!IncomingRoomInfoUpdates.IsEmpty())
{
FXmppRoomId RoomId;
if (IncomingRoomInfoUpdates.Dequeue(RoomId))
{
FEmbeddedCommunication::AllowSleep(TickRequesterId);
OnReceieveRoomInfoUpdate(MoveTemp(RoomId));
}
}
return true;
}
void FXmppMultiUserChatStrophe::OnReceiveMucPresence(FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, VeryVerbose, TEXT("MUC: OnReceiveMucPresence: jid=%s nick=%s roomid=%s role=%s affiliation=%s"), *MemberPresence.UserJid.ToDebugString(), *MemberPresence.GetNickName(), *MemberPresence.GetRoomId(), *MemberPresence.Role, *MemberPresence.Affiliation);
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(MemberPresence.GetRoomId());
if (RoomPtr != nullptr)
{
const bool bUpdateIsUs = MemberPresence.GetNickName().Contains(ConnectionManager.GetUserJid().Id, ESearchCase::CaseSensitive);
const bool bLeftRoom = !MemberPresence.bIsAvailable;
const bool bCreateRoom = bUpdateIsUs && !bLeftRoom && RoomPtr->Status == ERoomStatusStrophe::CreatePending;
const bool bJoinPublicRoom = bUpdateIsUs && !bLeftRoom && RoomPtr->Status == ERoomStatusStrophe::JoinPublicPending;
const bool bJoinPrivateRoom = bUpdateIsUs && !bLeftRoom && RoomPtr->Status == ERoomStatusStrophe::JoinPrivatePending;
const bool bAlreadyInRoom = RoomPtr->HasMember(MemberPresence.UserJid);
if (bUpdateIsUs)
{
// This presence update is us doing something
if (bLeftRoom)
{
HandleExitRoomComplete(*RoomPtr, MoveTemp(MemberPresence));
}
else if (bCreateRoom)
{
HandleCreateRoomComplete(*RoomPtr, MoveTemp(MemberPresence));
}
else if (bJoinPrivateRoom)
{
HandleJoinPrivateRoomComplete(*RoomPtr, MoveTemp(MemberPresence));
}
else if (bJoinPublicRoom)
{
HandleJoinPublicRoomComplete(*RoomPtr, MoveTemp(MemberPresence));
}
else
{
ensureMsgf(false, TEXT("Unknown libstrope Presence Self-State update"));
}
}
// Other users
else
{
if (!bAlreadyInRoom && !bLeftRoom)
{
// Anyone we didn't know about, that isn't leaving, is joining
HandleRoomMemberJoined(*RoomPtr, MoveTemp(MemberPresence));
}
else if (bLeftRoom)
{
HandleRoomMemberLeft(*RoomPtr, MoveTemp(MemberPresence));
}
else
{
HandleRoomMemberChanged(*RoomPtr, MoveTemp(MemberPresence));
}
}
}
else
{
UE_LOG(LogXmpp, VeryVerbose, TEXT("MUC: OnReceiveMucPresence Ignored presence from room we haven't joined: Room=%s Connjid=%s"), *MemberPresence.GetRoomId(), *ConnectionManager.GetUserJid().Id);
}
}
void FXmppMultiUserChatStrophe::OnReceiveMucPresenceError(FXmppStropheErrorPair&& PresenceError)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(PresenceError.RoomId);
if (RoomPtr != nullptr)
{
switch (RoomPtr->Status)
{
case ERoomStatusStrophe::CreatePending:
Chatrooms.Remove(PresenceError.RoomId);
OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, PresenceError.RoomId, PresenceError.ErrorMessage);
break;
case ERoomStatusStrophe::JoinPrivatePending:
Chatrooms.Remove(PresenceError.RoomId);
OnXmppRoomJoinPrivateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, PresenceError.RoomId, PresenceError.ErrorMessage);
break;
case ERoomStatusStrophe::JoinPublicPending:
Chatrooms.Remove(PresenceError.RoomId);
OnXmppRoomJoinPublicCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, PresenceError.RoomId, PresenceError.ErrorMessage);
break;
case ERoomStatusStrophe::ExitPending:
case ERoomStatusStrophe::NotJoined:
case ERoomStatusStrophe::Joined:
// No-Op?
break;
}
}
else
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveMucPresenceError Received Error from room we haven't joined: Room=%s Connjid=%s Error=%s"), *PresenceError.RoomId, *ConnectionManager.GetUserJid().Id, *PresenceError.ErrorMessage);
}
}
void FXmppMultiUserChatStrophe::OnReceiveGroupChatMessage(TUniquePtr<FXmppChatMessage>&& GroupChatMessage)
{
check(GroupChatMessage.IsValid());
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(GroupChatMessage->FromJid.Id);
if (RoomPtr != nullptr)
{
TSharedRef<FXmppChatMessage> SharedChatMessageRef = MakeShareable(GroupChatMessage.Release());
RoomPtr->AddNewMessage(SharedChatMessageRef);
OnXmppRoomChatReceivedDelegate.Broadcast(ConnectionManager.AsShared(), SharedChatMessageRef->FromJid.Id, SharedChatMessageRef->FromJid, SharedChatMessageRef);
}
else
{
UE_LOG(LogXmpp, Log, TEXT("MUC: OnReceiveGroupChatMessage Ignored GroupChat from room we haven't joined: Room=%s Connjid=%s"), *GroupChatMessage->FromJid.Id, *ConnectionManager.GetUserJid().Id);
}
}
void FXmppMultiUserChatStrophe::OnReceiveGroupChatSubject(FXmppStropheSubjectUpdate&& SubjectUpdate)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(SubjectUpdate.RoomId);
if (RoomPtr != nullptr)
{
RoomPtr->Info.Subject = MoveTemp(SubjectUpdate.NewSubject);
OnXmppRoomInfoRefreshedDelegate.Broadcast(ConnectionManager.AsShared(), true, SubjectUpdate.RoomId, FString());
}
else
{
UE_LOG(LogXmpp, Log, TEXT("MUC: OnReceiveGroupChatMessage Ignored Updated Room Subject from room we haven't joined: Room=%s Connjid=%s NewSubject=%s"), *SubjectUpdate.RoomId, *ConnectionManager.GetUserJid().Id, *SubjectUpdate.NewSubject);
}}
void FXmppMultiUserChatStrophe::OnReceiveRoomConfigError(FXmppStropheErrorPair&& RoomConfigError)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomConfigError.RoomId);
if (RoomPtr != nullptr)
{
EConfigureRoomTypeStrophe* CallbackTypePtr = PendingRoomConfigCallbacks.Find(RoomPtr->GetRoomId());
if (ensure(CallbackTypePtr != nullptr))
{
switch (*CallbackTypePtr)
{
case EConfigureRoomTypeStrophe::NoCallback:
// No-Op
return; // Return on purpose so we don't log below
case EConfigureRoomTypeStrophe::UseConfigCallback:
OnXmppRoomConfiguredDelegate.Broadcast(ConnectionManager.AsShared(), false, RoomPtr->GetRoomId(), RoomConfigError.ErrorMessage);
break;
case EConfigureRoomTypeStrophe::UseCreateCallback:
SendExitRoomStanza(*RoomPtr);
break;
}
UE_LOG(LogXmpp, Warning, TEXT("MUC: Failed to Configure Room Room=%s Error=%s"), *RoomConfigError.RoomId, *RoomConfigError.ErrorMessage);
}
else
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigError Received Error from room we have no callback for: Room=%s Connjid=%s Error=%s"), *RoomConfigError.RoomId, *ConnectionManager.GetUserJid().Id, *RoomConfigError.ErrorMessage);
}
}
else
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigError Received Error from room we haven't joined: Room=%s Connjid=%s Error=%s"), *RoomConfigError.RoomId, *ConnectionManager.GetUserJid().Id, *RoomConfigError.ErrorMessage);
}
}
void FXmppMultiUserChatStrophe::OnReceiveRoomConfigSuccess(FXmppRoomId&& RoomId)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr != nullptr)
{
EConfigureRoomTypeStrophe* CallbackTypePtr = PendingRoomConfigCallbacks.Find(RoomPtr->GetRoomId());
if (ensure(CallbackTypePtr != nullptr))
{
switch (*CallbackTypePtr)
{
case EConfigureRoomTypeStrophe::NoCallback:
// No-Op
break;
case EConfigureRoomTypeStrophe::UseConfigCallback:
OnXmppRoomConfiguredDelegate.Broadcast(ConnectionManager.AsShared(), true, RoomPtr->GetRoomId(), FString());
break;
case EConfigureRoomTypeStrophe::UseCreateCallback:
if (ensure(RoomPtr->Status == ERoomStatusStrophe::CreatePending))
{
RoomPtr->Status = ERoomStatusStrophe::Joined;
}
else
{
UE_LOG(LogXmpp, Warning, TEXT("OnReceiveRoomConfigSuccess: Room %s is not in the CreatePending state (%s)"), *RoomPtr->GetRoomId(), LexToString(RoomPtr->Status));
}
OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, RoomPtr->GetRoomId(), FString());
break;
}
UE_LOG(LogXmpp, Verbose, TEXT("MUC: OnReceiveRoomConfigSuccess Received success for room %s"), *RoomId);
}
else
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigSuccess Received success from room with no callback Room=%s"), *RoomId);
}
}
else
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigSuccess Received RoomConfig from room we haven't joined: Room=%s Connjid=%s"), *RoomId, *ConnectionManager.GetUserJid().Id);
}
}
void FXmppMultiUserChatStrophe::OnReceieveRoomInfoUpdate(FXmppRoomId&& RoomId)
{
FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId);
if (RoomPtr != nullptr)
{
SendRequestRoomInfoConfigStanza(*RoomPtr);
}
else
{
UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceieveRoomInfoUpdate Received RoomInfoUpdate for room we haven't joined: Room=%s Connjid=%s"), *RoomId, *ConnectionManager.GetUserJid().Id);
}
}
bool FXmppMultiUserChatStrophe::SendJoinRoomStanza(const FXmppRoomStrophe& Room, const FString& Password)
{
// Create/Join room
FStropheStanza JoinRoomStanza(ConnectionManager, Strophe::SN_PRESENCE);
JoinRoomStanza.SetTo(Room.GetRoomJid());
{
FStropheStanza XStanza(ConnectionManager, Strophe::SN_X);
XStanza.SetNamespace(Strophe::SNS_MUC);
if (!Password.IsEmpty())
{
FStropheStanza PasswordStanza(ConnectionManager, Strophe::SN_PASSWORD);
PasswordStanza.SetText(Password);
XStanza.AddChild(PasswordStanza);
}
{
FStropheStanza HistoryStanza(ConnectionManager, Strophe::SN_HISTORY);
HistoryStanza.SetAttribute(Strophe::SA_MAXSTANZAS, FString::FromInt(MAX_MESSAGE_HISTORY));
XStanza.AddChild(HistoryStanza);
}
JoinRoomStanza.AddChild(XStanza);
}
return ConnectionManager.SendStanza(MoveTemp(JoinRoomStanza));
}
bool FXmppMultiUserChatStrophe::SendExitRoomStanza(const FXmppRoomStrophe& Room)
{
FStropheStanza ExitPresence(ConnectionManager, Strophe::SN_PRESENCE);
{
ExitPresence.SetTo(Room.GetRoomJid());
ExitPresence.SetType(Strophe::ST_UNAVAILABLE);
}
return ConnectionManager.SendStanza(MoveTemp(ExitPresence));
}
bool FXmppMultiUserChatStrophe::SendRequestRoomInfoConfigStanza(const FXmppRoomStrophe& Room)
{
FStropheStanza IQStanza(ConnectionManager, Strophe::SN_IQ);
{
IQStanza.SetId(FGuid::NewGuid().ToString());
IQStanza.SetTo(Room.GetRoomJid().GetBareId());
IQStanza.SetFrom(ConnectionManager.GetUserJid());
IQStanza.SetType(Strophe::ST_GET);
{
FStropheStanza QueryStanza(ConnectionManager, Strophe::SN_QUERY);
QueryStanza.SetNamespace(Strophe::SNS_DISCO_INFO);
IQStanza.AddChild(QueryStanza);
}
}
return ConnectionManager.SendStanza(MoveTemp(IQStanza));
}
bool FXmppMultiUserChatStrophe::InternalConfigureRoom(const FXmppRoomStrophe& Room, const FXmppRoomConfig& RoomConfig, EConfigureRoomTypeStrophe CallbackType)
{
const auto SetStanzaConfig = [this](FStropheStanza& ParentStanza, const FString& Key, const FString& Value)
{
FStropheStanza FieldStanza(ConnectionManager, Strophe::SN_FIELD);
FieldStanza.SetAttribute(Strophe::SA_VAR, Key);
{
FStropheStanza ValueStanza(ConnectionManager, Strophe::SN_VALUE);
ValueStanza.SetText(Value);
FieldStanza.AddChild(ValueStanza);
}
ParentStanza.AddChild(FieldStanza);
};
FStropheStanza IQStanza(ConnectionManager, Strophe::SN_IQ);
{
IQStanza.SetId(FGuid::NewGuid().ToString());
IQStanza.SetTo(Room.GetRoomJid().GetBareId());
IQStanza.SetFrom(ConnectionManager.GetUserJid());
IQStanza.SetType(Strophe::ST_SET);
FStropheStanza QueryStanza(ConnectionManager, Strophe::SN_QUERY);
{
QueryStanza.SetNamespace(Strophe::SNS_MUC_OWNER);
FStropheStanza XStanza(ConnectionManager, Strophe::SN_X);
{
XStanza.SetNamespace(Strophe::SNS_X_DATA);
XStanza.SetType(Strophe::ST_SUBMIT);
SetStanzaConfig(XStanza, TEXT("FORM_TYPE"), TEXT("http://jabber.org/protocol/muc#roomconfig"));
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_roomname"), RoomConfig.RoomName);
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_roomdesc"), RoomConfig.RoomDesc);
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_persistentroom"), RoomConfig.bIsPersistent ? TEXT("1") : TEXT("0"));
SetStanzaConfig(XStanza, TEXT("muc#maxhistoryfetch"), FString::FromInt(RoomConfig.MaxMsgHistory));
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_changesubject"), RoomConfig.bAllowChangeSubject ? TEXT("1") : TEXT("0"));
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_anonymity"), FXmppRoomConfig::ConvertRoomAnonymityToString(RoomConfig.RoomAnonymity));
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_membersonly"), RoomConfig.bIsMembersOnly ? TEXT("1") : TEXT("0"));
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_moderatedroom"), RoomConfig.bIsModerated ? TEXT("1") : TEXT("0"));
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_publicroom"), RoomConfig.bAllowPublicSearch ? TEXT("1") : TEXT("0"));
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_passwordprotectedroom"), RoomConfig.bIsPrivate ? TEXT("1") : TEXT("0"));
if (RoomConfig.bIsPrivate)
{
SetStanzaConfig(XStanza, TEXT("muc#roomconfig_roomsecret"), RoomConfig.Password);
}
QueryStanza.AddChild(XStanza);
}
}
IQStanza.AddChild(QueryStanza);
}
PendingRoomConfigCallbacks.Emplace(Room.GetRoomId(), CallbackType);
return ConnectionManager.SendStanza(MoveTemp(IQStanza));
}
void FXmppMultiUserChatStrophe::HandleCreateRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleCreateRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName());
FXmppChatMemberRef ChatMember = MakeShareable(new FXmppChatMember(MemberPresence));
Room.Members.Emplace(ChatMember);
FXmppRoomConfig* RoomConfigPtr = PendingRoomCreateConfigs.Find(Room.GetRoomId());
if (ensure(RoomConfigPtr != nullptr))
{
const bool bIsOwner = ChatMember->Affiliation == EXmppChatMemberAffiliation::Owner;
if (bIsOwner)
{
const bool bSuccess = InternalConfigureRoom(Room, *RoomConfigPtr, EConfigureRoomTypeStrophe::UseCreateCallback);
if (!bSuccess)
{
SendExitRoomStanza(Room);
}
}
else
{
Room.Status = ERoomStatusStrophe::Joined;
// We joined instead of creating
OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString());
}
// Remove our pending configurations after we've copied things out in ConfigureRoom
PendingRoomCreateConfigs.Remove(Room.GetRoomId());
}
else
{
Room.Status = ERoomStatusStrophe::Joined;
OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, Room.GetRoomId(), TEXT("Missing Room Configuration"));
}
}
void FXmppMultiUserChatStrophe::HandleJoinPrivateRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleJoinPrivateRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName());
Room.Status = ERoomStatusStrophe::Joined;
Room.Members.Emplace(MakeShareable(new FXmppChatMember(MemberPresence)));
OnXmppRoomJoinPrivateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString());
}
void FXmppMultiUserChatStrophe::HandleJoinPublicRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleJoinPublicRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName());
Room.Status = ERoomStatusStrophe::Joined;
Room.Members.Emplace(MakeShareable(new FXmppChatMember(MemberPresence)));
OnXmppRoomJoinPublicCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString());
}
void FXmppMultiUserChatStrophe::HandleExitRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleExitRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName());
if (Room.Status == ERoomStatusStrophe::CreatePending)
{
OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, Room.GetRoomId(), TEXT("Failed to configure room"));
}
else
{
if (Room.Status != ERoomStatusStrophe::ExitPending)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: Server initiated room exit complete; in state %s"), LexToString(Room.Status));
}
OnXmppRoomExitCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString());
}
// Do not use Room after this
Chatrooms.Remove(Room.GetRoomId());
}
void FXmppMultiUserChatStrophe::HandleRoomMemberJoined(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleRoomMemberJoined: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName());
FXmppChatMemberRef NewMember = MakeShareable(new FXmppChatMember(MemberPresence));
if (NewMember->Affiliation == EXmppChatMemberAffiliation::Owner)
{
Room.Info.OwnerId = MemberPresence.UserJid.Resource;
}
Room.Members.Emplace(NewMember);
OnXmppRoomMemberJoinDelegate.Broadcast(ConnectionManager.AsShared(), Room.GetRoomId(), MemberPresence.UserJid);
}
void FXmppMultiUserChatStrophe::HandleRoomMemberChanged(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleRoomMemberChanged: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName());
for (FXmppChatMemberRef& Member : Room.Members)
{
if (Member->RoomMemberJid == MemberPresence.UserJid)
{
// We don't need to update Nickname as it's part of the User's JID
Member->UserPresence = MemberPresence;
Member->Affiliation = EXmppChatMemberAffiliation::ToType(MemberPresence.Affiliation);
Member->Role = EXmppChatMemberRole::ToType(MemberPresence.Role);
break;
}
}
OnXmppRoomMemberChangedDelegate.Broadcast(ConnectionManager.AsShared(), Room.GetRoomId(), MemberPresence.UserJid);
}
void FXmppMultiUserChatStrophe::HandleRoomMemberLeft(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence)
{
UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleRoomMemberLeft: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName());
const int32 RoomMemberCount = Room.Members.Num();
for (int32 Index = 0; Index < RoomMemberCount; ++Index)
{
const FXmppChatMemberRef& Member = Room.Members[Index];
if (Member->RoomMemberJid == MemberPresence.UserJid)
{
Room.Members.RemoveAt(Index);
break;
}
}
OnXmppRoomMemberExitDelegate.Broadcast(ConnectionManager.AsShared(), Room.GetRoomId(), MemberPresence.UserJid);
}
void FXmppMultiUserChatStrophe::CleanupMessages()
{
while (!IncomingMucPresenceUpdates.IsEmpty())
{
TUniquePtr<FXmppMucPresence> MucPresence;
IncomingMucPresenceUpdates.Dequeue(MucPresence);
FEmbeddedCommunication::AllowSleep(TickRequesterId);
}
while (!IncomingMucPresenceErrors.IsEmpty())
{
FXmppStropheErrorPair ErrorInfo;
IncomingMucPresenceErrors.Dequeue(ErrorInfo);
FEmbeddedCommunication::AllowSleep(TickRequesterId);
}
while (!IncomingGroupChatMessages.IsEmpty())
{
TUniquePtr<FXmppChatMessage> GroupChatMessage;
IncomingGroupChatMessages.Dequeue(GroupChatMessage);
FEmbeddedCommunication::AllowSleep(TickRequesterId);
}
while (!IncomingRoomSubjects.IsEmpty())
{
FXmppStropheSubjectUpdate NewSubject;
IncomingRoomSubjects.Dequeue(NewSubject);
FEmbeddedCommunication::AllowSleep(TickRequesterId);
}
while (!IncomingRoomConfigErrors.IsEmpty())
{
FXmppStropheErrorPair RoomConfigError;
IncomingRoomConfigErrors.Dequeue(RoomConfigError);
FEmbeddedCommunication::AllowSleep(TickRequesterId);
}
while (!IncomingRoomConfigWriteSuccesses.IsEmpty())
{
FXmppRoomId RoomId;
IncomingRoomConfigWriteSuccesses.Dequeue(RoomId);
FEmbeddedCommunication::AllowSleep(TickRequesterId);
}
while (!IncomingRoomInfoUpdates.IsEmpty())
{
FXmppRoomId RoomId;
IncomingRoomInfoUpdates.Dequeue(RoomId);
FEmbeddedCommunication::AllowSleep(TickRequesterId);
}
}
#undef TickRequesterId
#endif