그룹 #

그룹 또는 클랜은 다수의 사용자를 작은 커뮤니티 또는 팀으로 모읍니다.

그룹은 총괄 관리자, 관리자 및 구성원으로 이뤄집니다. 그룹은 사용자가 그룹을 나열할 때 결과에 표시할지 여부의 결정에 따라 공개 또는 비공개일 수 있습니다. 비공개 그룹은 WhatsApp 그룹이 작동하는 방식과 비슷합니다. 그룹 관리자 중 한 명이 가입 초대를 한 사용자만 추가할 수 있습니다.

그룹에는 최대 구성원 수도 설정되어 있습니다. 그룹을 클라이언트에서 생성한 경우 기본적으로 100으로 설정되며 그룹을 코드 런타임에서 생성한 경우에는 재정의할 수 있습니다.

그룹 사용자의 상태는 네 가지입니다.

코드목적
0관리자모든 그룹에는 최소 1명의 관리자가 있어야 합니다. 총괄 관리자는 모든 관리자 권한이 부여되며, 그룹을 삭제하고 관리자 구성원을 승격시킬 수 있습니다.
1관리자관리자는 2명 이상일 수 있습니다. 관리자는 그룹을 업데이트하거나 구성원을 수락, 추방, 승격, 강등, 금지, 추가할 수 있습니다.
2구성원일반 그룹 구성원 새로운 사용자의 가입 요청을 수락할 수 없습니다.
3가입 요청새로운 사용자로부터 가입 요청이 있습니다. 이것은 그룹의 최대 구성원 숫자에 포함되지 않습니다.

그룹 나열 및 필터링 #

name, lang_tag, open 및 (다수의) members과(와) 같은 여러 선택적 필터를 사용하여 나열할 수 있습니다. 어떤 필터도 사용하지 않으면 이 작업은 기존의 모든 그룹을 나열합니다.

name 필터는 대소문자를 구분하지 않으며 나머지 필터와 상호 배타적입니다. 이를 통해 사용자는 이름으로 특정 그룹을 찾을 수 있으며 부분 일치를 위한 % 와일드카드를 접미사로 지원합니다. 예를 들어, “Persian” 단어가 접두사로 붙은 그룹을 찾는 것은 persian% 이름 필터로 작성할 수 있습니다.

나머지 필터는 어떤 방식으로든 결합하거나 생략할 수 있습니다. 예를 들어, openmembers 필터를 사용하여 지정된 최대 수의 구성원이 있는 모든 공개 그룹을 나열할 수 있습니다.

Client
1
2
const groups = await client.listGroups(session, "heroes%", 20); // fetch first 20 groups
console.info("List of groups:", groups);
Client
1
2
3
4
5
6
7
// Filter for group names which start with "heroes"
const string nameFilter = "heroes%";
var result = await client.ListGroupsAsync(session, nameFilter, 20);
foreach (var g in result.Groups)
{
    System.Console.WriteLine("Group name '{0}' count '{1}'", g.Name, g.EdgeCount);
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
auto successCallback = [this](NGroupListPtr list)
{
    for (auto& group : list->groups)
    {
        std::cout << "Group name '" << group.name << "' count " << group.edgeCount << std::endl;
    }
};

// Filter for group names which start with "heroes"
// fetch first 20 groups
client->listGroups(session, "heroes%", 20, "", successCallback);
Client
1
2
3
4
5
6
7
// Filter for group names which start with "heroes"
String nameFilter = "heroes%";
GroupList groups = client.listGroups(session, nameFilter, 20).get();

for (Group group : groups.getGroupsList()) {
    System.out.format("Group name %s count %s", group.getName(), group.getEdgeCount());
}
Client
1
2
3
4
5
6
7
8
9
var list : NakamaAPI.ApiGroupList = yield(client.list_groups_async(session, "heroes*", 20), "completed")

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

for g in list.groups:
    var group = g as NakamaAPI.ApiGroup
    print("Group: name %s, id %s", [group.name, group.id])
Client
1
2
curl "http://127.0.0.1:7350/v2/group?limit=20&name=heroes%25" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
GET /v2/group?limit=20&name=heroes%&cursor=<cursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
local result = client.list_groups("heroes*", "<cursor>", 20)

for _,group in ipairs(result.groups) do
  pprint(groups)
end

그룹 목록에 대한 메시지 응답에 커서가 있습니다. 커서를 사용하여 다음 결과 세트를 빠르게 검색할 수 있습니다.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/group?limit=20&name=heroes%25&cursor=somecursor" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
const groups = await client.listGroups(session, "heroes%", 20, cursor);
console.info("List of groups:", groups);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Filter for group names which start with "heroes"
const string nameFilter = "heroes%";
var result = await client.ListGroupsAsync(session, nameFilter, 20);

// If there are more results get next page.
if (result.Cursor != null)
{
    result = await client.ListGroupsAsync(session, nameFilter, 20, result.Cursor);

    foreach (var g in result.Groups)
    {
        System.Console.WriteLine("Group name '{0}' count '{1}'", g.Name, g.EdgeCount);
    }
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void YourClass::processGroupList(NGroupListPtr list)
{
    for (auto& group : list->groups)
    {
        std::cout << "Group name '" << group.name << "' count " << group.edgeCount << std::endl;
    }

    if (!list->cursor.empty())
    {
        // request next page
        requestHeroes(list->cursor);
    }
}

void YourClass::requestHeroes(const string& cursor)
{
    // Filter for group names which start with "heroes"
    // fetch first 20 groups
    client->listGroups(session, "heroes%", 20, cursor, std::bind(&YourClass::processGroupList, this, std::placeholders::_1));
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Filter for group names which start with "heroes"
String nameFilter = "heroes%";
GroupList groups = client.listGroups(session, nameFilter, 20).get();

if (groups.getCursor() != null) {
    groups = client.listGroups(session, nameFilter, 20, groups.getCursor()).get();

    for (Group group : groups.getGroupsList()) {
        System.out.format("Group name %s count %s", group.getName(), group.getEdgeCount());
    }
}
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
var list : NakamaAPI.ApiGroupList = yield(client.list_groups_async(session, "heroes*", 20), "completed")

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

for g in list.groups:
    var group = g as NakamaAPI.ApiGroup
    print("Group: name %s, id %s", [group.name, group.id])

var cursor = list.cursor

while cursor: # While there are more results get next page.
    list = yield(client.list_groups_async(session, "heroes*", 20, cursor), "completed")

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

    for g in list.groups:
        var group = g as NakamaAPI.ApiGroup
        print("Group: name %s, id %s", [group.name, group.id])

    cursor = list.cursor
Client
1
2
3
4
5
GET /v2/group?limit=20&name=heroes%&cursor=somecursor
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local cursor = nil

repeat
  local result = client.list_groups("heroes*", cursor, 20)

  if result.error then
    print(result.message)
    break
  end

  cursor = result.cursor

  for _,group in ipairs(result.groups) do
    pprint(groups)
  end
until not cursor

그룹 가입 #

가입할 그룹을 찾은 사용자는 구성원 가입을 요청할 수 있습니다. 공개 그룹은 권한 없이 가입할 수 있지만 비공개 그룹은 총괄 관리자 또는 관리자가 해당 사용자를 수락해야 합니다.

사용자가 그룹에 가입하거나 그룹에서 나가면 이벤트 메시지가 채팅 기록에 추가됩니다. 이렇게 하면 구성원은 그룹에서 변경된 내용을 쉽게 확인할 수 있습니다.

그룹에 속한 사용자는 그룹 채팅에 가입하고 메시지 기록에 액세스할 수 있습니다.

Client
1
2
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/join" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
const group_id = "<group id>";
await client.joinGroup(session, group_id);
console.info("Sent group join request", group_id);
Client
1
2
3
const string groupId = "<group id>";
await client.JoinGroupAsync(session, groupId);
System.Console.WriteLine("Sent group join request '{0}'", groupId);
Client
1
2
3
4
5
6
7
auto successCallback = []()
{
    std::cout << "Sent group join request" << std::endl;
};

string group_id = "<group id>";
client->joinGroup(session, group_id, successCallback);
Client
1
2
3
String groupid = "<group id>";
client.joinGroup(session, groupid).get();
System.out.format("Sent group join request %s", groupid);
Client
1
2
3
4
5
6
7
8
var group_id = "<group id>"
var join : NakamaAsyncResult = yield(client.join_group_async(session, group_id), "completed")

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

print("Sent group join request %s" % group_id)
Client
1
2
3
4
5
POST /v2/group/<group id>/join
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
8
9
local group_id = "<group_id>"
local result = client.join_group(group_id)

if result.error then
  print(result.message)
  return
end

print("Sent group join request", group_id)

그룹에 추가된 사용자에게는 인앱 알림이 전달됩니다. 비공개 그룹의 경우 사용자가 가입을 요청하면 관리자 또는 총괄 관리자에게 알림이 전달됩니다.

사용자의 그룹 나열 #

각 사용자는 구성원, 관리자 또는 총괄 관리자로 가입한 그룹을 나열할 수 있습니다. 목록에는 가입을 요청했지만 아직 수락되지 않은 그룹도 포함됩니다.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/user/<user id>/group" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
6
7
8
const userId = "<user id>";
const groups = await client.listUserGroups(session, userid);

groups.user_groups.forEach(function(userGroup){
  console.log("Group: name '%o' id '%o'.", userGroup.group.name, userGroup.group.id);
  // group.State is one of: SuperAdmin, Admin, Member, or Join.
  console.log("Group's state is %o.", userGroup.state);
});
Client
1
2
3
4
5
6
7
8
const string userId = "<user id>";
var result = await client.ListUserGroupsAsync(session, userId);

foreach (var ug in result.UserGroups)
{
    var g = ug.Group;
    System.Console.WriteLine("Group '{0}' role '{1}'", g.Id, ug.State);
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
auto successCallback = [](NUserGroupListPtr list)
{
    for (auto& userGroup : list->userGroups)
    {
        std::cout << "Group name " << userGroup.group.name << std::endl;
    }
};

string userId = "<user id>";
client->listUserGroups(session, userId, {}, {}, {}, successCallback);
Client
1
2
3
4
5
6
String userid = "<user id>";
UserGroupList userGroups = client.listUserGroups(session, userid).get();

for (UserGroupList.UserGroup userGroup : userGroups.getUserGroupsList()) {
    System.out.format("Group name %s role %d", userGroup.getGroup().getName(), userGroup.getState());
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var user_id = "<user id>"
var result : NakamaAPI.ApiUserGroupList = yield(client.list_user_groups_async(session, user_id), "completed")

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

for ug in result.user_groups:
    var g = ug.group as NakamaAPI.ApiGroup
    print("Group %s role %s", g.id, ug.state)
Client
1
2
3
4
5
GET /v2/user/<user id>/group
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
local user_id = "<user>"
local result = client.list_user_groups(user_id)

if result.error then
  print(result.message)
  return
end

for _,user_group in ipairs(result.user_groups) do
  pprint(user_group)
end

그룹 구성원 나열 #

사용자는 그룹에 속한 모든 구성원을 나열할 수 있습니다. 여기에는 비공개 그룹 가입을 요청했지만 아직 수락되지 않은 다른 사용자가 포함됩니다.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/group/<group id>/user" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
const group_id = "<group id>";
const users = await client.listGroupUsers(session, group_id);
console.info("Users in group:", users);
Client
1
2
3
4
5
6
7
const string groupId = "<group id>";
var result = await client.ListGroupUsersAsync(session, groupId);
foreach (var ug in result.UserGroups)
{
    var g = ug.Group;
    System.Console.WriteLine("group '{0}' role '{1}'", g.Id, ug.State);
}
Client
1
2
3
4
5
6
7
auto successCallback = [](NGroupUserListPtr list)
{
    std::cout << "Users in group: " << list->groupUsers << std::endl;
};

string group_id = "<group id>";
client->listGroupUsers(session, group_id, {}, {}, {}, successCallback);
Client
1
2
3
4
5
6
String groupid = "<group id>";
GroupUserList groupUsers = client.listGroupUsers(session, groupid).get();

for (GroupUserList.GroupUser groupUser : groupUsers.getGroupUsersList()) {
    System.out.format("Username %s role %d", groupUser.getUser().getUsername(), groupUser.getState());
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var group_id = "<group id>"
var member_list : NakamaAPI.ApiGroupUserList = yield(client.list_group_users_async(session, group_id), "completed")

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

for ug in member_list.group_users:
    var u = ug.user as NakamaAPI.ApiUser
    print("User %s role %s" % [u.id, ug.state])
Client
1
2
3
4
5
GET /v2/group/<group id>/user
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
local group_id = "<group_id>"
local result = client.list_group_users(group_id)

if result.error then
  print(result.message)
  return
end

for _,user in ipairs(result.group_users) do
  pprint(user)
end

그룹 생성 #

이름 및 기타 선택적 필드를 사용하여 그룹을 만들 수 있습니다. 이러한 선택적 필드는 사용자가 그룹을 나열하고 필터링할 때 사용됩니다. 그룹을 생성한 사용자는 그룹의 소유자이자 총괄 관리자입니다.

Client
1
2
3
4
5
6
7
8
curl -X POST "http://127.0.0.1:7350/v2/group" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "name": "pizza-lovers",
    "description": "pizza lovers, pineapple haters",
    "lang_tag": "en_US",
    "open": true
  }'
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const group_name = "pizza-lovers";
const description = "pizza lovers, pineapple haters";

const group = await client.createGroup(session, {
  name: group_name,
  description: description,
  lang_tag: "en_US",
  open: true
});

console.info("New group:", group);
Client
1
2
3
4
const string name = "pizza-lovers";
const string desc = "pizza lovers, pineapple haters";
var group = await client.CreateGroupAsync(session, name, desc);
System.Console.WriteLine("New group: {0}", group);
Client
1
2
3
4
5
6
7
8
auto successCallback = [](const NGroup& group)
{
    std::cout << "New group ID: " << group.id << std::endl;
};

string group_name = "pizza-lovers";
string description = "pizza lovers, pineapple haters";
client->createGroup(session, group_name, description, "", "en_US", true, {}, successCallback);
Client
1
2
3
4
String name = "pizza-lovers";
String desc = "pizza lovers, pineapple haters";
Group group = client.createGroup(session, name, desc).get();
System.out.format("New group %s", group.getId());
Client
1
2
3
4
5
6
7
8
9
var group_name = "pizza-lovers"
var group_desc = "pizza lovers, pineapple haters"
var group : NakamaAPI.ApiGroup = yield(client.create_group_async(session, group_name, group_desc), "completed")

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

print("New group: %s" % group)
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
POST /v2/group
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "name": "pizza-lovers",
  "description": "pizza lovers, pineapple haters",
  "lang_tag": "en_US",
  "open": true
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
local avatar_url = ""
local description = "pizza lovers, pineapple haters"
local lang_tag = "en-gb"
local max_count = 10
local name = "pizza-lovers"
local open = true

local result = client.create_group(avatar_url, description, lang_tag, max_count, name, open)

if result.error then
  print(result.message)
  return
end

서버 측 코드로 그룹을 생성할 수도 있습니다. 이렇게 하면 그룹을 다른 레코드 또는 기능과 함께 생성해야 하는 경우에 매우 유용할 수 있습니다.

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
local nk = require("nakama")

local metadata = { -- Add whatever custom fields you want.
  my_custom_field = "some value"
}

local user_id = "<user id>"
local name = "pizza-lovers"
local creator_id = user_id
local lang = "en_US"
local description = "pizza lovers, pineapple haters"
local avatar_url = "url://somelink"
local open = true
local maxMemberCount = 100

local success, err = pcall(nk.group_create, user_id, name, creator_id, lang, description, avatar_url, open, metadata, maxMemberCount)

if (not success) then
  nk.logger_error(("Could not create group: %q"):format(err))
end
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
metadata := map[string]interface{}{
    "my_custom_field": "some value", // Add whatever custom fields you want.
}

userID := "<user id>"
creatorID := userID
name := "pizza-lovers"
description := "pizza lovers, pineapple haters"
langTag := "en"
open := true
avatarURL := "url://somelink"
maxCount := 100

if _, err := nk.GroupCreate(ctx, userID, name, creatorID, langTag, description, avatarURL, open, metadata, maxCount); err != nil {
    logger.Error("Could not create group: %s", err.Error())
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let userId = 'dcb891ea-a311-4681-9213-6741351c9994';
let creatorId = 'dcb891ea-a311-4681-9213-6741351c9994';
let name = 'Some unique group name';
let description = 'My awesome group.';
let lang = 'en';
let open = true;
let avatarURL = 'url://somelink';
let metadata = { custom_field: 'some_value' };
let maxCount = 100;

let group = {} as nkruntime.Group;

try {
    group = nk.groupCreate(userId, name, creatorId, lang, description, avatarURL, open, metadata, maxCount);
} catch (error) {
    // Handle error
}

그룹 메타데이터 #

group.metadata에 그룹에 대한 추가 필드를 저장할 수 있습니다. 이는 사용자들에게 공개적으로 제공하려는 데이터를 공유하고, 그룹 나열 및 필터링에 사용할 수 있는 추가 세부 정보를 제공할 수 있습니다.

메타데이터를 사용하여 기존 그룹 기능 이외에 새로운 기능을 생성할 수도 있습니다.

메타데이터는 그룹당 16KB로 제한되며 스크립트 런타임을 통해서만 설정할 수 있습니다.

다음 예는 그룹 메타데이터를 사용하여 추가된 역할로 Nakama의 그룹 구성원 권한을 확장하는 방법을 보여줍니다(모범 사례 가이드 참조). 특히, 다른 구성원을 그룹에서 추방하기 위해 그룹 구성원이 가져야 하는 경비원 역할에 대해 설명합니다.

Server
 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
// Assuming a group metadata structure as follows
const metadata = {
  roles: {
    '000d8152-3258-457b-905b-05a9223c5c8c': ['bouncer'],
    '2c0c8e80-fcbc-4b61-901a-dace129f45f5': ['bouncer', 'vip'],
  }
}

// Add a before hook to only allow bouncers to kick group users
let BeforeKickGroupUser: nkruntime.BeforeHookFunction<KickGroupUsersRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.KickGroupUsersRequest): nkruntime.KickGroupUsersRequest | void {
  const groups = nk.groupsGetId([data.groupId]);
  if (groups.length == 0) {
    logger.warn('Invalid group Id');
    return null;
  }
  
  // Only continue with the Kick request if the actioning user has the bouncer role
  const roles = groups[0].metadata.roles;
  if (roles && roles[ctx.userId] && roles[ctx.userId].includes('bouncer')) {
    return data;
  }

  logger.warn("you must be a bouncer to kick group members")
  return null;
};

// Register inside InitModule
initializer.registerBeforeKickGroupUsers(BeforeKickGroupUser);
Server
 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
38
39
40
41
42
43
44
// Assuming a group metadata structure as follows
type GroupMetadata struct {
	Roles map[string][]string `json:"roles"`
}

metadata := &GroupMetadata {
  Roles: map[string][]string{
    "000d8152-3258-457b-905b-05a9223c5c8c": { "bouncer" },
    "2c0c8e80-fcbc-4b61-901a-dace129f45f5": { "bouncer", "vip" },
  },
}

// Add a before hook to only allow bouncers to kick group users
if err := initializer.RegisterBeforeKickGroupUsers(func (ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.KickGroupUsersRequest) (*api.KickGroupUsersRequest, error) {
  userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
  if !ok {
    logger.Error("invalid user")
    return nil, runtime.NewError("invalid user", 13)
  }

  groups, err := nk.GroupsGetId(ctx, []string { in.GroupId })
  if err != nil || len(groups) == 0 {
    logger.Error("group not found")
    return nil, runtime.NewError("group not found", 5)
  }

  // Only continue with the Kick request if the actioning user has the bouncer role
  var metadata GroupMetadata
  if err := json.Unmarshal([]byte(groups[0].GetMetadata()), &metadata); err != nil {
    logger.Error("error deserializing metadata")
    return nil, runtime.NewError("error deserializing metadata", 13)
  }

  for _, role := range metadata.Roles[userId] {
    if role == "bouncer" {
      return in, nil
    }
  }

  return nil, runtime.NewError("you must be a bouncer to kick group members", 7)
}); err != nil {
  logger.Error("unable to register before kick group users hook: %v", err)
  return err
}
Server
 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
-- Assuming a group metadata structure as follows
local metadata = {
  ["roles"] = {
    ["000d8152-3258-457b-905b-05a9223c5c8c"] = { "bouncer" },
    ["2c0c8e80-fcbc-4b61-901a-dace129f45f5"] = { "bouncer", "vip" },
  }
}

-- Add a before hook to only allow bouncers to kick group users
local function before_kick_group_users(context, payload)
    local groups = nk.groups_get_id({ payload.group_id })
    if #groups == 0 then
        nk.logger_error("group not found")
        return nil
    end

    local roles = groups[1].metadata["roles"]
    if roles == nil or roles[context.user_id] == nil then
        nk.logger_error("no roles configured for user")
        return nil
    end

    for _, role in ipairs(roles[context.user_id]) do
        if role == "bouncer" then
            return payload
        end
    end

    nk.logger_error("you must be a bouncer to kick group members")
    return nil
end

nk.register_req_before(before_kick_group_users, "KickGroupUsers")

그룹 업데이트 #

그룹이 생성되면 해당 관리자는 선택적 필드를 업데이트할 수 있습니다.

Client
1
2
3
4
5
curl -X PUT "http://127.0.0.1:7350/v2/group/<group id>" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "description": "Better than Marvel Heroes!",
  }'
Client
1
2
3
4
const group_id = "<group id>";
const description = "Better than Marvel Heroes!";
const group = await client.updateGroup(session, group_id, { description: description });
console.info("Updated group:", group);
Client
1
2
3
4
const string groupId = "<group id>";
const string desc = "Better than Marvel Heroes!";
var group = await client.UpdateGroupAsync(session, groupId, null, desc);
System.Console.WriteLine("Updated group: {0}", group);
Client
1
2
3
4
5
6
7
8
auto successCallback = []()
{
    std::cout << "Updated group" << std::endl;
};

string group_id = "<group id>";
string description = "Better than Marvel Heroes!";
client->updateGroup(session, group_id, opt::nullopt, description, opt::nullopt, opt::nullopt, opt::nullopt, successCallback);
Client
1
2
3
4
String groupid = "<group id>";
String desc = "Better than Marvel Heroes!";
client.updateGroup(session, groupid, null, desc).get();
System.out.format("Updated group %s", groupid);
Client
1
2
3
4
5
6
7
8
9
var group_id = "<group id>"
var description = "Better than Marvel Heroes!"
var update : NakamaAsyncResult = yield(client.update_group_async(session, group_id, null, description), "completed")

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

print("Updated group")
Client
1
2
3
4
5
6
7
8
PUT /v2/group/<group id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "description": "I was only kidding. Basil sauce ftw!",
}
Client
1
2
3
4
5
6
7
8
local group_id = "<group id>"
local avatar_url = "http://example.com/example.jpg"

local result = client.update_group(group_id, avatar_url)

if result.error then
  print(result.message)
end

그룹 크기 업데이트 #

그룹 크기 업데이트는 서버를 통해서만 정식으로 수행할 수 있는 작업입니다.

Server
1
2
3
4
5
6
7
8
local nk = require("nakama")

local new_max_size = 50
local success, err = pcall(nk.group_update("<GroupId>", nil, "", "", "", "", "", nil, nil, new_max_size))

if (not success) then
  nk.logger_error(("Could not update group: %q"):format(err))
end
Server
1
2
3
4
5
6
newMaxSize := 50
open := true

if err := nk.GroupUpdate(ctx, "<GroupId>", "", "", "", "", "", open, nil, newMaxSize); err != nil {
    return err
}
Server
1
2
let newMaxSize = 50
nk.groupUpdate("<GroupId>", "", null, null, null, null, null, null, null, newMaxSize);

그룹 나가기 #

사용자는 그룹에서 나갈 수 있으며 나가면 더 이상 그룹 채팅에 가입하거나 메시지 기록을 읽을 수 없습니다. 사용자가 총괄 관리자인 경우 해당 그룹에 다른 총괄 관리자가 한 명 이상 있을 때만 나갈 수 있습니다.

사용자가 그룹을 나가면 다른 구성원이 읽을 수 있는 이벤트 메시지가 그룹 채팅에 생성됩니다.

Client
1
2
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/leave" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
const group_id = "<group id>";
await client.leaveGroup(session, group_id);
Client
1
2
const string groupId = "<group id>";
await client.LeaveGroupAsync(session, groupId);
Client
1
2
string groupId = "<group id>";
client->leaveGroup(session, groupId);
Client
1
2
String groupId = "<group id>";
client.leaveGroup(session, groupId).get();
Client
1
2
3
4
5
6
7
8
var group_id = "<group id>"
var leave : NakamaAsyncResult = yield(client.leave_group_async(session, group_id), "completed")

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

print("Group left")
Client
1
2
3
4
5
POST /v2/group/<group id>/leave
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
local group_id = "<group id>"
local result = client.leave_group(group_id)

if result.error then
  print(result.message)
end

그룹 관리 #

각 그룹은 한 명 이상의 총괄 관리자 또는 관리자가 관리합니다. 이러한 사용자는 선택적 필드를 변경하고, 새 구성원을 수락 또는 거부하고, 구성원 또는 다른 관리자를 제거하고, 다른 구성원을 관리자로 승격시킬 수 있는 권한이 있는 구성원입니다.

그룹에는 한 명 이상의 총괄 관리자가 있어야 합니다. 마지막 총괄 관리자는 나가기 전에 다른 구성원을 승격해야 합니다.

새 구성원 수락 #

사용자가 비공개 그룹에 가입하면 관리자가 해당 사용자를 수락하거나 거부할 때까지 가입 요청이 생성됩니다. 총괄 관리자 또는 관리자는 사용자를 그룹에 수락할 수 있습니다.

Client
1
2
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/add?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
270

Client
1
2
3
const group_id = "<group id>";
const user_id = "<user id>";
await client.addGroupUsers(session, group_id, [user_id]);
Client
1
2
3
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.AddGroupUsersAsync(session, groupId, userIds);
Client
1
2
3
4
5
6
7
8
auto successCallback = []()
{
    std::cout << "added user to group" << std::endl;
};

string group_id = "<group id>";
string user_id = "<user id>";
client->addGroupUsers(session, group_id, { user_id }, successCallback);
Client
1
2
3
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.addGroupUsers(session, groupid, userIds).get();
Client
1
2
3
4
5
6
7
8
9
var group_id = "<group id>"
var user_ids = ["<user id>"]
var accept : NakamaAsyncResult = yield(client.add_group_users_async(session, group_id, user_ids), "completed")

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

print("User added")
Client
1
2
3
4
5
POST /v2/group/<group id>/add?user_ids=<user id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
local group_id = "<group id>"
local user_ids = { "<user id>" }
local result = client.add_group_users(group_id, user_ids)

if result.error then
  print(result.message)
end

그룹에 추가된 사용자에게는 인앱 알림이 전달됩니다. 비공개 그룹의 경우 가입 요청 알림이 관리자에게 전달됩니다.

이 사용자의 그룹 가입을 거부하려면 해당 사용자를 추방해야 합니다.

구성원 승격 #

관리자는 그룹의 다른 구성원을 관리자로 승격할 수 있습니다. 이렇게 하면 해당 구성원에게 그룹을 관리할 수 있는 동일한 권한이 부여됩니다. 그룹에 한 명 이상의 관리자가 있을 수 있습니다.

Client
1
2
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/promote?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
Client
1
2
3
const group_id = "<group id>";
const user_id = "<user id>";
await client.promoteGroupUsers(session, group_id, [user_id]);
Client
1
2
3
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.PromoteGroupUsersAsync(session, groupId, userIds);
Client
1
2
3
4
5
6
7
8
auto successCallback = []()
{
    std::cout << "user has been promoted" << std::endl;
};

string group_id = "<group id>";
string user_id = "<user id>";
client->promoteGroupUsers(session, group_id, { user_id }, successCallback);
Client
1
2
3
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.promoteGroupUsers(session, groupid, userIds).get();
Client
1
2
3
4
5
6
7
8
9
var group_id = "<group id>"
var user_ids = ["<user id>"]
var promote : NakamaAsyncResult = yield(client.promote_group_users_async(session, group_id, user_ids), "completed")

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

print("User promoted")
Client
1
2
3
4
5
POST /v2/group/<group id>/promote?user_ids=<user id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
local group_id = "<group id>"
local user_ids = { "<user id>" }
local result = client.promote_group_users(group_id, user_ids)

if result.error then
  print(result.message)
end

구성원 강등 #

관리자는 그룹에서 다른 구성원의 역할을 강등할 수 있습니다. 이렇게 하면 구성원의 현재 권한이 취소되고 강등된 역할에서 사용할 수 있는 권한이 부여됩니다. 그룹에서 이미 역할이 가장 낮은 구성원은 강등의 영향을 받지 않습니다.

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Client
1
2
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/demote?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
Client
1
2
3
const group_id = "<group id>";
const user_id = "<user id>";
await client.demoteGroupUsers(session, group_id, [user_id]);
Client
1
2
3
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.DemoteGroupUsersAsync(session, groupId, userIds);
Client
1
2
3
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.demoteGroupUsers(session, groupid, userIds).get();
Client
1
2
3
4
5
POST /v2/group/<group id>/demote?user_ids=<user id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
local group_id = "<group id>"
local user_ids = { "<user id>" }
local result = client.demote_group_users(group_id, user_ids)

if result.error then
  print(result.message)
end

구성원 추방 #

관리자 또는 총괄 관리자는 그룹에서 구성원을 추방할 수 있습니다. 사용자는 제거되지만 사용자가 금지되거나 그룹이 비공개인 경우가 아니면 나중에 다시 참여할 수 있으며 이 경우 관리자가 재가입 요청을 수락해야 합니다.

사용자가 그룹에서 제거되어도 다른 그룹에 가입할 수 있습니다. 나쁜 사용자의 경우 그룹에서 추방하고 그룹 또는전체 서버에 다시 가입하는 것을 금지해야 합니다. 이렇게 하면 사용자가 서버에 연결하고 전혀 상호 작용할 수 없습니다.

Client
1
2
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/kick?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
Client
1
2
3
const group_id = "<group id>";
const user_id = "<user id>";
await client.kickGroupUsers(session, group_id, [user_id]);
Client
1
2
3
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.KickGroupUsersAsync(session, groupId, userIds);
Client
1
2
3
4
5
6
7
8
auto successCallback = []()
{
    std::cout << "user has been kicked" << std::endl;
};

string group_id = "<group id>";
string user_id = "<user id>";
client->kickGroupUsers(session, group_id, { user_id }, successCallback);
Client
1
2
3
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.kickGroupUsers(session, groupid, userIds).get();
Client
1
2
3
4
5
6
7
8
9
var group_id = "<group id>"
var user_ids = ["<user id>"]
var kick : NakamaAsyncResult = yield(client.kick_group_users_async(session, group_id, user_ids), "completed")

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

print("User kicked")
Client
1
2
3
4
5
POST /v2/group/<group id>/kick?user_ids=<user id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
local group_id = "<group id>"
local user_ids = { "<user id>" }
local result = client.kick_group_users(group_id, user_ids)

if result.error then
  print(result.message)
end

그룹 구성원 금지 #

관리자 또는 총괄 관리자는 그룹에서 구성원을 금지할 수 있습니다. 사용자는 그룹에서 추방되며 다시 가입하거나 다시 가입을 요청할 수 없습니다.

Nakama 콘솔 또는 런타임 코드 기능을 통해 이 사용자의 금지를 해제할 수 있습니다.

Client
1
2
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/ban?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
Client
1
2
3
const group_id = "<group id>";
const user_id = "<user id>";
await client.banGroupUsers(session, group_id, [user_id]);
Client
1
2
3
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.BanGroupUsersAsync(session, groupId, userIds);
Client
1
2
3
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.banGroupUsers(session, groupid, userIds).get();
Client
1
2
3
4
5
POST /v2/group/<group id>/ban?user_ids=<user id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
local group_id = "<group id>"
local user_ids = { "<user id>" }
local result = client.ban_group_users(group_id, user_ids)

if result.error then
  print(result.message)
end

그룹 제거 #

모든 구성원을 해산할 수 있는 총괄 관리자 중 한 명만이 그룹을 제거할 수 있습니다. 그룹이 제거되면 해당 이름을 다시 사용하여 새 그룹을 만들 수 있습니다.

Client
1
2
curl -X DELETE "http://127.0.0.1:7350/v2/group/<group id>" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
const group_id = "<group id>";
await client.deleteGroup(session, group_id);
Client
1
2
const string groupId = "<group id>";
await client.DeleteGroupAsync(session, groupId);
Client
1
2
3
4
5
6
7
auto successCallback = []()
{
    std::cout << "group deleted" << std::endl;
};

string group_id = "<group id>";
client->deleteGroup(session, group_id, successCallback);
Client
1
2
String groupid = "<group id>";
client.deleteGroup(session, groupid).get();
Client
1
2
3
4
5
6
7
8
var group_id = "<group id>"
var remove : NakamaAsyncResult = yield(client.delete_group_async(session, group_id), "completed")

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

print("Group removed")
Client
1
2
3
4
5
DELETE /v2/group/<group id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
local group_id = "<group id>"
local result = client.delete_group(group_id)

if result.error then
  print(result.message)
end

Related Pages