# 好友

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/friends/
**Summary:** 可通过好友建立社交社区。用户可以将其他用户添加到自己的好友列表中，查看在线好友或好友最后一次在线的时间，一起实时聊天，并在游戏或协作中一起互动。

---


# 好友

可通过好友建立社交社区。用户可以将其他用户添加到自己的好友列表中，查看在线好友或好友最后一次在线的时间，一起实时聊天，并在游戏或协作中一起互动。

每个用户可以通过以下方式建立好友列表：在社交网络中已经认识的人、发送的朋友请求、收到的请求以及服务器推荐的可能认识的人。这些信息存储在系统内的社交图谱中，通过此类信息可以与其他用户有效互动。非常类似于Twitter或Facebook的运作方式。

必须认真维护任何社交社区都，防止垃圾邮件或滥用行为。为了帮助解决这个问题，用户还可以屏蔽其不想再联系的用户，服务器也可以通过服务器端的代码来禁止用户，从而完全禁用一个账户。

{{< note "important" >}}
Nakama是一个常见的日语词汇，直译为好友或同志。有些人认为这个词的意思是“比家人更亲密的人”，但官方未作此类解释。我们觉得它表达了我们希望开发人员在游戏和应用程序中建立的社交社区！
{{< / note >}}

## 添加好友

用户可以通过对方用户ID或用户名添加一位或多位好友。被添加的用户在确认好友请求之前不会在列表中标记为好友。收到请求的用户可以通过回加用户进行确认。

发送好友请求或添加用户后，将在应用程序内发送通知。在[应用程序内发送通知](../notifications/#receive-notifications)部分查看更多信息。

{{< code type="client" >}}
```javascript
var ids = ["user-id1", "user-id2"];
var usernames = ["username1"];
await client.addFriends(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var ids = new[] {"user-id1", "user-id2"};
var usernames = new[] {"username1"};
await client.AddFriendsAsync(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
vector<string> ids = { "user-id1", "user-id2" };
vector<string> usernames = { "username1" };
client->addFriends(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```java
List<String> ids = Arrays.asList("user-id1", "user-id2");
String[] usernames = new String[] {"username1"};
client.addFriends(session, ids, usernames).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var ids = ["user-id1", "user-id2"]
var usernames = ["username1"]
var result : NakamaAsyncResult = yield(client.add_friends_async(session, ids, usernames), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local ids = { "user-id1", "user-id2" }
local usernames = { "username1" }
local result = client.add_friends(ids, usernames)

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

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/friend?ids=user-id1&ids=user-id2&usernames=username1" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/friend?ids=user-id1&ids=user-id2&usernames=username1
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

两个用户互相添加为好友后，即可轻松在1对1频道中发起实时聊天。在[实时聊天](../chat/)部分查看更多信息。

### 导入好友

使用Facebook、Steam或其他社交网络帐户[注册](../authentication/#social-providers)或[链接](../authentication/#link-or-unlink)的用户可以选择将相应账户中的好友导入好友列表。

或者，通过Nakama的按需导入好友的支持，让用户可随时执行此导入。

#### Facebook

随时将用户的Facebook好友导入您的应用程序，导入时还可以选择`reset`导入其当前的好友列表。

{{< code type="client" >}}
```javascript
const token = "<facebookAuthToken>";
const reset = true;
await client.importFacebookFriends(token, reset);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var token = "<facebookAuthToken>";
var reset = true;
await client.ImportFacebookFriendsAsync(token, reset);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
string token = "<facebookAuthToken>";
bool reset = true;
client->importFacebookFriends(token, reset);
```
{{< / code >}}

{{< code type="client" >}}
```java
String token = "<facebookAuthToken>";
Bool reset = true;
client.importFacebookFriends(token, reset).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var token = "<facebookAuthToken>"
var reset = true
var result : NakamaAsyncResult = yield(client.import_facebook_friends(token, reset), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local token = "<facebookAuthToken>"
local reset = true
local result = client.import_facebook_friends(token, nil, reset)

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

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/friend/facebook" \
  -H 'Authorization: Bearer <session token>'
  -d '{"token": <facebookAuthToken>, "reset": true}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/friend/facebook
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "token": "<facebookAuthToken>",
  "reset": true
}
```
{{< / code >}}

#### Steam

随时将用户的Facebook好友导入您的应用程序，在导入时还可以选择`reset`导入其当前的好友列表。

{{< code type="client" >}}
```javascript
const steamToken = "<steamToken>";
const reset = true;
await client.importSteamFriends(steamToken, reset);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var steamToken = "<steamToken>";
var reset = true;
await client.ImportSteamFriendsAsync(steamToken, reset);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
string steamToken = "<steamToken>";
bool reset = true;
client->importSteamFriends(steamToken, reset);
```
{{< / code >}}

{{< code type="client" >}}
```java
String steamToken = "<steamToken>";
Bool reset = true;
client.importSteamFriends(steamToken, reset).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var steam_token = "<steam_token>"
var reset = true
var result : NakamaAsyncResult = yield(client.import_steam_friends(steam_token, reset), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local steam_token = "<steam_token>";
local reset = true
local result = client.import_steam_friends(steam_token, nil, reset)

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

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/friend/steam" \
  -H 'Authorization: Bearer <session token>'
  -d '{"steamToken": <steamToken>, "reset": true}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/friend/steam
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "steamToken": "<steamToken>",
  "reset": true
}
```
{{< / code >}}

## 列出好友

您可以列出一个用户的所有好友、被屏蔽的用户、收到的朋友请求（被邀请）以及发出的邀请。这些状态会在好友列表中一同被返回，以便在UI中轻松显示。
一个好友列表将返回的页面最多包含1000个好友。将被返回的游标传递给后续列出调用，以便检索更多的好友页面。

{{< code type="client" >}}
```javascript
const friends = await client.listFriends(session);
console.info("Successfully retrieved friend list:", friends);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ListFriendsAsync(session);

foreach (var f in result.Friends)
{
    System.Console.WriteLine("Friend '{0}' state '{1}'", f.User.Username, f.State);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NFriendsPtr friends)
{
    std::cout << "Successfully retrieved friend list: " << friends->friends.size() << std::endl;
};

client->listFriends(session, {}, {}, {}, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
Friends friends = client.listFriends(session).get();

for (Friend friend : friends.getFriendsList()) {
  System.out.format("Friend %s state %d", friend.getUser().getUsername(), friend.getState());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var list : NakamaAPI.ApiFriendList = yield(client.list_friends_async(session), "completed")

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

for f in list.friends:
    var friend = f as NakamaAPI.ApiFriend
    print("User %s, status %s" % [friend.user.id, friend.state])
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local result = client.list_friends()

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

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

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

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

## 移除好友

用户可以删除好友，拒绝收到的邀请，取消发送好友请求，或取消屏蔽用户。用户被取消屏蔽后，将被从好友列表中完全移除。如要重新添加这些用户，用户都必须再一次添加对方。

与添加好友的工作方式类似，我们重用删除好友来取消或撤销当前与另一用户的任何好友状态。

{{< code type="client" >}}
```javascript
var ids = ["user-id1", "user-id2"];
var usernames = ["username1"];
await client.deleteFriends(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var ids = new[] {"user-id1", "user-id2"};
var usernames = new[] {"username1"};
await client.DeleteFriendsAsync(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
vector<string> ids = { "user-id1", "user-id2" };
vector<string> usernames = { "username1" };
client->deleteFriends(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```java
List<String> ids = Arrays.asList("user-id1", "user-id2");
String[] usernames = new String[] {"username1"};
client.deleteFriends(session, ids, usernames).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var ids = ["user-id1", "user-id2"]
var usernames = ["username1"]
var remove : NakamaAsyncResult = yield(client.delete_friends_async(session, ids, usernames), "completed")

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

print("Remove friends: user ids %s, usernames %s" % [ids, usernames])
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local ids = { "user-id1", "user-id2" }
local usernames = { "username1" }
local result = client.delete_friends(ids, usernames)

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

{{< code type="client" >}}
```bash
curl -X DELETE "http://127.0.0.1:7350/v2/friend?ids=user-id1&ids=user-id2&usernames=username1" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```shell
DELETE /v2/friend?ids=user-id1&ids=user-id2&usernames=username1
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

## 屏蔽好友

如果您屏蔽用户，您可以阻止这个用户使用1对1聊天或其他社交功能。想要屏蔽别人的用户应发送消息。稍后可以通过[好友删除](#remove-friends)消息解除屏蔽。

被屏蔽的用户不会知道自己被谁屏蔽。该用户可以继续添加好友并与其他用户互动。

{{< code type="client" >}}
```javascript
var ids = ["user-id1", "user-id2"];
var usernames = ["username1"];
await client.blockFriends(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var ids = new[] {"user-id1", "user-id2"};
var usernames = new[] {"username1"};
await client.BlockFriendsAsync(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
vector<string> ids = { "user-id1", "user-id2" };
vector<string> usernames = { "username1" };
client->blockFriends(session, ids, usernames);
```
{{< / code >}}

{{< code type="client" >}}
```java
List<String> ids = Arrays.asList("user-id1", "user-id2");
String[] usernames = new String[] {"username1"};
client.blockFriends(session, ids, usernames).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var ids = ["user-id1", "user-id2"]
var usernames = ["username1"]
var block : NakamaAsyncResult = yield(client.block_friends_async(session, ids, usernames), "completed")

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

print("Remove friends: user ids %s, usernames %s" % [ids, usernames])
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local ids = { "user-id1", "user-id2" }
local usernames = { "username1" }
local result = client.block_friends(ids, usernames)

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

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/friend/block?ids=user-id1&ids=user-id2&usernames=username1" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/friend/block?ids=user-id1&ids=user-id2&usernames=username1
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

### 封禁用户

可以使用[服务器端代码](../../server-framework/)封禁用户。

最好由玩家社区内的版主系统使用。您可以为特定用户分配发送RPC的能力，以便永久封禁用户。

{{< code type="server" >}}
```lua
local nk = require("nakama")

local bad_users = {"someuserid", "anotheruserid"}
local success, err = pcall(nk.users_ban_id, bad_users)

if (not success) then
  nk.logger_error(("Ban failed: %q"):format(err))
end
```
{{< / code >}}

{{< code type="server" >}}
```go
if err := nk.UsersBanId(ctx, []string{
    "someruserid",
    "anotheruserid",
}); err != nil {
    logger.Error("Ban failed: %s", err.Error())
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let badUsers = ['someuserid', 'anotheruserid'];

try {
  nk.usersBanId(badUsers);
} catch (error) {
  // Handle error
}
```
{{< / code >}}

封禁用户将阻止他们在以后连接到服务器并进行交互，但这**并不**意味着[退出登录](../session/#session-logout)或[断开](../../server-framework/typescript-runtime/function-reference/#sessionDisconnect)他们的会话。

为确保被封禁的用户不能使用仍然有效的授权令牌进行重新连接，并确保关闭任何开放的套接字连接，您必须封禁该用户_并_退出登录和断开他们的活动会话。请查看关于[封禁用户](../../client-libraries/snippets/banning-users/)的完整示例。

## 好友状态

| 代码 | 用途                                                           |
|------|-------------------------------------------------------------------|
| 0    | 用户为双向好友。                                |
| 1    | 用户A已发送邀请，等待用户B接受。 |
| 2    | 用户A已收到邀请但尚未接受。       |
| 3    | 用户A已封禁用户B。                                         |
