ChatManager
Singleton manager class for the chat system. Manages chat server connection and message transmission/reception.
URL Confirmation
This API uses the service-api.playnanoo.com domain.
API Information
- Server List URL:
https://service-api.playnanoo.com/chat/v20211101/server - Filter URL:
https://service-api.playnanoo.com/chat/v20211101/filter - Method:
PUT - Authentication Required: No
Main Features
| Method | Description |
|---|---|
| Connect | Connect to chat server |
| IsConnected | Check connection status |
| Subscribe | Subscribe to channel (can set previous message count) |
| Unsubscribe | Unsubscribe from channel |
| GetChannels | Get channel list |
| GetPlayersOnline | Get player online status |
| SendPublicMessage | Send public message |
| SendPrivateMessage | Send private message |
| FetchFilterWords | Fetch filter word list |
| Filter | Apply filter words to message |
Unreal C++ Implementation
Header File (ChatManager.h)
// ChatManager.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Http.h"
#include "ChatClient.h"
#include "ChatModels.h"
#include "ChatManager.generated.h"
UCLASS()
class YOURPROJECT_API AChatManager : public AActor
{
GENERATED_BODY()
public:
// Singleton instance
static AChatManager* GetInstance();
AChatManager();
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
// Public API
void Connect(IChatListener* InListener);
bool IsConnected() const;
void Subscribe(const FString& Channel, int32 PrevMessageCount = 0);
void Unsubscribe(const FString& Channel);
void GetChannels();
void GetPlayersOnline(const TArray<FString>& UserIds);
void SendPublicMessage(const FString& Channel, const FString& Text);
void SendPrivateMessage(const FString& TargetUserId, const FString& Text);
// Filter words
void FetchFilterWords();
FString Filter(const FString& Message, TCHAR Separator = TEXT('*'));
private:
static const FString CHAT_SERVER_URL;
static const FString CHAT_FILTER_URL;
static AChatManager* Instance;
TUniquePtr<FChatClient> ChatClient;
IChatListener* Listener;
TArray<FString> FilterWords;
void FetchServerListAndConnect();
};
Implementation File (ChatManager.cpp)
// ChatManager.cpp
#include "ChatManager.h"
#include "PlayNANOOHelper.h"
const FString AChatManager::CHAT_SERVER_URL = TEXT("https://service-api.playnanoo.com/chat/v20211101/server");
const FString AChatManager::CHAT_FILTER_URL = TEXT("https://service-api.playnanoo.com/chat/v20211101/filter");
AChatManager* AChatManager::Instance = nullptr;
AChatManager* AChatManager::GetInstance()
{
return Instance;
}
AChatManager::AChatManager()
: Listener(nullptr)
{
PrimaryActorTick.bCanEverTick = true;
}
void AChatManager::BeginPlay()
{
Super::BeginPlay();
if (Instance != nullptr && Instance != this)
{
Destroy();
return;
}
Instance = this;
}
void AChatManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Unreal WebSocket automatically handles events,
// so Service() call is not needed like Unity.
}
void AChatManager::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (Instance == this)
{
Instance = nullptr;
}
if (ChatClient.IsValid())
{
ChatClient->Disconnect();
ChatClient.Reset();
}
Super::EndPlay(EndPlayReason);
}
void AChatManager::Connect(IChatListener* InListener)
{
Listener = InListener;
// Set user nickname (assuming it's retrieved from DataManager)
FString Nickname = UGameDataManager::Get()->GetNickname();
ChatClient = MakeUnique<FChatClient>(Listener, Nickname);
FetchServerListAndConnect();
}
void AChatManager::FetchServerListAndConnect()
{
// Create request body with player information
TSharedPtr<FJsonObject> Body = FPlayNANOOHelper::CreateRequestBody();
FString JsonBody = FPlayNANOOHelper::ToJsonString(Body);
// HTTP request
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(CHAT_SERVER_URL);
Request->SetVerb(TEXT("PUT"));
FPlayNANOOHelper::SetCommonHeaders(Request, false); // No auth token required
Request->SetContentAsString(JsonBody);
Request->OnProcessRequestComplete().BindLambda(
[this](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bSuccess)
{
if (bSuccess && Res.IsValid() && Res->GetResponseCode() >= 200 && Res->GetResponseCode() < 300)
{
FChatServerResponse ServerResponse;
if (ServerResponse.FromJson(Res->GetContentAsString()))
{
if (ServerResponse.Servers.Num() > 0)
{
// Select random server
int32 Index = FMath::RandRange(0, ServerResponse.Servers.Num() - 1);
ChatClient->Connect(ServerResponse.Servers[Index]);
}
else if (Listener)
{
Listener->OnError(TEXT("SERVER_LIST_FAILED"), TEXT("No servers available"));
}
}
else if (Listener)
{
Listener->OnError(TEXT("SERVER_LIST_FAILED"), TEXT("Failed to parse server list"));
}
}
else if (Listener)
{
Listener->OnError(TEXT("SERVER_LIST_FAILED"), TEXT("HTTP request failed"));
}
});
Request->ProcessRequest();
}
bool AChatManager::IsConnected() const
{
return ChatClient.IsValid() && ChatClient->GetIsConnected();
}
void AChatManager::Subscribe(const FString& Channel, int32 PrevMessageCount)
{
if (ChatClient.IsValid())
{
ChatClient->Subscribe(Channel, PrevMessageCount);
}
}
void AChatManager::Unsubscribe(const FString& Channel)
{
if (ChatClient.IsValid())
{
ChatClient->Unsubscribe(Channel);
}
}
void AChatManager::GetChannels()
{
if (ChatClient.IsValid())
{
ChatClient->GetChannels();
}
}
void AChatManager::GetPlayersOnline(const TArray<FString>& UserIds)
{
if (ChatClient.IsValid())
{
ChatClient->GetPlayersOnline(UserIds);
}
}
void AChatManager::SendPublicMessage(const FString& Channel, const FString& Text)
{
if (ChatClient.IsValid())
{
ChatClient->SendPublicMessage(Channel, Text);
}
}
void AChatManager::SendPrivateMessage(const FString& TargetUserId, const FString& Text)
{
if (ChatClient.IsValid())
{
ChatClient->SendPrivateMessage(TargetUserId, Text);
}
}
void AChatManager::FetchFilterWords()
{
// Create request body with player information
TSharedPtr<FJsonObject> Body = FPlayNANOOHelper::CreateRequestBody();
FString JsonBody = FPlayNANOOHelper::ToJsonString(Body);
// HTTP request
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(CHAT_FILTER_URL);
Request->SetVerb(TEXT("PUT"));
FPlayNANOOHelper::SetCommonHeaders(Request, false); // No auth token required
Request->SetContentAsString(JsonBody);
Request->OnProcessRequestComplete().BindLambda(
[this](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bSuccess)
{
if (bSuccess && Res.IsValid() && Res->GetResponseCode() >= 200 && Res->GetResponseCode() < 300)
{
FChatFilterResponse FilterResponse;
if (FilterResponse.FromJson(Res->GetContentAsString()))
{
FilterWords = FilterResponse.FilterWords;
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("[Chat] Failed to fetch filter words"));
}
});
Request->ProcessRequest();
}
FString AChatManager::Filter(const FString& Message, TCHAR Separator)
{
if (FilterWords.Num() == 0)
{
return Message;
}
FString Result = Message;
for (const FString& Word : FilterWords)
{
if (!Word.IsEmpty())
{
FString Replacement;
for (int32 i = 0; i < Word.Len(); i++)
{
Replacement.AppendChar(Separator);
}
Result = Result.Replace(*Word, *Replacement, ESearchCase::IgnoreCase);
}
}
return Result;
}
Usage Example
// ChatExample.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChatManager.h"
#include "ChatExample.generated.h"
UCLASS()
class YOURPROJECT_API AChatExample : public AActor, public IChatListener
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
// IChatListener implementation
virtual void OnConnected() override;
virtual void OnDisconnected() override;
virtual void OnError(const FString& Code, const FString& Message) override;
virtual void OnChannels(const TArray<FChatChannelInfo>& Channels) override;
virtual void OnSubscribed(const FChatUserInfo& User) override;
virtual void OnUnSubscribed(const FChatUserInfo& User) override;
virtual void OnPublicMessage(const FChatUserInfo& Sender, const FString& Message) override;
virtual void OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message) override;
virtual void OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message) override;
virtual void OnPlayerOnline(const TArray<FChatPlayerInfo>& Players) override;
void SendMessage();
};
// ChatExample.cpp
#include "ChatExample.h"
void AChatExample::BeginPlay()
{
Super::BeginPlay();
AChatManager::GetInstance()->Connect(this);
}
void AChatExample::OnConnected()
{
UE_LOG(LogTemp, Log, TEXT("Chat server connection successful"));
// Fetch filter words
AChatManager::GetInstance()->FetchFilterWords();
// Fetch previous 10 chat messages
AChatManager::GetInstance()->Subscribe(TEXT("global"), 10);
}
void AChatExample::OnDisconnected()
{
UE_LOG(LogTemp, Log, TEXT("Chat server disconnected"));
}
void AChatExample::OnError(const FString& Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Chat error: [%s] %s"), *Code, *Message);
}
void AChatExample::OnChannels(const TArray<FChatChannelInfo>& Channels)
{
for (const auto& Ch : Channels)
{
UE_LOG(LogTemp, Log, TEXT("Channel: %s, Users: %d"), *Ch.channel, Ch.count);
}
}
void AChatExample::OnSubscribed(const FChatUserInfo& User)
{
UE_LOG(LogTemp, Log, TEXT("%s has entered."), *User.visitorName);
}
void AChatExample::OnUnSubscribed(const FChatUserInfo& User)
{
UE_LOG(LogTemp, Log, TEXT("%s has left."), *User.visitorName);
}
void AChatExample::OnPublicMessage(const FChatUserInfo& Sender, const FString& Message)
{
// Apply filter words
FString FilteredMsg = AChatManager::GetInstance()->Filter(Message);
UE_LOG(LogTemp, Log, TEXT("[%s]: %s"), *Sender.visitorName, *FilteredMsg);
}
void AChatExample::OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message)
{
// Apply filter words
FString FilteredMsg = AChatManager::GetInstance()->Filter(Message);
UE_LOG(LogTemp, Log, TEXT("[Whisper from %s]: %s"), *Sender.visitorName, *FilteredMsg);
}
void AChatExample::OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message)
{
FString FilteredMsg = AChatManager::GetInstance()->Filter(Message);
UE_LOG(LogTemp, Log, TEXT("[Notification]: %s"), *FilteredMsg);
}
void AChatExample::OnPlayerOnline(const TArray<FChatPlayerInfo>& Players)
{
for (const auto& P : Players)
{
UE_LOG(LogTemp, Log, TEXT("Player: %s, Online: %s"), *P.userUniqueId, *P.online);
}
}
void AChatExample::SendMessage()
{
// Send message (send original to server)
AChatManager::GetInstance()->SendPublicMessage(TEXT("global"), TEXT("Hello!"));
}