# 群组

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/groups/
**Summary:** 群组使玩家可以作为一个小组或社区一同聊天或玩游戏。这些玩家群组可以是公开的，也可以是私人的，需要得到群组管理员的批准才能加入。

---


# 群组

群组或家族将一群用户聚集在一起，形成一个小社区或团队。

群组由超级管理员、管理员和成员组成。群组可以是公开的，也可以是私人的，这决定了当用户列出群组时，它是否出现在结果中。私人群组类似于WhatsApp群组的运作方式。只有当群组的一位管理员邀请用户加入时，才能添加用户。

群组也有最大成员数。如果群组是由客户端创建的，则默认设置为100；如果群组是通过代码运行时程序创建的，那么可以超过此值。

<!-- more -->

群组用户有四种状态：

| 代码 | 用途      |
|------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 0    | 超级管理员   | 任何群组都必须拥有至少一位超级管理员。超级管理员拥有管理员的所有权限，另外还可以删除群组和升级管理员成员。 |
| 1    | 管理员        | 可以有一个或多个管理员。管理员可以更新群组，也可以接受、踢出、升级、降级、封禁或添加成员。                                               |
| 2    | 成员       | 群组常规成员。无法接受新用户的加入请求。                                                                                                |
| 3    | 加入请求 | 新用户发来的新的加入请求。这不会被计入群组成员的最大数量。                                                                       |

## 列出和过滤群组

可以使用许多可选过滤器列出群组：`name`, `lang_tag`, `open` 和（值为） `members`。如果省略所有过滤器，操作将列出所有现有群组。

过滤器`name`不区分大小写，与其余过滤器相互排斥。它可以帮助用户按名称查找特定的群组，并支持使用`%`通配符匹配后缀部分。例如，查找前缀为“Persian”的群组将被写为`persian%`名称过滤器。

剩余的过滤器可以以任何方式组合或省略，例如，我们可以使用`open`和`members`过滤器最多列出所有拥有指定数量成员的开放群组。

{{< code type="client" >}}
```javascript
const groups = await client.listGroups(session, "heroes%", 20); // fetch first 20 groups
console.info("List of groups:", groups);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// 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);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
// 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());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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])
```
{{< / code >}}

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/group?limit=20&name=heroes%25" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local result = client.list_groups("heroes*", "<cursor>", 20)

for _,group in ipairs(result.groups) do
  pprint(groups)
end
```
{{< / code >}}

群组列表的消息响应包含一个游标。游标可用于快速检索下一组结果。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/group?limit=20&name=heroes%25&cursor=somecursor" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const groups = await client.listGroups(session, "heroes%", 20, cursor);
console.info("List of groups:", groups);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// 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);
    }
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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));
}
```
{{< / code >}}

{{< code type="client" >}}
```java
// 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());
    }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

## 加入群组

用户找到想要加入的群组时，可以请求成为成员。公开群组不需要任何就可以加入，而私人群组需要[超级管理员或管理员接受](#accept-new-members)用户才可加入。

当用户加入或退出群组时，事件消息会被添加到聊天历史记录中。成员可以轻松查看群组内的变动。

群组中的用户可以加入[群组聊天](../chat/#groups)并查看[群组聊天的历史消息](../chat/#message-history)。

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/join" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
await client.joinGroup(session, group_id);
console.info("Sent group join request", group_id);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
await client.JoinGroupAsync(session, groupId);
System.Console.WriteLine("Sent group join request '{0}'", groupId);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = []()
{
    std::cout << "Sent group join request" << std::endl;
};

string group_id = "<group id>";
client->joinGroup(session, group_id, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
client.joinGroup(session, groupid).get();
System.out.format("Sent group join request %s", groupid);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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)
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/group/<group id>/join
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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)
```
{{< / code >}}
用户在被添加到群组时将会[在应用程序内收到通知](../notifications/)。在私人群组中，当用户请求加入时，管理员或超级管理员将收到通知。

## 列出用户群组

每位用户都可以列出自己作为成员或作为管理员或超级管理员加入的群组。另外，还可以列出自己已经发送加入请求但尚未被接受的群组。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/user/<user id>/group" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
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);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
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);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
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());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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)
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/user/<user id>/group
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

## 列出群组成员

用户可以列出所在群组的所有成员。其中包括已请求加入私人群组但尚未被接受的其他用户。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/group/<group id>/user" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
const users = await client.listGroupUsers(session, group_id);
console.info("Users in group:", users);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
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);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NGroupUserListPtr list)
{
    std::cout << "Users in group: " << list->groupUsers << std::endl;
};

string group_id = "<group id>";
client->listGroupUsers(session, group_id, {}, {}, {}, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
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());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/group/<group id>/user
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

## 创建群组

可以使用名称和其他可选字段创建群组。这些可选字段在用户[列出和过滤群组时使用](#list-and-filter-groups)。创建群组的用户是群组的所有者和超级管理员。

{{< code type="client" >}}
```bash
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
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
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);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
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);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
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());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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)
```
{{< / code >}}

{{< code type="client" >}}
```shell
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
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

您还可以使用服务器端代码创建群组。创建群组时，若必须有其他一些记录或特征，这可能很有用。

{{< code type="server" >}}
```lua
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
```
{{< / code >}}

{{< code type="server" >}}
```go
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())
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
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
}
```
{{< / code >}}

### 群组元数据

您可以在`group.metadata`中存储群组的其他字段。这有助于共享您希望公开给用户的数据，并提供可用于列出和过滤群组的其他详细信息。

元数据还可用于在现有群组功能的基础上[创建新功能](./best-practices/#use-metadata-to-layer-new-functionality)。

每个群组的元数据限制在16KB，并且只能通过[脚本运行](../../server-framework/)进行设置。

以下示例显示了您如何使用群组元数据来扩展Nakama中群组成员的权限和增加角色（如[最佳实践指南](./best-practices/#use-metadata-to-layer-new-functionality)中所述）。具体来说，我们将引入**保镖**角色的概念，群组成员必须拥有该角色才能踢出群组中的另一名成员。

{{< code type="server" >}}
```typescript
// 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);
```
{{< / code >}}

{{< code type="server" >}}
```go
// 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
}
```
{{< / code >}}


{{< code type="server" >}}
```lua
-- 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")
```
{{< / code >}}

## 更新群组

创建群组后，群组管理员可以更新可选字段。

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/group/<group id>" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "description": "Better than Marvel Heroes!",
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
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);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
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);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
String desc = "Better than Marvel Heroes!";
client.updateGroup(session, groupid, null, desc).get();
System.out.format("Updated group %s", groupid);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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")
```
{{< / code >}}

{{< code type="client" >}}
```shell
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!",
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

## 更新群组规模

更新群组规模操作只能通过服务器授权执行。

{{< code type="server" >}}
```lua
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
```
{{< / code >}}

{{< code type="server" >}}
```go
newMaxSize := 50
open := true

if err := nk.GroupUpdate(ctx, "<GroupId>", "", "", "", "", "", open, nil, newMaxSize); err != nil {
    return err
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let newMaxSize = 50
nk.groupUpdate("<GroupId>", "", null, null, null, null, null, null, null, newMaxSize);
```
{{< / code >}}

## 退出群组

用户可以退出群组，退出后无法再[加入群组聊天](../chat/#groups)或读取[消息历史记录](../chat/#message-history)。如果用户为超级管理员，只有当群组中除此位用户外至少有一位超级管理员时才可以退出群组。

任何用户退出群组时都会在群组聊天中生成事件消息，其他成员可以读取该消息。

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/leave" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
await client.leaveGroup(session, group_id);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
await client.LeaveGroupAsync(session, groupId);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
string groupId = "<group id>";
client->leaveGroup(session, groupId);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupId = "<group id>";
client.leaveGroup(session, groupId).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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")
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/group/<group id>/leave
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local group_id = "<group id>"
local result = client.leave_group(group_id)

if result.error then
  print(result.message)
end
```
{{< / code >}}

## 管理群组

每个群组由一位或多位超级管理员或管理员管理。这些用户是具有更改可选字段、接受或拒绝新成员、移除成员或其他管理员、将其他成员升级为管理员权限的成员。

{{< note "important" >}}
每个群组必须拥有至少一位超级管理员。群组的最后一位超级管理员必须要将另一位成员[升级](../groups/#promote-a-member)为超级管理员才可以[退出](../groups/#leave-a-group)群组。
{{< / note >}}

### 接受新成员

用户加入私人群组时会创建加入请求，等待管理员接受或拒绝用户。超级管理员或管理员可以接受用户加入群组。

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/add?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
const user_id = "<user id>";
await client.addGroupUsers(session, group_id, [user_id]);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.AddGroupUsersAsync(session, groupId, userIds);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.addGroupUsers(session, groupid, userIds).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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")
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

用户在被添加到群组时将会[在应用程序内收到通知](../notifications/)。在私人群组中，管理员将收到加入请求通知。

要拒绝用户加入群组，您应该[将他们踢出](#kick-a-member)。

### 升级成员

一位管理员可以将群组的另一位成员升级为管理员。这将同样赋予此成员[管理群组](#manage-groups)的权限。一个群组可以有一个或多个管理员。

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/promote?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
const user_id = "<user id>";
await client.promoteGroupUsers(session, group_id, [user_id]);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.PromoteGroupUsersAsync(session, groupId, userIds);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.promoteGroupUsers(session, groupid, userIds).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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")
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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 >}}

### 降级成员

一位管理员可以将群组的另一位成员降一个等级。这将撤消成员的当前权限，并为成员分配降级后可用的权限。降级不会影响群组中已处于最低等级的成员。

{{< missing type="client" lang="cpp" />}}

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/demote?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
const user_id = "<user id>";
await client.demoteGroupUsers(session, group_id, [user_id]);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.DemoteGroupUsersAsync(session, groupId, userIds);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.demoteGroupUsers(session, groupid, userIds).get();
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

### 踢出成员

管理员或超级管理员可以将成员踢出群组。被移除的用户可以重新加入，除非用户[被封禁](#ban-a-group-member)，或者群组是私人群组，必须由管理员接受重新加入请求。

用户被从一个群组中移除不会对用户加入其它群组造成影响。有时，需要将恶意用户踢出群组，并被禁止重新加入[群组](#ban-a-group-member)或[整个服务器](../friends/#ban-a-user)。这将使用户无法连接到服务器进行任何互动。

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/kick?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
const user_id = "<user id>";
await client.kickGroupUsers(session, group_id, [user_id]);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.KickGroupUsersAsync(session, groupId, userIds);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.kickGroupUsers(session, groupid, userIds).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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")
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

### 封禁群组成员

管理员或超级管理员可以封禁成员加入群组。用户被踢出群组后，无法重新加入，甚至无法请求重新加入。

可以通过Nakama控制台或运行时代码功能解除对用户的封禁。

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/group/<group id>/ban?user_ids=<user id>" \
    -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
const user_id = "<user id>";
await client.banGroupUsers(session, group_id, [user_id]);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
var userIds = new[] {"<user id>"};
await client.BanGroupUsersAsync(session, groupId, userIds);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
String[] userIds = new String[] {"<user id>"};
client.banGroupUsers(session, groupid, userIds).get();
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

## 移除群组

群组只能由超级管理员移除，移除群组将解散所有成员。群组被移除后，可以重新使用这个群组的名称[创建新的群组](#create-a-group)。

{{< code type="client" >}}
```bash
curl -X DELETE "http://127.0.0.1:7350/v2/group/<group id>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const group_id = "<group id>";
await client.deleteGroup(session, group_id);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string groupId = "<group id>";
await client.DeleteGroupAsync(session, groupId);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = []()
{
    std::cout << "group deleted" << std::endl;
};

string group_id = "<group id>";
client->deleteGroup(session, group_id, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String groupid = "<group id>";
client.deleteGroup(session, groupid).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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")
```
{{< / code >}}

{{< code type="client" >}}
```shell
DELETE /v2/group/<group id>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local group_id = "<group id>"
local result = client.delete_group(group_id)

if result.error then
  print(result.message)
end
```
{{< / code >}}
