실시간 채팅 #

실시간 채팅을 통해 라이브 커뮤니티가 쉽게 활성화됩니다.

사용자는 1대1로 또는 그룹의 일부로 또는 채팅방에서 채팅할 수 있습니다. 메시지에는 이미지, 링크 및 기타 컨텐츠를 포함시킬 수 있습니다. 수신자가 온라인 상태면 이러한 메시지는 즉시 클라이언트에게 전달되고 메시지 기록에 저장되므로 오프라인 사용자가 온라인 상태가 되면 쉽게 확인할 수 있습니다.

실시간 채팅 엔진을 통해 전달되는 모든 메시지는 메시지를 받을 사용자를 내부적으로 식별하기 위해 사용되는 채널에 속합니다. 사용자는 연결될 때 채널을 명시적으로 가입하고 탈퇴합니다. 이렇게 하면 관심있는 메시지를 선택적으로 듣거나 또는 사용 중인 특정 채널을 쉽게 “음소거"할 수 있습니다. 사용자는 한 번에 여러 채널에 가입하여 여러 그룹이나 채팅방에서 동시에 채팅할 수도 있습니다.

채팅 채널 #

채널에는 3 가지 유형이 있습니다:

  1. 채팅방은 공개적인 채팅에 좋습니다. 어떤 사용자도 허가없이 가입하고 참여할 수 있습니다. 이런 채팅방은 동시 커뮤니케이션에서 수백만 명의 사용자로 확장될 수 있습니다. 라이브 참여 앱 또는 라이브 이벤트 또는 토너먼트가 있는 게임에 적합합니다.

  2. 그룹 채팅은 그룹의 사용자에게만 공개됩니다. 각 사용자는 그룹의 구성원이어야 하며 다른 사용자는 참여할 수 없습니다. 팀 기반 게임 플레이 또는 협업에서 그룹 채팅을 사용할 수 있습니다.

  3. 직접 채팅은 두 사용자에게만 공개됩니다. 각 사용자는 채팅에 초대되면 알림이 전달됩니다. 나쁜 사용자의 스팸을 방지하기 위해 메시지를 교환하려면 두 사용자 모두 가입해야 합니다.

지속성 #

기본적으로 모든 채널은 영구적이므로 채널을 통해 주고 받은 메시지는 데이터베이스에 저장되며 나중에 메시지 기록에서 사용할 수 있습니다. 이 기록을 통해 오프라인 사용자는 확인하지 못한 메시지를 다음에 연결한 후 확인할 수 있습니다.

메시지를 온라인 사용자에게만 보내고 메시지 기록에 보관하지 않기 위해 클라이언트는 지속성이 비활성화된 채널에 가입할 수 있습니다.

숨겨진 채널 구성원 #

기본적으로 채널에 가입하는 모든 사용자는 다른 사용자에게 표시됩니다. 기존 채널 참가자는 사용자가 연결 및 연결 해제될 때 이벤트를 받으며 새로운 채널 가입자는 이미 채널에 있는 사용자 목록을 받습니다.

사용자는 연결할 때 자신의 채널 존재를 숨기도록 선택할 수 있으므로 사용자의 가입/탈퇴 알림이 생성되지 않고 사용자가 채널 구성원 목록에 표시되지 않습니다. 이런 경우에도 사용자는 평소처럼 실시간 메시지를 주고 받을 수 있습니다.

채팅 가입 #

다른 사용자에게 메시지를 보내려면 사용자는 통신할 채팅 채널에 가입해야 합니다. 이렇게 하면 실시간으로 메시지를 받을 수도 있습니다.

각 사용자는 자신의 세션으로 다양한 방, 그룹 및 직접 채팅에 가입할 수 있습니다. 각 장치는 별도의 세션으로 식별되므로 한 사용자가 다른 장치에서 같은 채팅에 연결할 수도 있습니다.

채팅방 #

채팅방은 사용자가 채팅할 수 있게 동적으로 생성됩니다. 채팅방에는 이름이 있으며 사용자가 가입할 때 서버에 설정됩니다. 가입할 수 있는 채팅방 이름 목록은 클라이언트 코드에 저장하거나 저장소 기록으로 원격 구성을 통해 저장할 수 있습니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
4
5
6
7
8
const roomname = "MarvelMovieFans";
const persistence = true;
const hidden = false;

// 1 = Room, 2 = Direct Message, 3 = Group
const response = await socket.joinChat(roomname, 1, persistence, hidden);

console.log("Now connected to channel id: '%o'", response.channel.id);
Client
1
2
3
4
5
var roomname = "MarvelMovieFans";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(roomname, ChannelType.Room, persistence, hidden);
Console.WriteLine("Now connected to channel id: '{0}'", channel.Id);
Client
1
2
3
4
5
6
7
auto successCallback = [](NChannelPtr channel)
{
    std::cout << "Now connected to channel id: " << channel->id << std::endl;
};

string roomname = "MarvelMovieFans";
rtClient->joinChat(roomname, NChannelType::ROOM, true, false, successCallback);
Client
1
2
3
4
5
String roomname = "MarvelMovieFans";
boolean persistence = true;
boolean hidden = false;
Channel channel = socket.joinChat(roomname, ChannelType.ROOM, persistence, hidden).get();
System.out.format("Now connected to channel id: %s", channel.getId());
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var roomname = "MarvelMovieFans"
var persistence = true
var hidden = false
var type = NakamaSocket.ChannelType.Room
var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(roomname, type, persistence, hidden), "completed")

if channel.is_exception():
    print("An error occurred: %s" % channel)
    return

print("Now connected to channel id: '%s'" % [channel.id])

roomId 변수에는 메시지를 보내는 데 사용되는 ID가 포함됩니다.

그룹 #

그룹 채팅은 그룹의 구성원인 사용자만 가입할 수 있습니다. 메시지는 실시간으로 그룹 구성원에게 푸시되며 구성원들은 기록 메시지를 읽을 수 있습니다. 쫓겨나거나 그룹을 나간 사용자는 더 이상 메시지를 받거나 기록을 읽을 수 없습니다.

그룹 채팅에 가입하는 사용자는 그룹 ID가 필요하며 사용자는 이를 나열할 수 있습니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
4
5
6
7
const groupId = "<group id>";
const persistence = true;
const hidden = false;
// 1 = Room, 2 = Direct Message, 3 = Group
const response = await socket.joinChat(3, groupId, persistence, hidden);

console.log("You can now send messages to channel id: ", response.channel.id);
Client
1
2
3
4
5
var groupId = "<group id>";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(groupId, ChannelType.Group, persistence, hidden);
Console.WriteLine("You can now send messages to channel id: '{0}'", channel.Id);
Client
1
2
3
4
5
6
7
auto successCallback = [](NChannelPtr channel)
{
    std::cout << "You can now send messages to channel id: " << channel->id << std::endl;
};

string groupId = "<group id>";
rtClient->joinChat(groupId, NChannelType::GROUP, true, false, successCallback);
Client
1
2
3
4
5
String groupId = "<group id>";
boolean persistence = true;
boolean hidden = false;
Channel channel = socket.joinChat(groupId, ChannelType.GROUP, persistence, hidden).get();
System.out.format("You can now send messages to channel id %s", channel.getId());
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var group_id = "<group id>"
var persistence = true
var hidden = false
var type = NakamaSocket.ChannelType.Group
var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(group_id, type, persistence, hidden), "completed")

if channel.is_exception():
    print("An error occurred: %s" % channel)
    return

print("Now connected to channel id: '%s'" % [channel.id])

"<group id>" 변수는 메시지를 보내는데 사용되는 ID여야 합니다.

직접 #

사용자는 ID를 통해 다른 사용자에게 메시지를 직접 보낼 수 있습니다. 친구, 그룹, 순위표, 매치메이커, 채팅방 및 저장소 검색은 모두 채팅할 사용자를 찾는 방법입니다.

두 사용자가 모두 채팅에 가입할 때까지 실시간으로 메시지가 전달되지 않습니다. 이렇게 하면 나쁜 사용자가 보내는 스팸 메시지가 방지되므로 중요합니다.

다른 사용자를 직접 채팅에 초대하려면:

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
4
5
6
const userId = "<user id to invite>";
const persistence = true;
const hidden = false;
// 1 = Room, 2 = Direct Message, 3 = Group
const response = await socket.joinChat(userId, 2, persistence, hidden);
console.log("You can now send messages to channel id:", response.channel.id);
Client
1
2
3
4
5
var userId = "<user id to invite>";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(userId, ChannelType.DirectMessage, persistence, hidden);
Console.WriteLine("You can now send messages to channel id: '{0}'", channel.Id);
Client
1
2
3
4
5
6
7
auto successCallback = [](NChannelPtr channel)
{
    std::cout << "You can now send messages to channel id: " << channel->id << std::endl;
};

string userId = "<user id to invite>";
rtClient->joinChat(userId, NChannelType::DIRECT_MESSAGE, true, false, successCallback);
Client
1
2
3
4
5
String userId = "<user id to invite>";
boolean persistence = true;
boolean hidden = false;
Channel channel = socket.joinChat(userId, ChannelType.DIRECT_MESSAGE, persistence, hidden).get();
System.out.format("You can now send messages to channel id %s", channel.getId());
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var user_id = "<user id to invite>"
var persistence = true
var hidden = false
var type = NakamaSocket.ChannelType.DirectMessage
var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(user_id, type, persistence, hidden), "completed")

if channel.is_exception():
    print("An error occurred: %s" % channel)
    return

print("Now connected to channel id: '%s'" % [channel.id])

채팅 요청이 들어오면 사용자는 인앱 알림을 받게 됩니다. 초대를 수락하려면 사용자는 요청하는 사용자를 대상으로 동일한 joinChat 요청으로 응답합니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
4
5
6
const userId = "<user id of requestor>";
const persistence = true;
const hidden = false;
// 1 = Room, 2 = Direct Message, 3 = Group
const response = await socket.joinChat(userId, 2, persistence, hidden);
console.log("You can now send messages to channel id:", response.channel.id);
Client
1
2
3
4
5
var userId = "<user id of requestor>";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(userId, ChannelType.DirectMessage, persistence, hidden);
Console.WriteLine("You can now send messages to channel id: '{0}'", channel.Id);
Client
1
2
3
4
5
6
7
auto successCallback = [](NChannelPtr channel)
{
    std::cout << "You can now send messages to channel id: " << channel->id << std::endl;
};

string userId = "<user id of requestor>";
rtClient->joinChat(userId, NChannelType::DIRECT_MESSAGE, true, false, successCallback);
Client
1
2
3
4
5
String userId = "<user id of requestor>";
boolean persistence = true;
boolean hidden = false;
Channel channel = socket.joinChat(userId, ChannelType.DIRECT_MESSAGE, persistence, hidden).get();
System.out.format("You can now send messages to channel id %s", channel.getId());
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var user_id = "<user id of requestor>"
var persistence = true
var hidden = false
var type = NakamaSocket.ChannelType.DirectMessage
var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(user_id, type, persistence, hidden), "completed")

if channel.is_exception():
    print("An error occurred: %s" % channel)
    return

print("Now connected to channel id: '%s'" % [channel.id])

"<user id>" 변수는 메시지를 보내는데 사용되는 ID여야 합니다.

사용자는 원치 않는 직접 메시지를 중지하기 위해 다른 사용자를 차단할 수 있습니다.

온라인 사용자 나열 #

채팅에 가입하는 각 사용자는 채팅 채널에서 “현재 상태"가 됩니다(“숨겨진” 채널 사용자로 참여하지 않은 경우). 이러한 현재 상태를 통해 온라인 상태인 사용자를 확인할 수 있습니다.

현재 상태는 사용자 ID와 결합된 고유한 세션으로 구성됩니다. 이렇게 하면 채팅 채널에서 여러 장치를 사용하여 연결한 사용자를 쉽게 구별할 수 있습니다.

채팅 채널에 가입하는 사용자에게는 채팅 채널에서 연결된 다른 모든 사용자의 초기 현재 상태 목록이 제공됩니다. 콜백을 사용하여 가입 및 탈퇴하는 사용자의 현재 상태 변경 사항을 서버에서 받을 수 있습니다. 이렇게 하면 온라인 사용자 목록을 쉽게 유지 관리하고 변경 사항이 있을 때 업데이트할 수 있습니다.

서버는 다른 사용자가 채팅에 가입하거나 탈퇴할 때만 현재 상태 업데이트를 푸시하도록 최적화되어 있습니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var onlineUsers = [];
socket.onchannelpresence = (presences) => {
  // Remove all users who left.
  onlineUsers = onlineUsers.filter((user) => {
    return !presences.leave.includes(user);
  });
  // Add all users who joined.
  onlineUsers.concat(presences.join);
};

const roomname = "PizzaFans";
const persistence = true;
const hidden = false;

// 1 = Room, 2 = Direct Message, 3 = Group
const response = await socket.joinChat(roomname, 1, persistence, hidden);

// Setup initial online user list.
onlineUsers.concat(response.channel.presences);
// Remove your own user from list.
onlineUsers = onlineUsers.filter((user) => {
  return user != channel.self;
});
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var roomUsers = new List<IUserPresence>(10);
socket.ReceivedChannelPresence += presenceEvent =>
{
    foreach (var presence in presenceEvent.Leaves)
    {
        roomUsers.Remove(presence);
    }

    roomUsers.AddRange(presenceEvent.Joins);
    Console.WriteLine("Room users: [{0}]", string.Join(",\n  ", roomUsers));
};

var roomName = "PizzaFans";
var persistence = true;
var hidden = false;
var channel = await socket.JoinChatAsync(roomName, ChannelType.Room, persistence, hidden);
roomUsers.AddRange(channel.Presences);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// add this to your class: std::vector<NUserPresence> onlineUsers;

rtListener->setChannelPresenceCallback([this](const NChannelPresenceEvent& event)
{
    // Remove all users who left.
    for (auto& left : event.leaves)
    {
        for (auto it = onlineUsers.begin(); it != onlineUsers.end(); ++it)
        {
            if (it->userId == left.userId)
            {
                onlineUsers.erase(it);
                break;
            }
        }
    }

    // Add all users who joined.
    onlineUsers.insert(onlineUsers.end(), event.joins.begin(), event.joins.end());
});

auto successCallback = [this](NChannelPtr channel)
{
    onlineUsers.reserve(channel->presences.size());

    // Setup initial online user list without self.
    for (auto& joined : channel->presences)
    {
        if (joined.userId != channel->self.userId)
        {
           onlineUsers.push_back(joined);
        }
    }
};

string roomname = "PizzaFans";
rtClient->joinChat(roomname, NChannelType::ROOM, true, false, successCallback);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
final List<UserPresence> connectedUsers = new ArrayList<UserPresence>();
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onChannelPresence(final ChannelPresenceEvent presence) {
        connectedUsers.addAll(presence.getJoins());
        for (UserPresence presence : presence.getLeaves()) {
            for (int i = 0; i < connectedUsers.size(); i++) {
                if (connectedUsers.get(i).getUserId().equals(presence.getUserId())) {
                    connectedUsers.remove(i);
                }
            }
        }
    }
};

String roomname = "PizzaFans";
boolean persistence = true;
boolean hidden = false;
Channel channel = socket.joinChat(roomname, ChannelType.ROOM, persistence, hidden);
connectedUsers.addAll(channel.getPresences());
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var room_users = {}

func _ready():
  # First, setup the socket as explained in the authentication section.
  socket.connect("received_channel_presence", self, "_on_channel_presence")

  # Connect to the room.
  var roomname = "MarvelMovieFans"
  var persistence = true
  var hidden = false
  var type = NakamaSocket.ChannelType.Room
  var channel : NakamaRTAPI.Channel = yield(socket.join_chat_async(roomname, type, persistence, hidden), "completed")

  if channel.is_exception():
      print("An error occurred: %s" % channel)
      return

  # Add users already present in chat room.
  for p in channel.presences:
      room_users[p.user_id] = p

  print("Users in room: %s" % [room_users.keys()])

func _on_channel_presence(p_presence : NakamaRTAPI.ChannelPresenceEvent):
  for p in p_presence.joins:
      room_users[p.user_id] = p

  for p in p_presence.leaves:
      room_users.erase(p.user_id)

  print("Users in room: %s" % [room_users.keys()])

메시지 받기 #

사용자가 채팅 채널에 가입하여 실시간으로 메시지를 수신하기 시작합니다. 새 메시지는 각각 이벤트 핸들러에서 수신하고 UI에 추가할 수 있습니다. 메시지는 서버에서 처리한 순서대로 전달됩니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
4
socket.onchannelmessage = (message) => {
  console.log("Received a message on channel: %o", message.channel_id);
  console.log("Message content: %o", message.content);
};
Client
1
2
3
4
5
6
socket.ReceivedChannelMessage += message =>
{
    Console.WriteLine("Received: {0}", message);
    Console.WriteLine("Message has channel id: {0}", message.ChannelId);
    Console.WriteLine("Message content: {0}", message.Content);
};
Client
1
2
3
4
5
rtListener->setChannelMessageCallback([](const NChannelMessage& msg)
{
    // msg.content is JSON string
    std::cout << "OnChannelMessage " << msg.content << std::cout;
});
Client
1
2
3
4
5
6
7
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onChannelMessage(final ChannelMessage message) {
        System.out.format("Received a message on channel %s", message.getChannelId());
        System.out.format("Message content: %s", message.getContent());
    }
};
Client
1
2
3
4
5
6
7
8
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_channel_message", self, "_on_channel_message")

func _on_channel_message(p_message : NakamaAPI.ApiChannelMessage):
    print(p_message)
    print("Received a message on channel: %s" % [p_message.channel_id])
    print("Message content: %s" % [p_message.content])

그룹 #

그룹 채팅의 경우 사용자는 서버로부터 다른 메시지를 받습니다. 이 메시지에는 그룹에 가입하거나 탈퇴하는 사용자에 대한 이벤트, 누군가가 관리자로 승격되는 경우 등이 포함됩니다. 사용자가 채팅 스트림에서 이러한 메시지를 보거나 UI에서 무시하도록 지정할 수 있습니다.

Type 메시지로 채팅 메시지와 이벤트 메시지를 식별할 수 있습니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
if (message.code != 0) {
  console.log("Received message with code:", message.code);
}
Client
1
2
3
4
if (message.Code != 0)
{
    Console.WriteLine("Received message with code '{0}'", message.Code);
}
Client
1
2
3
4
if (msg.code != 0)
{
    std::cout << "Received message with code: " << msg.code << std::endl;
}
Client
1
2
3
if (message.getCode() != 0) {
    System.out.println("Received message with code %s", message.getCode());
}
Client
1
2
if p_message.code != 0:
    print("Received message with code:", p_message.code)
CodePurposeSourceDescription
0Chat MessageUser사용자가 보낸 모든 메시지.
1Chat UpdateUser이전에 보낸 메시지를 업데이트하는 사용자.
2Chat RemoveUser이전에 보낸 메시지를 제거하는 사용자.
3Joined GroupServer사용자가 그룹에 참여했을 때의 이벤트 메시지.
4Added to GroupServer사용자가 그룹에 추가되었을 때의 이벤트 메시지.
5Left GroupServer사용자가 그룹을 떠났을 때의 이벤트 메시지.
6Kicked from GroupServer관리자가 그룹에서 사용자를 강퇴시켰을 때의 이벤트 메시지.
7Promoted in GroupServer사용자가 그룹 관리자로 승격될 때의 이벤트 메시지.
8Banned in GroupServer사용자가 그룹에서 차단되었을 때의 이벤트 메시지.
9Demoted in GroupServer사용자가 그룹에서 강등되었을 때의 이벤트 메시지.

메시지 보내기 #

사용자가 채팅 채널에 가입하면 해당 ID를 사용하여 JSON 인코딩 문자열로 메시지를 보낼 수 있습니다.

전송된 모든 메시지는 서버에서 수신할 때 승인을 반환합니다. 반환된 승인에는 메시지 ID, 타임스탬프 및 메시지를 보낸 사용자에 대한 세부 정보가 포함됩니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
var channelId = "<channel id>";
var data = { "some": "data" };
const messageAck = await socket.writeChatMessage(channelId, data);
Client
1
2
3
var channelId = "<channel id>";
var content = new Dictionary<string, string> {{"hello", "world"}}.ToJson();
var sendAck = await socket.WriteChatMessageAsync(channelId, content);
Client
1
2
3
4
5
6
7
8
auto successCallback = [](const NChannelMessageAck& ack)
{
    std::cout << "message id: " << ack.messageId << std::endl;
};

string channelId = "<channel id>";
string data = "{ "some": "data" }";
rtClient->writeChatMessage(channelId, data, successCallback);
Client
1
2
3
String channelId = "<channel id>";
final String content = "{"message":"Hello world"}";
ChannelMessageAck sendAck = socket.writeChatMessage(channelId, content).get();
Client
1
2
3
4
5
6
7
8
9
var channel_id = "<channel id>"
var data = { "some": "data" }
var message_ack : NakamaRTAPI.ChannelMessageAck = yield(socket.write_chat_message_async(channel_id, data), "completed")

if message_ack.is_exception():
    print("An error occurred: %s" % message_ack)
    return

print("Sent message %s" % [message_ack])

메시지 컨텐츠 필터링 #

모욕적이거나 공격적인 행위를 방지하기 위해 채팅 채널에서 사용자의 메시지를 필터링하는 것이 일반적입니다. 이는 서버 런타임 코드후크를 사용하여 부적절한 컨텐츠가 포함된 메시지를 삭제하거나 거부함으로써 Nakama에서 달성할 수 있습니다.

이 코드는 메시지가 전송될 때 서버에서 트리거됩니다.

Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Inside your Go module's `InitModule` function.
if err := initializer.RegisterBeforeRt("ChannelMessageSend", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, envelope *rtapi.Envelope) (*rtapi.Envelope, error) {
  message := envelope.GetChannelMessageSend()
  if strings.Contains(message.Content, "bad word") {
    // Alternatively, to sanitize instead of reject:
    // message.Content = strings.ReplaceAll(message.Content, "bad word", "****")

    // Reject the message send:
    return nil, runtime.NewError("profanity detected", 3)
  }
  return envelope, nil
}); err != nil {
  return err
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let rtBeforeChannelMessageSend: nkruntime.RtBeforeHookFunction<nkruntime.Envelope> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, envelope: nkruntime.Envelope) : nkruntime.Envelope {
    let e = envelope as nkruntime.EnvelopeChannelMessageSend;
    if (e == null)
    {
        return e;
    }

    if (e.channelMessageSend.content.indexOf('Bad Word') !== -1) {
        // Alternatively, to sanitize instead of reject:
        //e.channelMessageSend.content = e.channelMessageSend.content.replace('Bad Word', '****');

        // Reject the message send.
        throw new Error("Profanity detected");
    }

    return e;
}

initializer.registerRtBefore("ChannelMessageSend", rtBeforeChannelMessageSend);

위 예의 경우, 고유한 필터링 기준(“악성 단어” 인덱스)을 만들고 유지 관리하면서 원하는 경우 타사 필터링 API와의 통합도 선택할 수 있습니다.

채팅 나가기 #

사용자는 더 이상 실시간으로 메시지를 보내지 않기 위해 채팅 채널을 나갈 수 있습니다. 이는 UI의 다른 부분에 있는 동안 채팅을 “음소거"하는데 유용할 수 있습니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
var channelId = "<channel id>";
await socket.leaveChat(channelId);
Client
1
2
var channelId = "<channel id>";
await socket.LeaveChatAsync(channelId);
Client
1
2
string channelId = "<channel id>";
rtClient->leaveChat(channelId);
Client
1
2
String channelId = "<channel id>";
socket.leaveChat(channelId).get();
Client
1
2
3
4
5
6
7
8
var channel_id = "<channel id>"
var result : NakamaAsyncResult = yield(socket.leave_chat_async(channel_id), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return

print("Left chat")

메시지 기록 #

모든 채팅 대화에서 메시지 기록은 저장됩니다(지속성이 false로 설정되지 않은 경우). 이 기록에는 서버가 그룹 채팅 채널에 보낸 이벤트 메시지도 포함됩니다. 각 사용자는 다음에 온라인으로 연결할 때 채널의 이전 메시지를 검색할 수 있습니다. 사용자는 채팅 기록을 보기 위해 채팅 채널에 가입할 필요가 없습니다. 이는 사용자가 채팅에 온라인으로 표시되지 않은 상태에서 메시지를 “미리 보기"하는데 유용합니다.

메시지는 최신 순 또는 역순(오래된 것에서 가장 최근 것)으로 나열될 수도 있습니다. 메시지는 더 많은 메시지가 있는 경우 커서를 포함하여 각각 최대 100개의 배치로 반환됩니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/channel/<channelId>" \
  -H 'authorization: Bearer <session token>'
Client
1
2
3
4
5
6
7
8
const channelId = "<channel id>";
const result = await client.listChannelMessages(session, channelId, 10);

result.messages.forEach((message) => {
  console.log("Message has id %o and content %o", message.message_id, message.data);
});

console.log("Get the next page of messages with the cursor:", result.next_cursor);
Client
1
2
3
4
5
6
7
var channelId = "<channel id>";
var result = await client.ListChannelMessagesAsync(session, channelId, 10, true);

foreach (var m in result.Messages)
{
    Console.WriteLine("Message id '{0}' content '{1}'", m.MessageId, m.Content);
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
auto successCallback = [](NChannelMessageListPtr list)
{
    for (auto& message : list->messages)
    {
        std::cout << "message content: " << message.content << std::endl;
    }

    std::cout << "Get the next page of messages with the cursor: " << list->nextCursor << std::endl;
};

string channelId = "<channel id>";
client->listChannelMessages(session, channelId, 10, opt::nullopt, opt::nullopt, successCallback);
Client
1
2
3
4
5
6
String channelId = "<channel id>";
ChannelMessageList messages = client.listChannelMessages(session, channelId, 10).get();

for (ChannelMessage message : messages.getMessagesList()) {
    System.out.format("Message content: %s", message.getContent());
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var channel_id = "<channel id>"
var result : NakamaAPI.ApiChannelMessageList = yield(client.list_channel_messages_async(session, channel_id, 10), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return

for m in result.messages:
    var message : NakamaAPI.ApiChannelMessage = m as NakamaAPI.ApiChannelMessage
    print("Message has id %s and content %s" % [message.message_id, message.content])

print("Get the next page of messages with the cursor: %s" % [result.next_cursor])
Client
1
2
3
4
5
GET /v2/channel/<channelId>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>

커서를 사용하여 다음 결과 세트를 위해 메시지 배치 처리 후 페이징할 수 있습니다.

UI에는 최근 100개의 메시지만 나열하는 것이 좋습니다. 좋은 사용자 경험은 사용자가 UI 패널의 맨 아래로 스크롤할 때 다음 100개의 오래된 메시지를 표시하는 것입니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/channel/<channelId>?forward=true&limit=10&cursor=<cursor>" \
  -H 'Authorization: Bearer <session token>'
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var channelId = "<channel id>";
var forward = true;
var result = await client.listChannelMessages(session, channelId, 10, forward);

result.messages.forEach((message) => {
  console.log("Message has id %o and content %o", message.message_id, message.data);
});

if (result.next_cursor) {
  // Get the next 10 messages.
  var result = await client.listChannelMessages(session, channelId, 10, forward, result.next_cursor);

  result.messages.forEach((message) => {
    console.log("Message has id %o and content %o", message.message_id, message.data);
  });
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var channelId = "<channel id>";
var result = await client.ListChannelMessagesAsync(session, channelId, 10, true);

foreach (var m in result.Messages)
{
    Console.WriteLine("Message id '{0}' content '{1}'", m.MessageId, m.Content);
}

if (!string.IsNullOrEmpty(result.NextCursor)) {
    // Get the next 10 messages.
    var result = await client.ListChannelMessagesAsync(session, channelId, 10, true, result.NextCursor);
    foreach (var m in messages)
    {
        Console.WriteLine("Message id '{0}' content '{1}'", m.MessageId, m.Content);
    }
};
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void YourClass::listChannelMessages(const std::string& cursor)
{
    auto successCallback = [this](NChannelMessageListPtr list)
    {
        for (auto& message : list->messages)
        {
            std::cout << "message content: " << message.content << std::endl;
        }

        if (!list->nextCursor.empty())
        {
            listChannelMessages(list->nextCursor);
        }
    };

    string channelId = "<channel id>";
    client->listChannelMessages(session, channelId, 10, cursor, true, successCallback);
}

listChannelMessages("");
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
String channelId = "<channel id>";
ChannelMessageList messages = client.listChannelMessages(session, channelId, 10).get();

if (messages.getNextCursor() != null) {
    messages = client.listChannelMessages(session, channelId, 10, messages.getNextCursor()).get();

    for (ChannelMessage message : messages.getMessagesList()) {
        System.out.format("Message content: %s", message.getContent());
    }
  }
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var channel_id = "<channel id>"
var forward = true
var result : NakamaAPI.ApiChannelMessageList = yield(client.list_channel_messages_async(session, channel_id, 10, forward), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return

for m in result.messages:
    var message : NakamaAPI.ApiChannelMessage = m as NakamaAPI.ApiChannelMessage
    print("Message has id %s and content %s" % [message.message_id, message.content])

if result.next_cursor:
    result = yield(client.list_channel_messages_async(session, channel_id, 10, forward, result.next_cursor), "completed")

    if result.is_exception():
        print("An error occurred: %s" % result)
        return

    for m in result.messages:
        var message : NakamaAPI.ApiChannelMessage = m as NakamaAPI.ApiChannelMessage
        print("Message has id %s and content %s" % [message.message_id, message.content])
Client
1
2
3
4
5
GET /v2/channel/<channel id>?forward=true&limit=10&cursor=<cursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>

캐시 가능한 커서 #

클라이언트가 목록을 마지막으로 검색한 이후에 추가된 메시지만 검색하는 것이 더 좋을 수 있습니다. 이는 각 채널 메시지와 함께 반환된 캐시 가능한 커서로 수행할 수 있습니다. 새 목록 작업을 통해 커서를 보내면 표시된 메시지보다 더 최신인 메시지만 검색합니다.

캐시 가능한 커서는 검색된 가장 최근 채널 메시지의 위치를 표시합니다. 캐시 가능한 커서를 장치 저장소에 저장하고 클라이언트가 최근 알림에 대한 다음 요청을 실시할 때 사용하는 것이 좋습니다.

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
4
5
6
const cacheableCursor = "<cacheableCursor>";
const channelId = "<channelId>";
const result = await client.listChannelMessages(channelId, 10, cacheableCursor);
result.messages.forEach((message) => {
  console.log("Message has id %o and content %o", message.message_id, message.data);
});
Client
1
2
3
4
5
6
7
const string cacheableCursor = "<cacheableCursor>";
const string channelId = "<channelId>";
var result = await client.ListChannelMessagesAsync(session, channelId, 10, true, cacheableCursor);
foreach (var m in result.Messages)
{
    Console.WriteLine("Message id '{0}' content '{1}'", m.MessageId, m.Content);
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
auto successCallback = [this](NChannelMessageListPtr list)
{
    for (auto& message : list->messages)
    {
        std::cout << "Message content " << message.content << std::endl;
    }
};

string channelId = "<channelId>";
string cacheableCursor = "<cacheableCursor>";
client->listChannelMessages(session, channelId, 10, cacheableCursor, true, successCallback);
Client
1
2
3
4
5
6
String channelId = "<channel id>";
String cacheableCursor = "<cacheableCursor>";
ChannelMessageList messages = client.listChannelMessages(session, channelId, 10, cacheableCursor).get();
for (ChannelMessage message : messages.getMessagesList()) {
        System.out.format("Message content: %s", message.getContent());
    }
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var cacheable_cursor = "<cacheable cursor>";
var channel_id = "<channel id>"
var forward = true
var result : NakamaAPI.ApiChannelMessageList = yield(client.list_channel_messages_async(session, channel_id, 10, forward, cacheable_cursor), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for m in result.messages:
    var message : NakamaAPI.ApiChannelMessage = m as NakamaAPI.ApiChannelMessage
    print("Message has id %s and content %s" % [message.message_id, message.content])