/**
* Copyright 2019 Heroic Labs and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using Nakama;
using Nakama.TinyJson;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using DemoGame.Scripts.Utils;
using DemoGame.Scripts.Session;
namespace DemoGame.Scripts.Chat
{
///
/// Used for communication between nakama server and local chat channels
///
public class ChatManager : Singleton
{
#region private variables
///
/// Chat channels sorted by id
///
private Dictionary _chatChannels = new Dictionary();
#endregion
#region mono and initialization
private void Start()
{
//If NakamaSessionManager singleton is already connected to Nakama server - initialize, else - wait for connection
if (NakamaSessionManager.Instance.IsConnected == true)
{
Init();
}
else
{
NakamaSessionManager.Instance.OnConnectionSuccess += Init;
}
}
///
/// Initializes manager
///
private void Init()
{
//Register methods to Socket events
NakamaSessionManager.Instance.OnConnectionSuccess -= Init;
NakamaSessionManager.Instance.Socket.OnChannelMessage += ReceiveMessage;
NakamaSessionManager.Instance.Socket.OnChannelPresence += ReceiveChannelPresence;
}
#endregion
#region public methods
///
/// Join chat with given user, returns ChatChannel object of chat, creates new ChatChannel and ads to dictionary if needed
///
public async Task JoinChatWithUserAsync(string userId)
{
try
{
IChannel channel = await NakamaSessionManager.Instance.Socket.JoinChatAsync(userId, ChannelType.DirectMessage, true);
//checks if channel is already created if yes - get it from dict and return, else create its and return
ChatChannel chatChannel = _chatChannels.ContainsKey(channel.Id) ? _chatChannels[channel.Id] : CreateChannel(channel.Id);
Debug.Log("Joined direct chat: " + channel.Id);
return chatChannel;
}
catch (Exception e)
{
Debug.LogError("Joining chat with user error: " + e);
return null;
}
}
///
/// Join chat with given group(clan), returns ChatChannel object of chat, creates new ChatChannel and ads to dictionary if needed
///
public async Task JoinChatWithGroupAsync(string groupId)
{
try
{
IChannel channel = await NakamaSessionManager.Instance.Socket.JoinChatAsync(groupId, ChannelType.Group, true);
//checks if channel is already created if yes - get it from dict and return, else create its and return
ChatChannel chatChannel = _chatChannels.ContainsKey(channel.Id) ? _chatChannels[channel.Id] : CreateChannel(channel.Id);
//receive initial user presences
ReceiveChannelPresence(channel.Id, channel.Presences, null);
Debug.Log("Joined group chat: " + channel.Id);
return chatChannel;
}
catch (Exception e)
{
Debug.LogError("Joining chat with group error: " + e);
return null;
}
}
///
/// Leaves chat with given id and removes it from _chatChannels dictionary
///
public async Task LeaveChatChannelAsync(string channelId)
{
try
{
await NakamaSessionManager.Instance.Socket.LeaveChatAsync(channelId);
_chatChannels.Remove(channelId);
Debug.Log("Leaved chat: " + channelId);
return true;
}
catch (Exception e)
{
Debug.LogError("Couldn't leave chat: " + e);
return false;
}
}
///
/// Sends new message to channel with given id
///
public async void SendNewMessage(string channelId, string message)
{
try
{
//Pack message content to json
string jsonMessage = (new Dictionary { { "message", message } }).ToJson();
IChannelMessageAck response = await NakamaSessionManager.Instance.Socket.WriteChatMessageAsync(channelId, jsonMessage);
Debug.Log("Message created by username " + response.Username + ", on channel " + response.ChannelId + ", on date " + response.CreateTime);
}
catch (Exception e)
{
Debug.LogError("Message sending error: " + e);
}
}
///
/// Updates message with given id on channel with given id
///
public async Task EditMessageAsync(string channelId, string messageId, string message)
{
try
{
//Pack message content to json
string jsonMessage = (new Dictionary { { "message", message } }).ToJson();
IChannelMessageAck response = await NakamaSessionManager.Instance.Socket.UpdateChatMessageAsync(channelId, messageId, jsonMessage);
Debug.Log("Message edited by username " + response.Username + ", on channel " + response.ChannelId + ", on date " + response.CreateTime);
return true;
}
catch (Exception e)
{
Debug.LogError("Message edit error: " + e);
return false;
}
}
///
/// Removes message with given id on channel with given id
///
public async Task RemoveMessageAsync(string channelId, string messageId)
{
try
{
IChannelMessageAck response = await NakamaSessionManager.Instance.Socket.RemoveChatMessageAsync(channelId, messageId);
Debug.Log("Message removed by username " + response.Username + ", on channel " + response.ChannelId + ", on date " + response.CreateTime);
return true;
}
catch (Exception e)
{
Debug.LogError("Message removing error: " + e);
return false;
}
}
///
/// Loads historical messages to chat channel, returns if channel history next cursor isn't null
///
///
///
public async Task LoadChannelHistoryAsync(ChatChannel channel)
{
try
{
Client client = NakamaSessionManager.Instance.Client;
ISession session = NakamaSessionManager.Instance.Session;
IApiChannelMessageList loadedMessages = await client.ListChannelMessagesAsync(session, channel.Id, 10, false, channel.NextCursor);
foreach (IApiChannelMessage message in loadedMessages.Messages)
{
ReceiveMessage(message, true);
}
channel.NextCursor = loadedMessages.NextCursor;
//if next cursor is empty - no more messages could be loaded - return false, else return true
if (string.IsNullOrEmpty(channel.NextCursor))
{
return false;
}
else
{
return true;
}
}
catch (Exception e)
{
Debug.LogError("Error while loading channel history: " + e);
return true;
}
}
#endregion
#region private methods
///
/// Dispatches to be runned in main thread
///
private void ReceiveMessage(object sender, IApiChannelMessage message)
{
UnityMainThreadDispatcher.Instance().Enqueue(() => ReceiveMessage(message));
}
///
/// Receives and translates incoming messages
///
private void ReceiveMessage(IApiChannelMessage message, bool historical = false)
{
Debug.Log("Received message: " + message);
ChatChannel channel;
string messageContent = "";
//Search for chat channel which should receive message, if it's not created - creates it
if (_chatChannels.ContainsKey(message.ChannelId))
{
channel = _chatChannels[message.ChannelId];
}
else
{
channel = CreateChannel(message.ChannelId);
}
//Translate message basing on message code
switch (message.Code)
{
//Receiving chat message
case 0:
if (!string.IsNullOrEmpty(message.Content) && message.Content != "{}")
{
messageContent = JsonParser.FromJson>(message.Content)["message"];
}
channel.ChatMessage(message.MessageId, message.SenderId, message.Username, messageContent, message.CreateTime, historical);
break;
//Receiving chat message update
case 1:
if (!string.IsNullOrEmpty(message.Content) && message.Content != "{}")
{
messageContent = JsonParser.FromJson>(message.Content)["message"];
}
channel.ChatUpdate(message.MessageId, messageContent, message.CreateTime);
break;
//Receiving chat message remove
case 2:
channel.ChatRemove(message.MessageId);
break;
//Receiving information about user joined group
case 3:
channel.JoinedGroup(message.MessageId, message.Username, historical);
break;
//Receiving information about user added to group
case 4:
channel.AddedToGroup(message.MessageId, message.Username, historical);
break;
//Receiving information about user left group
case 5:
channel.LeftGroup(message.MessageId, message.Username, historical);
break;
//Receiving information about user kicked group
case 6:
channel.KickedFromGroup(message.MessageId, message.Username, historical);
break;
//Receiving information about user promoted in group
case 7:
channel.PromotedInGroup(message.MessageId, message.Username, historical);
break;
}
}
///
/// Dispatches to be runned in main thread
///
private void ReceiveChannelPresence(object sender, IChannelPresenceEvent presenceEvent)
{
UnityMainThreadDispatcher.Instance().Enqueue(() => ReceiveChannelPresence(presenceEvent.ChannelId, presenceEvent.Joins, presenceEvent.Leaves));
}
///
/// Receives information about users presence on channel, manages users joining and leaving channel
///
private void ReceiveChannelPresence(string channelId, IEnumerable joins, IEnumerable leaves = null)
{
Client client = NakamaSessionManager.Instance.Client;
ISession session = NakamaSessionManager.Instance.Session;
ChatChannel channel;
//Receiving chat message
if (_chatChannels.ContainsKey(channelId))
{
channel = _chatChannels[channelId];
}
else
{
channel = CreateChannel(channelId);
}
//Managing joining users
foreach (IUserPresence presence in joins)
{
channel.JoinedChannel(presence.UserId, presence.Username);
}
//Managing leaving users
if (leaves != null)
{
foreach (IUserPresence presence in leaves)
{
channel.LeftChannel(presence.UserId);
}
}
}
///
/// Creates new with given id and returns it
///
private ChatChannel CreateChannel(string channelId)
{
ChatChannel channel = new ChatChannel(channelId);
_chatChannels.Add(channelId, channel);
return channel;
}
#endregion
}
}