Skip to main content

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

MethodDescription
ConnectConnect to chat server
IsConnectedCheck connection status
SubscribeSubscribe to channel (can set previous message count)
UnsubscribeUnsubscribe from channel
GetChannelsGet channel list
GetPlayersOnlineGet player online status
SendPublicMessageSend public message
SendPrivateMessageSend private message
FetchFilterWordsFetch filter word list
FilterApply 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!"));
}