diff --git a/.gitignore b/.gitignore
index adf8f72..4f8c0c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,4 @@
-# ---> Go
-# If you prefer the allow list template instead of the deny list, see community template:
-# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
-#
-# Binaries for programs and plugins
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
-
-# Test binary, built with `go test -c`
-*.test
-
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
-
-# Dependency directories (remove the comment below to include it)
-# vendor/
-
-# Go workspace file
-go.work
-
+.idea/
+*/.DS_Store
+.vscode
+go.sum
\ No newline at end of file
diff --git a/README.md b/README.md
index dc95b13..983a197 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,1013 @@
-# tencent-im
+## 腾讯云IM官方API文档
-腾讯云即时IM封装SDK
\ No newline at end of file
+点击查看 [官方文档](https://cloud.tencent.com/product/im/developer)
+
+## 如何使用
+
+```shell script
+go get git.echol.cn/loser/tencent-im
+```
+
+## 调用方法
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "git.echol.cn/loser/tencent-im"
+ "git.echol.cn/loser/tencent-im/account"
+)
+
+func main() {
+ tim := im.NewIM(&im.Options{
+ AppId: 1400579830, // 无效的AppId,请勿直接使用
+ AppSecret: "0d2a321b087fdb8fd5ed5ea14fe0489139086eb1b03541283fc9feeab8f2bfd3", // 无效的AppSecret,请勿直接使用
+ UserId: "administrator", // 管理员用户账号,请在腾讯云IM后台设置管理账号
+ })
+
+ // 导入账号
+ if err := tim.Account().ImportAccount(&account.Account{
+ UserId: "test1",
+ Nickname: "测试账号1",
+ FaceUrl: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png",
+ }); err != nil {
+ if e, ok := err.(im.Error); ok {
+ fmt.Println(fmt.Sprintf("import accout failed, code:%d, message:%s.", e.Code(), e.Message()))
+ } else {
+ fmt.Println(fmt.Sprintf("import accout failed:%s.", err.Error()))
+ }
+ }
+
+ fmt.Println("import account success.")
+
+ // 注册回调事件
+ tim.Callback().Register(callback.EventAfterFriendAdd, func(ack callback.Ack, data interface{}) {
+ fmt.Printf("%+v", data.(callback.AfterFriendAdd))
+ _ = ack.AckSuccess(0)
+ })
+
+ // 注册回调事件
+ tim.Callback().Register(callback.EventAfterFriendDelete, func(ack callback.Ack, data interface{}) {
+ fmt.Printf("%+v", data.(callback.AfterFriendDelete))
+ _ = ack.AckSuccess(0)
+ })
+
+ // 开启监听
+ http.HandleFunc("/callback", func(writer http.ResponseWriter, request *http.Request) {
+ tim.Callback().Listen(writer, request)
+ })
+
+ // 启动服务器
+ if err := http.ListenAndServe(":8080", nil); err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
+```
+
+## SDK列表
+
+
+
+ 模块 |
+ 名称 |
+ 方法 |
+ 说明 |
+ master |
+
+
+ 账号管理 |
+
+ 导入单个帐号
+ |
+ Account.ImportAccount |
+ 本接口用于将 App 自有帐号导入即时通信 IM 帐号系统,为该帐号创建一个对应的内部 ID,使该帐号能够使用即时通信 IM 服务。 |
+ √ |
+
+
+
+ 导入多个帐号
+ |
+ Account.ImportAccounts |
+ 本接口用于批量将 App 自有帐号导入即时通信 IM 帐号系统,为该帐号创建一个对应的内部 ID,使该帐号能够使用即时通信 IM 服务。 |
+ √ |
+
+
+
+ 删除单个帐号
+ |
+ Account.DeleteAccount |
+
+
+ - 本方法拓展于“删除多个帐号(DeleteAccounts)”方法。
+ - 仅支持删除套餐包类型为 IM 体验版的帐号,其他类型的账号(如:TRTC、白板、专业版、旗舰版)无法删除。
+
+ |
+ √ |
+
+
+
+ 删除多个帐号
+ |
+ Account.DeleteAccounts |
+ 仅支持删除套餐包类型为 IM 体验版的帐号,其他类型的账号(如:TRTC、白板、专业版、旗舰版)无法删除。 |
+ √ |
+
+
+
+ 查询单个帐号导入状态
+ |
+ Account.CheckAccount |
+
+
+ - 本方法拓展于“查询多个帐号导入状态(CheckAccounts)”方法。
+ - 用于查询自有帐号是否已导入即时通信 IM。
+
+ |
+ √ |
+
+
+
+ 查询多个帐号导入状态
+ |
+ Account.CheckAccounts |
+ 用于查询自有帐号是否已导入即时通信 IM,支持批量查询。 |
+ √ |
+
+
+
+ 失效帐号登录状态
+ |
+ Account.KickAccount |
+ 本接口适用于将 App 用户帐号的登录状态(例如 UserSig)失效。 |
+ √ |
+
+
+
+ 查询单个帐号在线状态
+ |
+ Account.GetAccountOnlineState |
+
+
+ - 本方法拓展于“查询多个帐号在线状态(GetAccountsOnlineState)”方法。
+ - 获取用户当前的登录状态。
+
+ |
+ √ |
+
+
+
+ 查询多个帐号在线状态
+ |
+ Account.GetAccountsOnlineState |
+ 获取用户当前的登录状态。 |
+ √ |
+
+
+ 资料管理 |
+
+ 设置资料
+ |
+ Profile.SetProfile |
+ 支持 标配资料字段 和 自定义资料字段 的设置。 |
+ √ |
+
+
+
+ 拉取资料
+ |
+ Profile.GetProfiles |
+
+
+ - 支持拉取好友和非好友的资料字段。
+ - 支持拉取 标配资料字段 和 自定义资料字段。
+ - 建议每次拉取的用户数不超过100,避免因回包数据量太大导致回包失败。
+ - 请确保请求中的所有帐号都已导入即时通信 IM,如果请求中含有未导入即时通信 IM 的帐号,即时通信 IM 后台将会提示错误。
+
+ |
+ √ |
+
+
+ 关系链管理 |
+
+ 添加单个好友
+ |
+ SNS.AddFriend |
+
+
+ - 本方法拓展于“添加多个好友(AddFriends)”方法。
+ - 添加好友,仅支持添加单个好友
+
+ |
+ √ |
+
+
+
+ 添加多个好友
+ |
+ SNS.AddFriends |
+ 添加好友,支持批量添加好友。 |
+ √ |
+
+
+
+ 导入单个好友
+ |
+ SNS.ImportFriend |
+ 本方法拓展于“添加多个好友(ImportFriends)”方法。 |
+ √ |
+
+
+
+ 导入多个好友
+ |
+ SNS.ImportFriends |
+
+
+ - 支持批量导入单向好友。
+ - 往同一个用户导入好友时建议采用批量导入的方式,避免并发写导致的写冲突。
+
+ |
+ √ |
+
+
+
+ 更新单个好友
+ |
+ SNS.UpdateFriend |
+
+
+ - 本方法拓展于“更新多个好友(UpdateFriends)”方法。
+
+ |
+ √ |
+
+
+
+ 更新多个好友
+ |
+ SNS.UpdateFriends |
+
+
+ - 支持批量更新同一用户的多个好友的关系链数据。
+ - 更新一个用户多个好友时,建议采用批量方式,避免并发写导致的写冲突。
+
+ |
+ √ |
+
+
+
+ 删除单个好友
+ |
+ SNS.DeleteFriend |
+ 本方法拓展于“删除多个好友(DeleteFriends)”方法。 |
+ √ |
+
+
+
+ 删除多个好友
+ |
+ SNS.DeleteFriends |
+ 删除好友,支持单向删除好友和双向删除好友。 |
+ √ |
+
+
+
+ 删除所有好友
+ |
+ SNS.DeleteAllFriends |
+ 清除指定用户的标配好友数据和自定义好友数据。 |
+ √ |
+
+
+
+ 校验单个好友
+ |
+ SNS.CheckFriend |
+ 本方法拓展于“校验多个好友(CheckFriends)”方法。 |
+ √ |
+
+
+
+ 校验多个好友
+ |
+ SNS.CheckFriends |
+ 支持批量校验好友关系。 |
+ √ |
+
+
+
+ 拉取好友
+ |
+ SNS.FetchFriends |
+
+
+ - 分页拉取全量好友数据。
+ - 不支持资料数据的拉取。
+ - 不需要指定请求拉取的字段,默认返回全量的标配好友数据和自定义好友数据。
+
+ |
+ √ |
+
+
+
+ 拉取好友
+ |
+ SNS.PullFriends |
+
+
+ - 本API是借助"拉取好友"API进行扩展实现
+ - 分页拉取全量好友数据。
+ - 不支持资料数据的拉取。
+ - 不需要指定请求拉取的字段,默认返回全量的标配好友数据和自定义好友数据。
+
+ |
+ √ |
+
+
+
+ 拉取单个指定好友
+ |
+ SNS.GetFriend |
+
+
+ - 本方法拓展于“拉取多个指定好友(GetFriends)”方法。
+ - 支持拉取指定好友的好友数据和资料数据。
+
+ |
+ √ |
+
+
+
+ 拉取多个指定好友
+ |
+ SNS.GetFriends |
+
+
+ - 支持拉取指定好友的好友数据和资料数据。
+ - 建议每次拉取的好友数不超过100,避免因数据量太大导致回包失败。
+
+ |
+ √ |
+
+
+
+ 添加黑名单
+ |
+ SNS.AddBlacklist |
+ 添加黑名单,支持批量添加黑名单。 |
+ √ |
+
+
+
+ 删除黑名单
+ |
+ SNS.DeleteBlacklist |
+ 删除指定黑名单。 |
+ √ |
+
+
+
+ 拉取黑名单
+ |
+ SNS.FetchBlacklist |
+ 支持分页拉取所有黑名单。 |
+ √ |
+
+
+
+ 拉取黑名单
+ |
+ SNS.PullBlacklist |
+
+
+ - 本API是借助"拉取黑名单"API进行扩展实现
+ - 支持分页拉取所有黑名单。
+
+ |
+ √ |
+
+
+
+ 校验黑名单
+ |
+ SNS.CheckBlacklist |
+ 支持批量校验黑名单。 |
+ √ |
+
+
+
+ 添加分组
+ |
+ SNS.AddGroups |
+ 添加分组,支持批量添加分组,并将指定好友加入到新增分组中。 |
+ √ |
+
+
+
+ 删除分组
+ |
+ SNS.DeleteGroups |
+ 删除指定分组。 |
+ √ |
+
+
+
+ 拉取分组
+ |
+ SNS.GetGroups |
+ 拉取分组,支持指定分组以及拉取分组下的好友列表。 |
+ √ |
+
+
+ 私聊消息 |
+
+ 单发单聊消息
+ |
+ Private.SendMessage |
+
+
+ - 管理员向帐号发消息,接收方看到消息发送者是管理员。
+ - 管理员指定某一帐号向其他帐号发消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+ - 该接口不会检查发送者和接收者的好友关系(包括黑名单),同时不会检查接收者是否被禁言。
+ - 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+
+ |
+ √ |
+
+
+
+ 批量发单聊消息
+ |
+ Private.SendMessages |
+
+
+ - 支持一次对最多500个用户进行单发消息。
+ - 与单发消息相比,该接口更适用于营销类消息、系统通知 tips 等时效性较强的消息。
+ - 管理员指定某一帐号向目标帐号批量发消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+ - 该接口不触发回调请求。
+ - 该接口不会检查发送者和接收者的好友关系(包括黑名单),同时不会检查接收者是否被禁言。
+ - 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+
+ |
+ √ |
+
+
+
+ 导入单聊消息
+ |
+ Private.ImportMessage |
+
+
+ - 导入历史单聊消息到即时通信 IM。
+ - 平滑过渡期间,将原有即时通信实时单聊消息导入到即时通信 IM。
+ - 该接口不会触发回调。
+ - 该接口会根据 From_Account , To_Account ,MsgSeq , MsgRandom , MsgTimeStamp 字段的值对导入的消息进行去重。仅当这五个字段的值都对应相同时,才判定消息是重复的,消息是否重复与消息内容本身无关。
+ - 重复导入的消息不会覆盖之前已导入的消息(即消息内容以首次导入的为准)。
+ - 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+
+ |
+ √ |
+
+
+
+ 查询单聊消息
+ |
+ Private.FetchMessages |
+
+
+ - 管理员按照时间范围查询某单聊会话的消息记录。
+ - 查询的单聊会话由请求中的 From_Account 和 To_Account 指定。查询结果包含会话双方互相发送的消息,具体每条消息的发送方和接收方由每条消息里的 From_Account 和 To_Account 指定。
+ - 一般情况下,请求中的 From_Account 和 To_Account 字段值互换,查询结果不变。但通过 单发单聊消息 或 批量发单聊消息 接口发送的消息,如果指定 SyncOtherMachine 值为2,则需要指定正确的 From_Account 和 To_Account 字段值才能查询到该消息。
+ - 查询结果包含被撤回的消息,由消息里的 MsgFlagBits 字段标识。
+ - 若想通过 REST API 撤回单聊消息 接口撤回某条消息,可先用本接口查询出该消息的 MsgKey,然后再调用撤回接口进行撤回。
+ - 可查询的消息记录的时间范围取决于漫游消息存储时长,默认是7天。支持在控制台修改消息漫游时长,延长消息漫游时长是增值服务。具体请参考 漫游消息存储。
+ - 若请求时间段内的消息总大小超过应答包体大小限制(目前为13K)时,则需要续拉。您可以通过应答中的 Complete 字段查看是否已拉取请求的全部消息。
+
+ |
+ √ |
+
+
+
+ 续拉取单聊消息
+ |
+ Private.PullMessages |
+
+
+ - 本API是借助"查询单聊消息"API进行扩展实现。
+ - 管理员按照时间范围查询某单聊会话的消息记录。
+ - 查询的单聊会话由请求中的 From_Account 和 To_Account 指定。查询结果包含会话双方互相发送的消息,具体每条消息的发送方和接收方由每条消息里的 From_Account 和 To_Account 指定。
+ - 一般情况下,请求中的 From_Account 和 To_Account 字段值互换,查询结果不变。但通过 单发单聊消息 或 批量发单聊消息 接口发送的消息,如果指定 SyncOtherMachine 值为2,则需要指定正确的 From_Account 和 To_Account 字段值才能查询到该消息。
+ - 查询结果包含被撤回的消息,由消息里的 MsgFlagBits 字段标识。
+ - 若想通过 REST API 撤回单聊消息 接口撤回某条消息,可先用本接口查询出该消息的 MsgKey,然后再调用撤回接口进行撤回。
+ - 可查询的消息记录的时间范围取决于漫游消息存储时长,默认是7天。支持在控制台修改消息漫游时长,延长消息漫游时长是增值服务。具体请参考 漫游消息存储。
+ - 若请求时间段内的消息总大小超过应答包体大小限制(目前为13K)时,则需要续拉。您可以通过应答中的 Complete 字段查看是否已拉取请求的全部消息。
+
+ |
+ √ |
+
+
+
+ 撤回单聊消息
+ |
+ Private.RevokeMessage |
+
+
+ - 管理员撤回单聊消息。
+ - 该接口可以撤回所有单聊消息,包括客户端发出的单聊消息,由 REST API 单发 和 批量发 接口发出的单聊消息。
+ - 若需要撤回由客户端发出的单聊消息,您可以开通 发单聊消息之前回调 或 发单聊消息之后回调 ,通过该回调接口记录每条单聊消息的 MsgKey ,然后填在本接口的 MsgKey 字段进行撤回。您也可以通过 查询单聊消息 查询出待撤回的单聊消息的 MsgKey 后,填在本接口的 MsgKey 字段进行撤回。
+ - 若需要撤回由 REST API 单发 和 批量发 接口发出的单聊消息,需要记录这些接口回包里的 MsgKey 字段以进行撤回。
+ - 调用该接口撤回消息后,该条消息的离线、漫游存储,以及消息发送方和接收方的客户端的本地缓存都会被撤回。
+ - 该接口可撤回的单聊消息没有时间限制,即可以撤回任何时间的单聊消息。
+
+ |
+ √ |
+
+
+
+ 设置单聊消息已读
+ |
+ Private.SetMessageRead |
+ 设置用户的某个单聊会话的消息全部已读。 |
+ √ |
+
+
+
+ 查询单聊未读消息计数
+ |
+ Private.GetUnreadMessageNum |
+ App 后台可以通过该接口查询特定账号的单聊总未读数(包含所有的单聊会话)或者单个单聊会话的未读数。 |
+ √ |
+
+
+ 全员推送 |
+
+ 设置应用属性名称
+ |
+ Push.PushMessage |
+
+
+ - 支持全员推送。
+ - 支持按用户属性推送。
+ - 支持按用户标签推送。
+ - 管理员推送消息,接收方看到消息发送者是管理员。
+ - 管理员指定某一帐号向其他帐号推送消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+ - 支持消息离线存储,不支持漫游。
+ - 由于全员推送需要下发的帐号数量巨大,下发完全部帐号需要一定时间(根据帐号总数而定,一般在一分钟内)。
+
+ |
+ √ |
+
+
+
+ 设置应用属性名称
+ |
+ Push.SetAttrNames |
+ 每个应用可以设置自定义的用户属性,最多可以有10个。通过本接口可以设置每个属性的名称,设置完成后,即可用于按用户属性推送等。 |
+ √ |
+
+
+
+ 获取应用属性名称
+ |
+ Push.GetAttrNames |
+ 管理员获取应用属性名称。使用前请先 设置应用属性名称 。 |
+ √ |
+
+
+
+ 获取用户属性
+ |
+ Push.GetUserAttrs |
+ 获取用户属性(必须以管理员帐号调用);每次最多只能获取100个用户的属性。使用前请先 设置应用属性名称 。 |
+ √ |
+
+
+
+ 设置用户属性
+ |
+ Push.SetUserAttrs |
+ 管理员给用户设置属性。每次最多只能给100个用户设置属性。使用前请先 设置应用属性名称 。 |
+ √ |
+
+
+
+ 删除用户属性
+ |
+ Push.DeleteUserAttrs |
+ 管理员给用户删除属性。注意每次最多只能给100个用户删除属性。使用前请先 设置应用属性名称。 |
+ √ |
+
+
+
+ 获取用户标签
+ |
+ Push.GetUserTags |
+ 获取用户标签(必须以管理员帐号调用)。每次最多只能获取100个用户的标签。 |
+ √ |
+
+
+
+ 添加用户标签
+ |
+ Push.AddUserTags |
+
+
+ - 管理员给用户添加标签。
+ - 每次请求最多只能给100个用户添加标签,请求体中单个用户添加标签数最多为10个。
+ - 单个用户可设置最大标签数为100个,若用户当前标签超过100,则添加新标签之前请先删除旧标签。
+ - 单个标签最大长度为50字节。
+
+ |
+ √ |
+
+
+
+ 删除用户标签
+ |
+ Push.DeleteUserTags |
+ 管理员给用户删除标签。注意每次最多只能给100个用户删除标签。 |
+ √ |
+
+
+
+ 删除用户所有标签
+ |
+ Push.DeleteUserAllTags |
+ 管理员给用户删除所有标签。注意每次最多只能给100个用户删除所有标签。 |
+ √ |
+
+
+ 全局禁言管理 |
+
+ 设置全局禁言
+ |
+ Mute.SetNoSpeaking |
+
+
+ - 设置帐号的单聊消息全局禁言。
+ - 设置帐号的群组消息全局禁言。
+
+ |
+ √ |
+
+
+
+ 查询全局禁言
+ |
+ Mute.GetNoSpeaking |
+
+
+ - 查询帐号的单聊消息全局禁言。
+ - 查询帐号的群组消息全局禁言。
+
+ |
+ √ |
+
+
+ 运营管理 |
+
+ 拉取运营数据
+ |
+ Operation.GetOperationData |
+ App 管理员可以通过该接口拉取最近30天的运营数据,可拉取的字段见下文可拉取的运营字段。 |
+ √ |
+
+
+
+ 下载最近消息记录
+ |
+ Operation.GetHistoryData |
+ App 管理员可以通过该接口获取 App 中最近7天中某天某小时的所有单发或群组消息记录的下载地址。 |
+ √ |
+
+
+
+ 获取服务器IP地址
+ |
+ Operation.GetIPList |
+ 基于安全等考虑,您可能需要获知服务器的 IP 地址列表,以便进行相关限制。App 管理员可以通过该接口获得 SDK、第三方回调所使用到的服务器 IP 地址列表或 IP 网段信息。 |
+ √ |
+
+
+ 群组管理 |
+
+ 拉取App中的所有群组ID
+ |
+ Group.FetchGroupIds |
+ App 管理员可以通过该接口获取App中所有群组的ID。 |
+ √ |
+
+
+
+ 拉取App中的所有群组
+ |
+ Group.FetchGroups |
+ 本方法由“拉取App中的所有群组ID(FetchGroupIds)”拓展而来 |
+ √ |
+
+
+
+ 续拉取App中的所有群组
+ |
+ Group.PullGroups |
+ 本方法由“拉取App中的所有群组(FetchGroups)”拓展而来 |
+ √ |
+
+
+
+ 创建群组
+ |
+ Group.CreateGroup |
+ App 管理员可以通过该接口创建群组。 |
+ √ |
+
+
+
+ 获取单个群详细资料
+ |
+ Group.GetGroup |
+ 本方法由“获取多个群详细资料(GetGroups)”拓展而来 |
+ √ |
+
+
+
+ 获取多个群详细资料
+ |
+ Group.GetGroups |
+ App 管理员可以根据群组 ID 获取群组的详细信息。 |
+ √ |
+
+
+
+ 拉取群成员详细资料
+ |
+ Group.FetchMembers |
+ App管理员可以根据群组ID获取群组成员的资料。 |
+ √ |
+
+
+
+ 拉取群成员详细资料
+ |
+ Group.PullMembers |
+ 本方法由“拉取群成员详细资料(FetchMembers)”拓展而来 |
+ √ |
+
+
+
+ 修改群基础资料
+ |
+ Group.UpdateGroup |
+ App管理员可以通过该接口修改指定群组的基础信息。 |
+ √ |
+
+
+
+ 增加群成员
+ |
+ Group.AddMembers |
+ App管理员可以通过该接口向指定的群中添加新成员。 |
+ √ |
+
+
+
+ 删除群成员
+ |
+ Group.DeleteMembers |
+ App管理员可以通过该接口删除群成员。 |
+ √ |
+
+
+
+ 修改群成员资料
+ |
+ Group.UpdateMember |
+ App管理员可以通过该接口修改群成员资料。 |
+ √ |
+
+
+
+ 解散群组
+ |
+ Group.DestroyGroup |
+ App管理员通过该接口解散群。 |
+ √ |
+
+
+
+ 拉取用户所加入的群组
+ |
+ Group.FetchMemberGroups |
+ App管理员可以通过本接口获取某一用户加入的群信息。默认不获取用户已加入但未激活好友工作群(Work)以及直播群(AVChatRoom)群信息。 |
+ √ |
+
+
+
+ 拉取用户所加入的群组
+ |
+ Group.PullMemberGroups |
+ 本方法由“拉取用户所加入的群组(FetchMemberGroups)”拓展而来 |
+ √ |
+
+
+
+ 查询用户在群组中的身份
+ |
+ Group.GetRolesInGroup |
+ App管理员可以通过该接口获取一批用户在群内的身份,即“成员角色”。 |
+ √ |
+
+
+
+ 批量禁言
+ |
+ Group.ForbidSendMessage |
+
+
+ - App 管理员禁止指定群组中某些用户在一段时间内发言。
+ - App 管理员取消对某些用户的禁言。
+ - 被禁言用户退出群组之后再进入同一群组,禁言仍然有效。
+
+ |
+ √ |
+
+
+
+ 取消禁言
+ |
+ Group.AllowSendMessage |
+ 本方法由“批量禁言(ForbidSendMessage)”拓展而来 |
+ √ |
+
+
+
+ 获取被禁言群成员列表
+ |
+ Group.GetShuttedUpMembers |
+ App管理员可以根据群组ID获取群组中被禁言的用户列表。 |
+ √ |
+
+
+
+ 在群组中发送普通消息
+ |
+ Group.SendMessage |
+ App管理员可以通过该接口在群组中发送普通消息。 |
+ √ |
+
+
+
+ 在群组中发送系统通知
+ |
+ Group.SendNotification |
+ App 管理员可以通过该接口在群组中发送系统通知。 |
+ √ |
+
+
+
+ 转让群主
+ |
+ Group.ChangeGroupOwner |
+
+
+ - App 管理员可以通过该接口将群主身份转移给他人。
+ - 没有群主的群,App 管理员可以通过此接口指定他人作为群主。
+ - 新群主必须为群内成员。
+
+ |
+ √ |
+
+
+
+ 撤回单条群消息
+ |
+ Group.RevokeMessage |
+ 本方法由“撤回多条群消息(RevokeMessages)”拓展而来 |
+ √ |
+
+
+
+ 撤回多条群消息
+ |
+ Group.RevokeMessages |
+ App 管理员通过该接口撤回指定群组的消息,消息需要在漫游有效期以内。 |
+ √ |
+
+
+
+ 导入群基础资料
+ |
+ Group.ImportGroup |
+ App 管理员可以通过该接口导入群组,不会触发回调、不会下发通知;当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群组数据。 |
+ √ |
+
+
+
+ 导入群消息
+ |
+ Group.ImportMembers |
+
+
+ - 该 API 接口的作用是导入群组的消息,不会触发回调、不会下发通知。
+ - 当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群消息数据。
+
+ |
+ √ |
+
+
+
+ 导入多个群成员
+ |
+ Group.ImportMembers |
+
+
+ - 该 API 接口的作用是导入群组成员,不会触发回调、不会下发通知。
+ - 当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群成员数据。
+
+ |
+ √ |
+
+
+
+ 设置成员未读消息计数
+ |
+ Group.SetMemberUnreadMsgNum |
+
+
+ - App管理员使用该接口设置群组成员未读消息数,不会触发回调、不会下发通知。
+ - 当App需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议设置群成员的未读消息计数。
+
+ |
+ √ |
+
+
+
+ 撤回指定用户发送的消息
+ |
+ Group.RevokeMemberMessages |
+ 该API接口的作用是撤回最近1000条消息中指定用户发送的消息。 |
+ √ |
+
+
+
+ 拉取群历史消息
+ |
+ Group.FetchMessages |
+
+
+ - 即时通信 IM 的群消息是按 Seq 排序的,按照 server 收到群消息的顺序分配 Seq,先发的群消息 Seq 小,后发的 Seq 大。
+ - 如果用户想拉取一个群的全量消息,首次拉取时不用填拉取 Seq,Server 会自动返回最新的消息,以后拉取时拉取 Seq 填上次返回的最小 Seq 减1。
+ - 如果返回消息的 IsPlaceMsg 为1,表示这个 Seq 的消息或者过期、或者存储失败、或者被删除了。
+
+ |
+ √ |
+
+
+
+ 续拉取群历史消息
+ |
+ Group.PullMessages |
+ 本方法由“拉取群历史消息(FetchMessages)”拓展而来 |
+ √ |
+
+
+
+ 获取直播群在线人数
+ |
+ Group.GetOnlineMemberNum |
+ App 管理员可以根据群组 ID 获取直播群在线人数。 |
+ √ |
+
+
+ 最近联系人 |
+
+ 拉取会话列表
+ |
+ RecentContact.FetchSessions |
+ 支持分页拉取会话列表。 |
+ √ |
+
+
+
+ 拉取会话列表
+ |
+ RecentContact.PullSessions |
+
+
+ - 本API是借助"拉取会话列表"API进行扩展实现
+ - 支持分页拉取会话列表。
+
+ |
+ √ |
+
+
+
+ 删除单个会话
+ |
+ RecentContact.DeleteSession |
+ 删除指定会话,支持同步清理漫游消息。 |
+ √ |
+
+
\ No newline at end of file
diff --git a/account/api.go b/account/api.go
new file mode 100644
index 0000000..07bf09b
--- /dev/null
+++ b/account/api.go
@@ -0,0 +1,305 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 20:07
+ * @Desc: 账号管理
+ */
+
+package account
+
+import (
+ "fmt"
+
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ serviceAccount = "im_open_login_svc"
+ serviceOpenIM = "openim"
+ commandImportAccount = "account_import"
+ commandImportAccounts = "multiaccount_import"
+ commandDeleteAccounts = "account_delete"
+ commandCheckAccounts = "account_check"
+ commandKickAccount = "kick"
+ commandQueryAccountsOnlineStatus = "query_online_status"
+
+ batchImportAccountsLimit = 100 // 导入账号限制
+ batchDeleteAccountsLimit = 100 // 删除账号限制
+ batchCheckAccountsLimit = 100 // 查询账号限制
+)
+
+type API interface {
+ // ImportAccount 导入单个帐号
+ // 本接口用于将 App 自有帐号导入即时通信 IM 帐号系统,
+ // 为该帐号创建一个对应的内部 ID,使该帐号能够使用即时通信 IM 服务。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1608
+ ImportAccount(account *Account) (err error)
+
+ // ImportAccounts 导入多个帐号
+ // 本接口用于批量将 App 自有帐号导入即时通信 IM 帐号系统,
+ // 为该帐号创建一个对应的内部 ID,使该帐号能够使用即时通信 IM 服务。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/4919
+ ImportAccounts(userIds ...string) (failUserIds []string, err error)
+
+ // DeleteAccount 删除账号
+ // 本方法拓展于“删除多个帐号(DeleteAccounts)”方法。
+ // 仅支持删除套餐包类型为 IM 体验版的帐号,其他类型的账号(如:TRTC、白板、专业版、旗舰版)无法删除。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/36443
+ DeleteAccount(userId string) (err error)
+
+ // DeleteAccounts 删除多个帐号
+ // 仅支持删除套餐包类型为 IM 体验版的帐号,其他类型的账号(如:TRTC、白板、专业版、旗舰版)无法删除。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/36443
+ DeleteAccounts(userIds ...string) (results []*DeleteResult, err error)
+
+ // CheckAccount 查询帐号导入状态
+ // 本方法拓展于“查询多个帐号导入状态(CheckAccounts)”方法。
+ // 用于查询自有帐号是否已导入即时通信 IM。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/38417
+ CheckAccount(userId string) (bool, error)
+
+ // CheckAccounts 查询多个帐号导入状态
+ // 用于查询自有帐号是否已导入即时通信 IM,支持批量查询。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/38417
+ CheckAccounts(userIds ...string) (results []*CheckResult, err error)
+
+ // KickAccount 使帐号登录状态失效
+ // 本接口适用于将 App 用户帐号的登录状态(例如 UserSig)失效。
+ // 例如,开发者判断一个用户为恶意帐号后,可以调用本接口将该用户当前的登录状态失效,这样用户使用历史 UserSig 登录即时通信 IM 会失败。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/3853
+ KickAccount(userId string) (err error)
+
+ // GetAccountOnlineState 查询帐号在线状态
+ // 本方法拓展于“查询多个帐号在线状态(GetAccountsOnlineState)”方法。
+ // 获取用户当前的登录状态。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2566
+ GetAccountOnlineState(userId string, isNeedDetail ...bool) (*OnlineStatusResult, error)
+
+ // GetAccountsOnlineState 查询多个帐号在线状态
+ // 获取用户当前的登录状态。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2566
+ GetAccountsOnlineState(userIds []string, isNeedDetail ...bool) (ret *OnlineStatusRet, err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// ImportAccount 导入单个帐号
+// 本接口用于将 App 自有帐号导入即时通信 IM 帐号系统,
+// 为该帐号创建一个对应的内部 ID,使该帐号能够使用即时通信 IM 服务。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1608
+func (a *api) ImportAccount(account *Account) (err error) {
+ if err = a.client.Post(serviceAccount, commandImportAccount, account, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// ImportAccounts 导入多个帐号
+// 本接口用于批量将 App 自有帐号导入即时通信 IM 帐号系统,
+// 为该帐号创建一个对应的内部 ID,使该帐号能够使用即时通信 IM 服务。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/4919
+func (a *api) ImportAccounts(userIds ...string) (failUserIds []string, err error) {
+ if c := len(userIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the userid is not set")
+ return
+ } else if c > batchImportAccountsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of imported accounts cannot exceed %d", batchImportAccountsLimit))
+ return
+ }
+
+ req := &importAccountsReq{UserIds: userIds}
+ resp := &importAccountsResp{}
+
+ if err = a.client.Post(serviceAccount, commandImportAccounts, req, resp); err != nil {
+ return
+ }
+
+ failUserIds = resp.FailUserIds
+
+ return
+}
+
+// DeleteAccount 删除账号
+// 本方法拓展于“删除多个帐号(DeleteAccounts)”方法。
+// 仅支持删除套餐包类型为 IM 体验版的帐号,其他类型的账号(如:TRTC、白板、专业版、旗舰版)无法删除。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/36443
+func (a *api) DeleteAccount(userId string) (err error) {
+ results, err := a.DeleteAccounts(userId)
+ if err != nil {
+ return
+ }
+
+ for _, result := range results {
+ if result.UserId == userId && result.ResultCode != enum.SuccessCode {
+ return core.NewError(result.ResultCode, result.ResultInfo)
+ }
+ }
+
+ return
+}
+
+// DeleteAccounts 删除多个帐号
+// 仅支持删除套餐包类型为 IM 体验版的帐号,其他类型的账号(如:TRTC、白板、专业版、旗舰版)无法删除。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/36443
+func (a *api) DeleteAccounts(userIds ...string) (results []*DeleteResult, err error) {
+ if c := len(userIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the userid is not set")
+ return
+ } else if c > batchDeleteAccountsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of deleted accounts cannot exceed %d", batchDeleteAccountsLimit))
+ return
+ }
+
+ req := &deleteAccountsReq{}
+ resp := &deleteAccountsResp{}
+
+ for _, userId := range userIds {
+ req.Deletes = append(req.Deletes, &accountItem{userId})
+ }
+
+ if err = a.client.Post(serviceAccount, commandDeleteAccounts, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// CheckAccount 查询帐号导入状态.
+// 本方法拓展于“查询多个帐号导入状态(CheckAccounts)”方法。
+// 用于查询自有帐号是否已导入即时通信 IM。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/38417
+func (a *api) CheckAccount(userId string) (bool, error) {
+ results, err := a.CheckAccounts(userId)
+ if err != nil {
+ return false, err
+ }
+
+ for _, result := range results {
+ if result.UserId == userId {
+ if result.ResultCode != enum.SuccessCode {
+ return false, core.NewError(result.ResultCode, result.ResultInfo)
+ } else {
+ return result.Status == ImportedStatusYes, nil
+ }
+ }
+ }
+
+ return false, nil
+}
+
+// CheckAccounts 查询多个帐号导入状态.
+// 用于查询自有帐号是否已导入即时通信 IM,支持批量查询。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/38417
+func (a *api) CheckAccounts(userIds ...string) (results []*CheckResult, err error) {
+ if c := len(userIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the account is not set")
+ return
+ } else if c > batchCheckAccountsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of checked accounts cannot exceed %d", batchCheckAccountsLimit))
+ return
+ }
+
+ req := &checkAccountsReq{}
+ resp := &checkAccountsResp{}
+
+ for _, userId := range userIds {
+ req.Checks = append(req.Checks, &accountItem{userId})
+ }
+
+ if err = a.client.Post(serviceAccount, commandCheckAccounts, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// KickAccount 失效帐号登录状态
+// 本接口适用于将 App 用户帐号的登录状态(例如 UserSig)失效。
+// 例如,开发者判断一个用户为恶意帐号后,可以调用本接口将该用户当前的登录状态失效,这样用户使用历史 UserSig 登录即时通信 IM 会失败。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/3853
+func (a *api) KickAccount(userId string) (err error) {
+ if err = a.client.Post(serviceAccount, commandKickAccount, &kickAccountReq{userId}, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// GetAccountOnlineState 查询帐号在线状态
+// 本方法拓展于“查询多个帐号在线状态(GetAccountsOnlineState)”方法。
+// 获取用户当前的登录状态。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2566
+func (a *api) GetAccountOnlineState(userId string, isNeedDetail ...bool) (*OnlineStatusResult, error) {
+ ret, err := a.GetAccountsOnlineState([]string{userId}, isNeedDetail...)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, item := range ret.Errors {
+ if item.UserId == userId && item.ErrorCode != enum.SuccessCode {
+ return nil, core.NewError(item.ErrorCode, "account exception")
+ }
+ }
+
+ for _, item := range ret.Results {
+ if item.UserId == userId {
+ return &item, nil
+ }
+ }
+
+ return nil, nil
+}
+
+// GetAccountsOnlineState 查询多个帐号在线状态
+// 获取用户当前的登录状态。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2566
+func (a *api) GetAccountsOnlineState(userIds []string, isNeedDetail ...bool) (ret *OnlineStatusRet, err error) {
+ req := &queryAccountsOnlineStatusReq{UserIds: userIds}
+ resp := &queryAccountsOnlineStatusResp{}
+
+ if len(isNeedDetail) > 0 && isNeedDetail[0] {
+ req.IsNeedDetail = 1
+ }
+
+ if err = a.client.Post(serviceOpenIM, commandQueryAccountsOnlineStatus, req, resp); err != nil {
+ return
+ }
+
+ ret = &OnlineStatusRet{
+ Results: resp.Results,
+ Errors: resp.Errors,
+ }
+
+ return
+}
diff --git a/account/enum.go b/account/enum.go
new file mode 100644
index 0000000..64de044
--- /dev/null
+++ b/account/enum.go
@@ -0,0 +1,16 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 11:09
+ * @Desc: TODO
+ */
+
+package account
+
+// ImportedStatusType 导入状态
+type ImportedStatusType string
+
+const (
+ ImportedStatusNo ImportedStatusType = "NotImported" // 未导入
+ ImportedStatusYes ImportedStatusType = "Imported" // 已导入
+)
diff --git a/account/types.go b/account/types.go
new file mode 100644
index 0000000..6b3e951
--- /dev/null
+++ b/account/types.go
@@ -0,0 +1,117 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 17:36
+ * @Desc: 账号管理数据类型
+ */
+
+package account
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+type (
+ // Account 导入单个账号
+ Account struct {
+ UserId string `json:"Identifier"` // (必填)用户名,长度不超过32字节
+ Nickname string `json:"Nick"` // (选填)用户昵称
+ FaceUrl string `json:"FaceUrl"` // (选填)用户头像 URL
+ }
+
+ // 批量导入账号(参数)
+ importAccountsReq struct {
+ UserIds []string `json:"Accounts"` // (必填)用户名,单个用户名长度不超过32字节,单次最多导入100个用户名
+ }
+
+ // 批量导入账号(响应)
+ importAccountsResp struct {
+ types.ActionBaseResp
+ FailUserIds []string `json:"FailAccounts"` // 导入失败的帐号列表
+ }
+
+ // 账号项
+ accountItem struct {
+ UserId string `json:"UserID"` // 帐号的UserID
+ }
+
+ // 删除多个帐号(请求)
+ deleteAccountsReq struct {
+ Deletes []*accountItem `json:"DeleteItem"` // 请求删除的帐号对象数组,单次请求最多支持100个帐号
+ }
+
+ // 删除多个账号(响应)
+ deleteAccountsResp struct {
+ types.ActionBaseResp
+ Results []*DeleteResult `json:"ResultItem"`
+ }
+
+ // DeleteResult 删除多个账号结果项
+ DeleteResult struct {
+ ResultCode int `json:"ResultCode"` // 单个帐号的错误码,0表示成功,非0表示失败
+ ResultInfo string `json:"ResultInfo"` // 单个帐号删除失败时的错误描述信息
+ UserId string `json:"UserID"` // 请求删除的帐号的 UserID
+ }
+
+ // 查询多个帐号(请求)
+ checkAccountsReq struct {
+ Checks []*accountItem `json:"CheckItem"` // (必填)请求检查的帐号对象数组,单次请求最多支持100个帐号
+ }
+
+ // 查询多个帐号(响应)
+ checkAccountsResp struct {
+ types.ActionBaseResp
+ Results []*CheckResult `json:"ResultItem"` // 检测结果
+ }
+
+ // CheckResult 检测结果
+ CheckResult struct {
+ UserId string `json:"UserID"` // 请求检查的帐号的 UserID
+ Status ImportedStatusType `json:"AccountStatus"` // 单个帐号的导入状态:Imported 表示已导入,NotImported 表示未导入
+ ResultCode int `json:"ResultCode"` // 单个帐号的检查结果:0表示成功,非0表示失败
+ ResultInfo string `json:"ResultInfo"` // 单个帐号检查失败时的错误描述信息
+ }
+
+ // 失效帐号登录状态(请求)
+ kickAccountReq struct {
+ UserId string `json:"Identifier"` // (必填)用户名
+ }
+
+ // 查询帐号在线状态(请求)
+ queryAccountsOnlineStatusReq struct {
+ UserIds []string `json:"To_Account"` // (必填)需要查询这些 UserID 的登录状态,一次最多查询500个 UserID 的状态
+ IsNeedDetail int `json:"IsNeedDetail"` // (选填)是否需要返回详细的登录平台信息。0表示不需要,1表示需要
+ }
+
+ // 查询帐号在线状态(响应)
+ queryAccountsOnlineStatusResp struct {
+ types.ActionBaseResp
+ Results []OnlineStatusResult `json:"QueryResult"` // 用户在线状态结构化信息
+ Errors []OnlineStatusError `json:"ErrorList"` // 状态查询失败的帐号列表,在此列表中的目标帐号,状态查询失败或目标帐号不存在。若状态全部查询成功,则 ErrorList 为空
+ }
+
+ // OnlineStatusRet 在线状态结果
+ OnlineStatusRet struct {
+ Results []OnlineStatusResult // 用户在线状态结构化信息
+ Errors []OnlineStatusError // 状态查询失败的帐号列表,在此列表中的目标帐号,状态查询失败或目标帐号不存在。若状态全部查询成功,则 ErrorList 为空
+ }
+
+ // OnlineStatusPlatform 详细的登录平台信息
+ OnlineStatusPlatform struct {
+ Platform string `json:"Platform"` // 登录的平台类型。可能的返回值有:"iPhone", "Android", "Web", "PC", "iPad", "Mac"。
+ Status string `json:"Status"` // 该登录平台的状态
+ }
+
+ // OnlineStatusResult 用户在线状态结构化信息项
+ OnlineStatusResult struct {
+ UserId string `json:"To_Account"` // 用户的 UserID
+ Status string `json:"Status"` // 用户状态,前台运行状态(Online)、后台运行状态(PushOnline)、未登录状态(Offline)
+ Detail []OnlineStatusPlatform `json:"Detail"` // 详细的登录平台信息
+ }
+
+ // OnlineStatusError 状态查询失败的帐号项
+ OnlineStatusError struct {
+ UserId string `json:"To_Account"` // 状态查询失败的目标帐号
+ ErrorCode int `json:"ErrorCode"` // 状态查询失败的错误码,若目标帐号的错误码为70107,表示该帐号不存在
+ }
+)
diff --git a/callback/callback.go b/callback/callback.go
new file mode 100644
index 0000000..6433307
--- /dev/null
+++ b/callback/callback.go
@@ -0,0 +1,289 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 14:24
+ * @Desc: TODO
+ */
+
+package callback
+
+import (
+ "encoding/json"
+ "errors"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+ "sync"
+)
+
+const (
+ commandStateChange = "State.StateChange"
+ commandBeforeFriendAdd = "Sns.CallbackPrevFriendAdd"
+ commandBeforeFriendResponse = "Sns.CallbackPrevFriendResponse"
+ commandAfterFriendAdd = "Sns.CallbackFriendAdd"
+ commandAfterFriendDelete = "Sns.CallbackFriendDelete"
+ commandAfterBlacklistAdd = "Sns.CallbackBlackListAdd"
+ commandAfterBlacklistDelete = "Sns.CallbackBlackListDelete"
+ commandBeforePrivateMessageSend = "C2C.CallbackBeforeSendMsg"
+ commandAfterPrivateMessageSend = "C2C.CallbackAfterSendMsg"
+ commandAfterPrivateMessageReport = "C2C.CallbackAfterMsgReport"
+ commandAfterPrivateMessageRevoke = "C2C.CallbackAfterMsgWithDraw"
+ commandBeforeGroupCreate = "Group.CallbackBeforeCreateGroup"
+ commandAfterGroupCreate = "Group.CallbackAfterCreateGroup"
+ commandBeforeApplyJoinGroup = "Group.CallbackBeforeApplyJoinGroup"
+ commandBeforeInviteJoinGroup = "Group.CallbackBeforeInviteJoinGroup"
+ commandAfterNewMemberJoinGroup = "Group.CallbackAfterNewMemberJoin"
+ commandAfterMemberExitGroup = "Group.CallbackAfterMemberExit"
+ commandBeforeGroupMessageSend = "Group.CallbackBeforeSendMsg"
+ commandAfterGroupMessageSend = "Group.CallbackAfterSendMsg"
+ commandAfterGroupFull = "Group.CallbackAfterGroupFull"
+ commandAfterGroupDestroyed = "Group.CallbackAfterGroupDestroyed"
+ commandAfterGroupInfoChanged = "Group.CallbackAfterGroupInfoChanged"
+)
+
+const (
+ EventStateChange Event = iota + 1
+ EventBeforeFriendAdd
+ EventBeforeFriendResponse
+ EventAfterFriendAdd
+ EventAfterFriendDelete
+ EventAfterBlacklistAdd
+ EventAfterBlacklistDelete
+ EventBeforePrivateMessageSend
+ EventAfterPrivateMessageSend
+ EventAfterPrivateMessageReport
+ EventAfterPrivateMessageRevoke
+ EventBeforeGroupCreate
+ EventAfterGroupCreate
+ EventBeforeApplyJoinGroup
+ EventBeforeInviteJoinGroup
+ EventAfterNewMemberJoinGroup
+ EventAfterMemberExitGroup
+ EventBeforeGroupMessageSend
+ EventAfterGroupMessageSend
+ EventAfterGroupFull
+ EventAfterGroupDestroyed
+ EventAfterGroupInfoChanged
+)
+
+const (
+ ackSuccessStatus = "OK"
+ ackFailureStatus = "FAIL"
+
+ ackSuccessCode = 0
+ ackFailureCode = 1
+
+ queryAppId = "SdkAppid"
+ queryCommand = "CallbackCommand"
+ queryClientId = "ClientIP"
+ queryOptPlatform = "OptPlatform"
+ queryContentType = "contenttype"
+)
+
+type (
+ Event int
+ EventHandlerFunc func(ack Ack, data interface{})
+ Options struct {
+ SdkAppId int
+ }
+
+ Callback interface {
+ // Register 注册事件
+ Register(event Event, handler EventHandlerFunc)
+ // Listen 监听事件
+ Listen(w http.ResponseWriter, r *http.Request)
+ }
+
+ callback struct {
+ appId int
+ mu sync.Mutex
+ handlers map[Event]EventHandlerFunc
+ }
+
+ Ack interface {
+ // Ack 应答
+ Ack(resp interface{}) error
+ // AckFailure 失败应答
+ AckFailure(message ...string) error
+ // AckSuccess 成功应答
+ AckSuccess(code int, message ...string) error
+ }
+
+ ack struct {
+ w http.ResponseWriter
+ }
+)
+
+func NewCallback(appId int) Callback {
+ return &callback{
+ appId: appId,
+ handlers: make(map[Event]EventHandlerFunc),
+ }
+}
+
+// Register 注册事件
+func (c *callback) Register(event Event, handler EventHandlerFunc) {
+ c.mu.Lock()
+ c.handlers[event] = handler
+ c.mu.Unlock()
+}
+
+// Listen 监听事件
+func (c *callback) Listen(w http.ResponseWriter, r *http.Request) {
+ a := newAck(w)
+
+ appId, ok := c.GetQuery(r, queryAppId)
+ if !ok || appId != strconv.Itoa(c.appId) {
+ _ = a.AckFailure("invalid sdk appId")
+ return
+ }
+
+ command, ok := c.GetQuery(r, queryCommand)
+ if !ok {
+ _ = a.AckFailure("invalid callback command")
+ return
+ }
+
+ body, err := ioutil.ReadAll(r.Body)
+ _ = r.Body.Close()
+ if err != nil {
+ _ = a.AckFailure(err.Error())
+ return
+ }
+
+ if event, data, err := c.parseCommand(command, body); err != nil {
+ _ = a.AckFailure(err.Error())
+ } else {
+ if fn, ok := c.handlers[event]; ok {
+ fn(a, data)
+ return
+ } else {
+ _ = a.AckSuccess(ackSuccessCode)
+ }
+ }
+}
+
+// parseCommand parse command and body package.
+func (c *callback) parseCommand(command string, body []byte) (event Event, data interface{}, err error) {
+ switch command {
+ case commandStateChange:
+ event = EventStateChange
+ data = &StateChange{}
+ case commandBeforeFriendAdd:
+ event = EventBeforeFriendAdd
+ data = &BeforeFriendAdd{}
+ case commandBeforeFriendResponse:
+ event = EventBeforeFriendResponse
+ data = &BeforeFriendResponse{}
+ case commandAfterFriendAdd:
+ event = EventAfterFriendAdd
+ data = &AfterFriendAdd{}
+ case commandAfterFriendDelete:
+ event = EventAfterFriendDelete
+ data = &AfterFriendDelete{}
+ case commandAfterBlacklistAdd:
+ event = EventAfterBlacklistAdd
+ data = &AfterBlacklistAdd{}
+ case commandAfterBlacklistDelete:
+ event = EventAfterBlacklistDelete
+ data = &AfterBlacklistDelete{}
+ case commandBeforePrivateMessageSend:
+ event = EventBeforePrivateMessageSend
+ data = &BeforePrivateMessageSend{}
+ case commandAfterPrivateMessageSend:
+ event = EventAfterPrivateMessageSend
+ data = &AfterPrivateMessageSend{}
+ case commandAfterPrivateMessageReport:
+ event = EventAfterPrivateMessageReport
+ data = &AfterPrivateMessageReport{}
+ case commandAfterPrivateMessageRevoke:
+ event = EventAfterPrivateMessageRevoke
+ data = &AfterPrivateMessageRevoke{}
+ case commandBeforeGroupCreate:
+ event = EventBeforeGroupCreate
+ data = &BeforeGroupCreate{}
+ case commandAfterGroupCreate:
+ event = EventAfterGroupCreate
+ data = &AfterGroupCreate{}
+ case commandBeforeApplyJoinGroup:
+ event = EventBeforeApplyJoinGroup
+ data = &BeforeApplyJoinGroup{}
+ case commandBeforeInviteJoinGroup:
+ event = EventBeforeInviteJoinGroup
+ data = &BeforeInviteJoinGroup{}
+ case commandAfterNewMemberJoinGroup:
+ event = EventAfterNewMemberJoinGroup
+ data = &AfterNewMemberJoinGroup{}
+ case commandAfterMemberExitGroup:
+ event = EventAfterMemberExitGroup
+ data = &AfterMemberExitGroup{}
+ case commandBeforeGroupMessageSend:
+ event = EventBeforeGroupMessageSend
+ data = &BeforeGroupMessageSend{}
+ case commandAfterGroupMessageSend:
+ event = EventAfterGroupMessageSend
+ data = &AfterGroupMessageSend{}
+ case commandAfterGroupFull:
+ event = EventAfterGroupFull
+ data = &AfterGroupFull{}
+ case commandAfterGroupDestroyed:
+ event = EventAfterGroupDestroyed
+ data = &AfterGroupDestroyed{}
+ case commandAfterGroupInfoChanged:
+ event = EventAfterGroupInfoChanged
+ data = &AfterGroupInfoChanged{}
+ default:
+ return 0, nil, errors.New("invalid callback command")
+ }
+
+ if err = json.Unmarshal(body, &data); err != nil {
+ return 0, nil, err
+ }
+
+ return event, data, nil
+}
+
+// GetQuery 获取查询参数
+func (c *callback) GetQuery(r *http.Request, key string) (string, bool) {
+ if values, ok := r.URL.Query()[key]; ok {
+ return values[0], ok
+ } else {
+ return "", false
+ }
+}
+
+func newAck(w http.ResponseWriter) Ack {
+ return &ack{w}
+}
+
+// Ack 应答
+func (a *ack) Ack(resp interface{}) error {
+ b, _ := json.Marshal(resp)
+ a.w.WriteHeader(http.StatusOK)
+ _, err := a.w.Write(b)
+ return err
+}
+
+// AckFailure 应答失败
+func (a *ack) AckFailure(message ...string) error {
+ resp := BaseResp{}
+ resp.ActionStatus = ackFailureStatus
+ resp.ErrorCode = ackFailureCode
+ if len(message) > 0 {
+ resp.ErrorInfo = message[0]
+ }
+
+ return a.Ack(resp)
+}
+
+// AckSuccess 应答成功
+func (a *ack) AckSuccess(code int, message ...string) error {
+ resp := BaseResp{}
+ resp.ActionStatus = ackSuccessStatus
+ resp.ErrorCode = code
+ if len(message) > 0 {
+ resp.ErrorInfo = message[0]
+ }
+
+ return a.Ack(resp)
+}
diff --git a/callback/types.go b/callback/types.go
new file mode 100644
index 0000000..2c131e7
--- /dev/null
+++ b/callback/types.go
@@ -0,0 +1,324 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 16:36
+ * @Desc: Callback request struct defined.
+ */
+
+package callback
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ BaseResp struct {
+ ErrorCode int `json:"ErrorCode"`
+ ErrorInfo string `json:"ErrorInfo"`
+ ActionStatus string `json:"ActionStatus"`
+ }
+
+ // StateChange 状态变更回调
+ StateChange struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ EventTime int64 `json:"EventTime"` // 触发本次回调的时间戳,单位为毫秒
+ Info struct {
+ UserId string `json:"To_Account"` // 用户 UserID
+ Action string `json:"Action"` // 用户上线或者下线的动作,Login 表示上线(TCP 建立),Logout 表示下线(TCP 断开),Disconnect 表示网络断开(TCP 断开)
+ Reason string `json:"Reason"` // 用户上下线触发的原因
+ } `json:"Info"` // 用户上下线的信息
+ KickedDevice []struct {
+ Platform string `json:"Platform"` // 被踢下线的设备的平台类型,可能的取值有"iOS", "Android", "Web", "Windows", "iPad", "Mac", "Linux"。
+ } `json:"KickedDevice"` // 此字段表示其他被踢下线的设备的信息
+ }
+
+ // BeforeFriendAdd 添加好友之前回调
+ BeforeFriendAdd struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ EventTime int64 `json:"EventTime"` // 触发本次回调的时间戳,单位为毫秒
+ RequesterUserId string `json:"Requester_Account"` // 请求发起方的 UserID
+ FromUserId string `json:"From_Account"` // 请求添加好友的用户的 UserID(A添加B为好友中的A)
+ AddType string `json:"AddType"` // 加好友方式(默认双向加好友方式,Add_Type_Single:表示单向加好友 Add_Type_Both:表示双向加好友)
+ ForceAddFlags int `json:"ForceAddFlags"` // 管理员强制加好友标记(1:表示强制加好友 0:表示常规加好友方式)
+ Friends []struct {
+ ToAccount string `json:"To_Account"` // 请求添加的用户的 UserID
+ Remark string `json:"Remark"` // From_Account 对 To_Account 设置的好友备注
+ GroupName string `json:"GroupName"` // From_Account 对 To_Account 设置的好友分组
+ AddSource string `json:"AddSource"` // 加好友来源
+ AddWording string `json:"AddWording"` // 加好友附言
+ } `json:"FriendItem"` // 加好友请求的参数
+ }
+
+ // BeforeFriendAddResp 添加好友之前回调应答
+ BeforeFriendAddResp struct {
+ BaseResp
+ Results []*BeforeFriendAddResult `json:"ResultItem"` // App 后台的处理结果
+ }
+
+ // BeforeFriendAddResult App后台的处理结果
+ BeforeFriendAddResult struct {
+ UserId string `json:"To_Account"` // (必填)请求添加的用户的 UserID
+ ResultCode int `json:"ResultCode"` // (必填)错误码:0表示允许加好友; 非0值表示不允许加好友; 如果不允许加好友,请将错误码设置在[38000, 39000]
+ ResultInfo string `json:"ResultInfo"` // (必填)错误信息
+ }
+
+ // BeforeFriendResponse 添加好友回应之前回调
+ BeforeFriendResponse struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ EventTime int64 `json:"EventTime"` // 触发本次回调的时间戳,单位为毫秒
+ RequesterUserId string `json:"Requester_Account"` // 请求发起方的 UserID
+ FromUserId string `json:"From_Account"` // 请求加好友回应的用户的 UserID
+ Friends []struct {
+ ToAccount string `json:"To_Account"` // 请求回应的用户的 UserID
+ Remark string `json:"Remark"` // From_Account 对 To_Account 设置的好友备注
+ TagName string `json:"TagName"` // From_Account 对 To_Account 设置的好友分组
+ ResponseAction string `json:"ResponseAction"` // 加好友回应方式,Response_Action_AgreeAndAdd 表示同意且添加对方为好友;Response_Action_Agree 表示同意对方加自己为好友;Response_Action_Reject 表示拒绝对方的加好友请求
+ } `json:"ResponseFriendItem"` // 加好友回应请求的参数
+ }
+
+ // BeforeFriendResponseResp 添加好友之前回调应答
+ BeforeFriendResponseResp struct {
+ BaseResp
+ Results []*BeforeFriendAddResult `json:"ResultItem"` // App 后台的处理结果
+ }
+
+ // BeforeFriendResponseResult App后台的处理结果
+ BeforeFriendResponseResult struct {
+ UserId string `json:"To_Account"` // (必填)请求添加的用户的 UserID
+ ResultCode int `json:"ResultCode"` // (必填)错误码:0表示允许加好友; 非0值表示不允许加好友; 如果不允许加好友,请将错误码设置在[38000, 39000]
+ ResultInfo string `json:"ResultInfo"` // (必填)错误信息
+ }
+
+ // AfterFriendAdd 添加好友之后
+ AfterFriendAdd struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ ClientCmd string `json:"ClientCmd"` // 触发回调的命令字:加好友请求,合理的取值如下:friend_add、FriendAdd; 加好友回应,合理的取值如下:friend_response、FriendResponse
+ AdminUserId string `json:"Admin_Account"` // 如果当前请求是后台触发的加好友请求,则该字段被赋值为管理员帐号;否则为空
+ ForceFlag int `json:"ForceFlag"` // 管理员强制加好友标记:1 表示强制加好友;0 表示常规加好友方式
+ PairList []struct {
+ FromUserId string `json:"From_Account"` // From_Account 的好友表中增加了 To_Account
+ ToUserId string `json:"To_Account"` // To_Account 被增加到了 From_Account 的好友表中
+ InitiatorUserId string `json:"Initiator_Account"` // 发起加好友请求的用户的 UserID
+ } `json:"PairList"` // 成功添加的好友对
+ }
+
+ // AfterFriendDelete 删除好友之后回调
+ AfterFriendDelete struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ PairList []struct {
+ FromUserId string `json:"From_Account"` // From_Account 的好友表中删除了 To_Account
+ ToUserId string `json:"To_Account"` // To_Account 从 From_Account 的好友表中删除
+ } `json:"PairList"` // 成功删除的好友
+ }
+
+ // AfterBlacklistAdd 添加黑名单之后回调
+ AfterBlacklistAdd struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ PairList []struct {
+ FromUserId string `json:"From_Account"` // From_Account 的黑名单列表中添加了 To_Account
+ ToUserId string `json:"To_Account"` // To_Account 被加入到 From_Account 的黑名单列表中
+ } `json:"PairList"` // 成功添加的黑名单关系链对
+ }
+
+ // AfterBlacklistDelete 删除黑名单之后回调
+ AfterBlacklistDelete struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ PairList []struct {
+ FromUserId string `json:"From_Account"` // From_Account 的黑名单列表中删除了 To_Account
+ ToUserId string `json:"To_Account"` // To_Account 从 From_Account 的黑名单列表中删除
+ } `json:"PairList"` // 成功删除的黑名单对
+ }
+
+ // BeforePrivateMessageSend 发单聊消息之前回调
+ BeforePrivateMessageSend struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ FromUserId string `json:"From_Account"` // 消息发送者 UserID
+ ToUserId string `json:"To_Account"` // 消息接收者 UserID
+ MsgSeq int `json:"MsgSeq"` // 消息序列号,用于标记该条消息(32位无符号整数)
+ MsgRandom int `json:"MsgRandom"` // 消息随机数,用于标记该条消息(32位无符号整数)
+ MsgTime int64 `json:"MsgTime"` // 消息的发送时间戳,单位为秒,单聊消息优先使用 MsgTime 进行排序,同一秒发送的消息则按 MsgSeq 排序,MsgSeq 值越大消息越靠后
+ MsgKey string `json:"MsgKey"` // 该条消息的唯一标识,可根据该标识进行 REST API 撤回单聊消息
+ OnlineOnlyFlag int `json:"OnlineOnlyFlag"` // 在线消息,为1,否则为0
+ MsgBody []*types.MsgBody `json:"MsgBody"` // 消息体
+ CloudCustomData string `json:"CloudCustomData"` // 消息自定义数据(云端保存,会发送到对端,程序卸载重装后还能拉取到)
+ }
+
+ // BeforePrivateMessageSendResp 发单聊消息之前回调应答
+ BeforePrivateMessageSendResp struct {
+ BaseResp
+ MsgBody []*types.MsgBody `json:"MsgBody,omitempty"` // (选填)App 修改之后的消息,如果没有,则默认使用用户发送的消息
+ CloudCustomData string `json:"CloudCustomData,omitempty"` // (选填)经过 App 修改之后的消息自定义数据(云端保存,会发送到对端,程序卸载重装后还能拉取到),即时通信 IM 后台将把修改后的消息发送给接收方
+ }
+
+ // AfterPrivateMessageSend 发单聊消息之后回调
+ AfterPrivateMessageSend struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ FromUserId string `json:"From_Account"` // 消息发送者 UserID
+ ToUserId string `json:"To_Account"` // 消息接收者 UserID
+ MsgSeq int `json:"MsgSeq"` // 消息序列号,用于标记该条消息(32位无符号整数)
+ MsgRandom int `json:"MsgRandom"` // 消息随机数,用于标记该条消息(32位无符号整数)
+ MsgTime int64 `json:"MsgTime"` // 消息的发送时间戳,单位为秒,单聊消息优先使用 MsgTime 进行排序,同一秒发送的消息则按 MsgSeq 排序,MsgSeq 值越大消息越靠后
+ MsgKey string `json:"MsgKey"` // 该条消息的唯一标识,可根据该标识进行 REST API 撤回单聊消息
+ OnlineOnlyFlag int `json:"OnlineOnlyFlag"` // 在线消息,为1,否则为0
+ MsgBody []*types.MsgBody `json:"MsgBody"` // 消息体
+ CloudCustomData string `json:"CloudCustomData"` // 消息自定义数据(云端保存,会发送到对端,程序卸载重装后还能拉取到)
+ SendMsgResult int `json:"SendMsgResult"` // 该条消息的下发结果,0表示下发成功,非0表示下发失败
+ ErrorInfo string `json:"ErrorInfo"` // 该条消息下发失败的错误信息,若消息发送成功,则为"send msg succeed"
+ UnreadMsgNum int `json:"UnreadMsgNum"` // To_Account 未读的单聊消息总数量(包含所有的单聊会话)。若该条消息下发失败(例如被脏字过滤),该字段值为-1
+ }
+
+ // AfterPrivateMessageReport 单聊消息已读上报后回调
+ AfterPrivateMessageReport struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ ReportUserId string `json:"Report_Account"` // 已读上报方 UserID
+ PeerUserId string `json:"Peer_Account"` // 会话对端 UserID
+ LastReadTime int64 `json:"LastReadTime"` // 已读时间
+ UnreadMsgNum int `json:"UnreadMsgNum"` // Report_Account 未读的单聊消息总数量(包含所有的单聊会话)
+ }
+
+ // AfterPrivateMessageRevoke 单聊消息撤回后回调
+ AfterPrivateMessageRevoke struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ FromUserId string `json:"From_Account"` // 消息发送者 UserID
+ ToUserId string `json:"To_Account"` // 消息接收者 UserID
+ MsgKey string `json:"MsgKey"` // 消息的唯一标识
+ UnreadMsgNum int `json:"UnreadMsgNum"` // To_Account 未读的单聊消息总数量(包含所有的单聊会话)
+ }
+
+ // BeforeGroupCreate 创建群组之前回调
+ BeforeGroupCreate struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ OperatorUserId string `json:"Operator_Account"` // 操作者
+ OwnerUserId string `json:"Owner_Account"` // 群主
+ Type string `json:"Type"` // 群组类型
+ Name string `json:"Name"` // 请求创建的群组的名称
+ CreateGroupNum int `json:"CreateGroupNum"` // 该用户已创建的同类的群组个数
+ MemberList []struct {
+ UserId string `json:"Member_Account"` // 成员 UserID
+ } `json:"MemberList"` // 请求创建的群组的初始化成员列表
+ }
+
+ // AfterGroupCreate 创建群组之后回调
+ AfterGroupCreate struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ OperatorUserId string `json:"Operator_Account"` // 操作者
+ OwnerUserId string `json:"Owner_Account"` // 群主
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ Name string `json:"Name"` // 请求创建的群组的名称
+ CreateGroupNum int `json:"CreateGroupNum"` // 该用户已创建的同类的群组个数
+ MemberList []struct {
+ UserId string `json:"Member_Account"` // 成员 UserID
+ } `json:"MemberList"` // 请求创建的群组的初始化成员列表
+ UserDefinedDataList []struct {
+ Key string `json:"Key"`
+ Value string `json:"Value"`
+ } `json:"UserDefinedDataList"` // 用户建群时的自定义字段
+ }
+
+ // BeforeApplyJoinGroup 申请入群之前回调
+ BeforeApplyJoinGroup struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ RequestorUserId string `json:"Requestor_Account"` // 申请者
+ }
+
+ // BeforeInviteJoinGroup 拉人入群之前回调
+ BeforeInviteJoinGroup struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ OperatorUserId string `json:"Operator_Account"` // 操作者
+ MemberList []struct {
+ UserId string `json:"Member_Account"` // 成员 UserID
+ } `json:"DestinationMembers"` // 要拉入群组的 UserID 集合
+ }
+
+ // BeforeInviteJoinGroupResp 拉人入群之前回调应答
+ BeforeInviteJoinGroupResp struct {
+ BaseResp
+ RefusedMemberUserIds []string `json:"RefusedMembers_Account,omitempty"` // 拒绝加入的用户列表
+ }
+
+ // AfterNewMemberJoinGroup 新成员入群之后回调
+ AfterNewMemberJoinGroup struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ JoinType string `json:"JoinType"` // 入群方式:Apply(申请入群);Invited(邀请入群)
+ OperatorUserId string `json:"Operator_Account"` // 操作者
+ MemberList []struct {
+ UserId string `json:"Member_Account"` // 成员 UserID
+ } `json:"NewMemberList"` // 新入群成员列表
+ }
+
+ // AfterMemberExitGroup 群成员离开之后回调
+ AfterMemberExitGroup struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ ExitType string `json:"ExitType"` // 成员离开方式:Kicked-被踢;Quit-主动退群
+ OperatorUserId string `json:"Operator_Account"` // 操作者
+ MemberList []struct {
+ UserId string `json:"Member_Account"` // 成员 UserID
+ } `json:"ExitMemberList"` // 离开群的成员列表
+ }
+
+ // BeforeGroupMessageSend 群内发言之前回调
+ BeforeGroupMessageSend struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ FromUserId string `json:"From_Account"` // 发送者
+ OperatorUserId string `json:"Operator_Account"` // 请求的发起者
+ OnlineOnlyFlag int `json:"OnlineOnlyFlag"` // 在线消息,为1,否则为0;直播群忽略此属性,为默认值0。
+ MsgRandom int `json:"Random"` // 随机数
+ MsgBody []*types.MsgBody `json:"MsgBody"` // 消息体
+ }
+
+ // BeforeGroupMessageSendResp 群内发言之前回调应答
+ BeforeGroupMessageSendResp struct {
+ BaseResp
+ MsgBody []*types.MsgBody `json:"MsgBody,omitempty"` // (选填)App 修改之后的消息,如果没有,则默认使用用户发送的消息
+ }
+
+ // AfterGroupMessageSend 群内发言之后回调
+ AfterGroupMessageSend struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ FromUserId string `json:"From_Account"` // 发送者
+ OperatorUserId string `json:"Operator_Account"` // 请求的发起者
+ OnlineOnlyFlag int `json:"OnlineOnlyFlag"` // 在线消息,为1,否则为0;直播群忽略此属性,为默认值0。
+ MsgSeq int `json:"MsgSeq"` // 消息的序列号
+ MsgRandom int `json:"Random"` // 随机数
+ MsgTime int64 `json:"MsgTime"` // 消息的时间
+ MsgBody []*types.MsgBody `json:"MsgBody"` // 消息体
+ }
+
+ // AfterGroupFull 群组满员之后回调
+ AfterGroupFull struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ }
+
+ // AfterGroupDestroyed 群组解散之后回调
+ AfterGroupDestroyed struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ Name string `json:"Name"` // 群组名称
+ OwnerUserId string `json:"Owner_Account"` // 操作者
+ MemberList []struct {
+ UserId string `json:"Member_Account"` // 成员 UserID
+ } `json:"MemberList"` // 被解散的群组中的成员
+ }
+
+ // AfterGroupInfoChanged 群组资料修改之后回调
+ AfterGroupInfoChanged struct {
+ CallbackCommand string `json:"CallbackCommand"` // 回调命令
+ GroupId string `json:"GroupId"` // 群ID
+ Type string `json:"Type"` // 群组类型
+ Notification string `json:"Notification"` // 修改后的群公告
+ OperatorUserId string `json:"Operator_Account"` // 请求的发起者
+ }
+)
diff --git a/example/main.go b/example/main.go
new file mode 100644
index 0000000..d9038f0
--- /dev/null
+++ b/example/main.go
@@ -0,0 +1,63 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/8/31 15:14
+ * @Desc: TODO
+ */
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ "git.echol.cn/loser/tencent-im"
+ "git.echol.cn/loser/tencent-im/account"
+ "git.echol.cn/loser/tencent-im/callback"
+)
+
+func main() {
+ tim := im.NewIM(&im.Options{
+ AppId: 1400579830, // 无效的AppId,请勿直接使用
+ AppSecret: "0d2a321b087fdb8fd5ed5ea14fe0489139086eb1b03541283fc9feeab8f2bfd3", // 无效的AppSecret,请勿直接使用
+ UserId: "administrator", // 管理员用户账号,请在腾讯云IM后台设置管理账号
+ })
+
+ // 导入账号
+ if err := tim.Account().ImportAccount(&account.Account{
+ UserId: "test1",
+ Nickname: "测试账号1",
+ FaceUrl: "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png",
+ }); err != nil {
+ if e, ok := err.(im.Error); ok {
+ fmt.Println(fmt.Sprintf("import account failed, code:%d, message:%s.", e.Code(), e.Message()))
+ } else {
+ fmt.Println(fmt.Sprintf("import account failed:%s.", err.Error()))
+ }
+ }
+
+ fmt.Println("import account success.")
+
+ // 注册回调事件
+ tim.Callback().Register(callback.EventAfterFriendAdd, func(ack callback.Ack, data interface{}) {
+ fmt.Printf("%+v", data.(callback.AfterFriendAdd))
+ _ = ack.AckSuccess(0)
+ })
+
+ // 注册回调事件
+ tim.Callback().Register(callback.EventAfterFriendDelete, func(ack callback.Ack, data interface{}) {
+ fmt.Printf("%+v", data.(callback.AfterFriendDelete))
+ _ = ack.AckSuccess(0)
+ })
+
+ // 开启监听
+ http.HandleFunc("/callback", func(writer http.ResponseWriter, request *http.Request) {
+ tim.Callback().Listen(writer, request)
+ })
+
+ // 启动服务器
+ if err := http.ListenAndServe(":8080", nil); err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..7450fc8
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,7 @@
+module git.echol.cn/loser/tencent-im
+
+go 1.18
+
+require git.echol.cn/loser/http v0.0.3
+
+require github.com/dobyte/http v0.0.2 // indirect
diff --git a/group/api.go b/group/api.go
new file mode 100644
index 0000000..9cf81ea
--- /dev/null
+++ b/group/api.go
@@ -0,0 +1,1315 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 20:46
+ * @Desc: Group Api Implementation.
+ */
+
+package group
+
+import (
+ "fmt"
+
+ "git.echol.cn/loser/tencent-im/internal/conv"
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ serviceGroup = "group_open_http_svc"
+ commandFetchGroupIds = "get_appid_group_list"
+ commandCreateGroup = "create_group"
+ commandDestroyGroup = "destroy_group"
+ commandGetGroups = "get_group_info"
+ commandFetchGroupMembers = "get_group_member_info"
+ commandUpdateGroup = "modify_group_base_info"
+ commandAddGroupMembers = "add_group_member"
+ commandDeleteGroupMember = "delete_group_member"
+ commandModifyGroupMemberInfo = "modify_group_member_info"
+ commandFetchMemberGroups = "get_joined_group_list"
+ commandGetRoleInGroup = "get_role_in_group"
+ commandForbidSendMsg = "forbid_send_msg"
+ commandGetGroupShuttedUin = "get_group_shutted_uin"
+ commandSendGroupMsg = "send_group_msg"
+ commandSendGroupSystemNotification = "send_group_system_notification"
+ commandChangeGroupOwner = "change_group_owner"
+ commandRecallGroupMsg = "group_msg_recall"
+ commandImportGroup = "import_group"
+ commandImportGroupMsg = "import_group_msg"
+ commandImportGroupMember = "import_group_member"
+ commandSetUnreadMsgNum = "set_unread_msg_num"
+ commandDeleteGroupMsgBySender = "delete_group_msg_by_sender"
+ commandGetGroupSimpleMsg = "group_msg_get_simple"
+ commandGetOnlineMemberNum = "get_online_member_num"
+
+ batchGetGroupsLimit = 50 // 批量获取群组限制
+)
+
+type API interface {
+ // FetchGroupIds 拉取App中的所有群组ID
+ // App 管理员可以通过该接口获取App中所有群组的ID。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1614
+ FetchGroupIds(limit int, next int, groupType ...Type) (ret *FetchGroupIdsRet, err error)
+
+ // FetchGroups 拉取App中的所有群组
+ // 本方法由“拉取App中的所有群组ID(FetchGroupIds)”拓展而来
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1614
+ FetchGroups(limit int, next int, groupTypeAndFilter ...interface{}) (ret *FetchGroupsRet, err error)
+
+ // PullGroups 续拉取App中的所有群组
+ // 本方法由“拉取App中的所有群组(FetchGroups)”拓展而来
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1614
+ PullGroups(arg *PullGroupsArg, fn func(ret *FetchGroupsRet)) (err error)
+
+ // CreateGroup 创建群组
+ // App 管理员可以通过该接口创建群组。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1615
+ CreateGroup(group *Group) (groupId string, err error)
+
+ // GetGroup 获取单个群详细资料
+ // 本方法由“获取多个群详细资料(GetGroups)”拓展而来
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1616
+ GetGroup(groupId string, filter ...*Filter) (group *Group, err error)
+
+ // GetGroups 获取多个群详细资料
+ // App 管理员可以根据群组 ID 获取群组的详细信息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1616
+ GetGroups(groupIds []string, filter ...*Filter) (groups []*Group, err error)
+
+ // FetchMembers 拉取群成员详细资料
+ // App管理员可以根据群组ID获取群组成员的资料。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1617
+ FetchMembers(groupId string, limit, offset int, filter ...*Filter) (ret *FetchMembersRet, err error)
+
+ // PullMembers 续拉取群成员详细资料
+ // 本方法由“拉取群成员详细资料(FetchMembers)”拓展而来
+ // App管理员可以根据群组ID获取群组成员的资料。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1617
+ PullMembers(arg *PullMembersArg, fn func(ret *FetchMembersRet)) (err error)
+
+ // UpdateGroup 修改群基础资料
+ // App管理员可以通过该接口修改指定群组的基础信息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1620
+ UpdateGroup(group *Group) (err error)
+
+ // AddMembers 增加群成员
+ // App管理员可以通过该接口向指定的群中添加新成员。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1621
+ AddMembers(groupId string, userIds []string, silence ...bool) (results []AddMembersResult, err error)
+
+ // DeleteMembers 删除群成员
+ // App管理员可以通过该接口删除群成员。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1622
+ DeleteMembers(groupId string, userIds []string, reasonAndSilence ...interface{}) (err error)
+
+ // UpdateMember 修改群成员资料
+ // App管理员可以通过该接口修改群成员资料。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1623
+ UpdateMember(groupId string, member *Member) (err error)
+
+ // DestroyGroup 解散群组
+ // App管理员通过该接口解散群。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1624
+ DestroyGroup(groupId string) (err error)
+
+ // FetchMemberGroups 拉取用户所加入的群组
+ // App管理员可以通过本接口获取某一用户加入的群信息。默认不获取用户已加入但未激活好友工作群(Work)以及直播群(AVChatRoom)群信息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1625
+ FetchMemberGroups(arg *FetchMemberGroupsArg) (ret *FetchMemberGroupsRet, err error)
+
+ // PullMemberGroups 续拉取用户所加入的群组
+ // 本方法由“拉取用户所加入的群组(FetchMemberGroups)”拓展而来
+ // App管理员可以通过本接口获取某一用户加入的群信息。默认不获取用户已加入但未激活好友工作群(Work)以及直播群(AVChatRoom)群信息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1625
+ PullMemberGroups(arg *PullMemberGroupsArg, fn func(ret *FetchMemberGroupsRet)) (err error)
+
+ // GetRolesInGroup 查询用户在群组中的身份
+ // App管理员可以通过该接口获取一批用户在群内的身份,即“成员角色”。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1626
+ GetRolesInGroup(groupId string, userIds []string) (memberRoles map[string]string, err error)
+
+ // ForbidSendMessage 批量禁言
+ // App 管理员禁止指定群组中某些用户在一段时间内发言。
+ // App 管理员取消对某些用户的禁言。
+ // 被禁言用户退出群组之后再进入同一群组,禁言仍然有效。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1627
+ ForbidSendMessage(groupId string, userIds []string, shutUpTime int64) (err error)
+
+ // AllowSendMessage 取消禁言
+ // 本方法由“批量禁言(ForbidSendMessage)”拓展而来
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1627
+ AllowSendMessage(groupId string, userIds []string) (err error)
+
+ // GetShuttedUpMembers 获取被禁言群成员列表
+ // App管理员可以根据群组ID获取群组中被禁言的用户列表。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2925
+ GetShuttedUpMembers(groupId string) (shuttedUps map[string]int64, err error)
+
+ // SendMessage 在群组中发送普通消息
+ // App管理员可以通过该接口在群组中发送普通消息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1629
+ SendMessage(groupId string, message *Message) (ret *SendMessageRet, err error)
+
+ // SendNotification 在群组中发送系统通知
+ // App 管理员可以通过该接口在群组中发送系统通知。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1630
+ SendNotification(groupId, content string, userId ...string) (err error)
+
+ // ChangeGroupOwner 转让群主
+ // App 管理员可以通过该接口将群主身份转移给他人。
+ // 没有群主的群,App 管理员可以通过此接口指定他人作为群主。
+ // 新群主必须为群内成员。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1633
+ ChangeGroupOwner(groupId, userId string) (err error)
+
+ // RevokeMessage 撤回单条群消息
+ // 本方法由“撤回多条群消息(RevokeMessages)”拓展而来
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/12341
+ RevokeMessage(groupId string, msgSeq int) (err error)
+
+ // RevokeMessages 撤回多条群消息
+ // App 管理员通过该接口撤回指定群组的消息,消息需要在漫游有效期以内。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/12341
+ RevokeMessages(groupId string, msgSeq ...int) (results map[int]int, err error)
+
+ // ImportGroup 导入群基础资料
+ // App 管理员可以通过该接口导入群组,不会触发回调、不会下发通知;当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群组数据。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1634
+ ImportGroup(group *Group) (groupId string, err error)
+
+ // ImportMessages 导入群消息
+ // 该 API 接口的作用是导入群组的消息,不会触发回调、不会下发通知。
+ // 当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群消息数据。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1635
+ ImportMessages(groupId string, messages ...*Message) (results []ImportMessagesResult, err error)
+
+ // ImportMembers 导入多个群成员
+ // 该 API 接口的作用是导入群组成员,不会触发回调、不会下发通知。
+ // 当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群成员数据。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1636
+ ImportMembers(groupId string, members ...*Member) (results []ImportMemberResult, err error)
+
+ // SetMemberUnreadMsgNum 设置成员未读消息计数
+ // App管理员使用该接口设置群组成员未读消息数,不会触发回调、不会下发通知。
+ // 当App需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议设置群成员的未读消息计数。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1637
+ SetMemberUnreadMsgNum(groupId, userId string, unreadMsgNum int) (err error)
+
+ // RevokeMemberMessages 撤回指定用户发送的消息
+ // 该API接口的作用是撤回最近1000条消息中指定用户发送的消息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2359
+ RevokeMemberMessages(groupId, userId string) (err error)
+
+ // FetchMessages 拉取群历史消息
+ // 即时通信 IM 的群消息是按 Seq 排序的,按照 server 收到群消息的顺序分配 Seq,先发的群消息 Seq 小,后发的 Seq 大。
+ // 如果用户想拉取一个群的全量消息,首次拉取时不用填拉取 Seq,Server 会自动返回最新的消息,以后拉取时拉取 Seq 填上次返回的最小 Seq 减1。
+ // 如果返回消息的 IsPlaceMsg 为1,表示这个 Seq 的消息或者过期、或者存储失败、或者被删除了。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2738
+ FetchMessages(groupId string, limit int, msgSeq ...int) (ret *FetchMessagesRet, err error)
+
+ // PullMessages 续拉取群历史消息
+ // 本方法由“拉取群历史消息(FetchMessages)”拓展而来
+ // 即时通信 IM 的群消息是按 Seq 排序的,按照 server 收到群消息的顺序分配 Seq,先发的群消息 Seq 小,后发的 Seq 大。
+ // 如果用户想拉取一个群的全量消息,首次拉取时不用填拉取 Seq,Server 会自动返回最新的消息,以后拉取时拉取 Seq 填上次返回的最小 Seq 减1。
+ // 如果返回消息的 IsPlaceMsg 为1,表示这个 Seq 的消息或者过期、或者存储失败、或者被删除了。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2738
+ PullMessages(groupId string, limit int, fn func(ret *FetchMessagesRet)) (err error)
+
+ // GetOnlineMemberNum 获取直播群在线人数
+ // App 管理员可以根据群组 ID 获取直播群在线人数。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/49180
+ GetOnlineMemberNum(groupId string) (num int, err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// FetchGroupIds 拉取App中的所有群组ID
+// App 管理员可以通过该接口获取App中所有群组的ID。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1614
+func (a *api) FetchGroupIds(limit int, next int, groupType ...Type) (ret *FetchGroupIdsRet, err error) {
+ req := &fetchGroupIdsReq{Limit: limit, Next: next}
+
+ if len(groupType) > 0 {
+ req.Type = string(groupType[0])
+ }
+
+ resp := &fetchGroupIdsResp{}
+
+ if err = a.client.Post(serviceGroup, commandFetchGroupIds, req, resp); err != nil {
+ return
+ }
+
+ ret = &FetchGroupIdsRet{}
+ ret.Next = resp.Next
+ ret.Total = resp.TotalCount
+ ret.HasMore = ret.Next != 0
+ ret.List = make([]string, 0, len(resp.GroupIdList))
+
+ for _, item := range resp.GroupIdList {
+ ret.List = append(ret.List, item.GroupId)
+ }
+
+ return
+}
+
+// FetchGroups 拉取App中的所有群组
+// 本方法由“拉取App中的所有群组ID(FetchGroupIds)”拓展而来
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1614
+func (a *api) FetchGroups(limit int, next int, groupTypeAndFilter ...interface{}) (ret *FetchGroupsRet, err error) {
+ if limit > batchGetGroupsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of groups id cannot exceed %d", batchGetGroupsLimit))
+ return
+ }
+
+ var (
+ resp *FetchGroupIdsRet
+ filter *Filter
+ groupType Type
+ )
+
+ if len(groupTypeAndFilter) > 0 {
+ for i, val := range groupTypeAndFilter {
+ if i > 1 {
+ break
+ }
+ switch v := val.(type) {
+ case Type:
+ groupType = v
+ case *Filter:
+ filter = v
+ }
+ }
+ }
+
+ if resp, err = a.FetchGroupIds(limit, next, groupType); err != nil {
+ return
+ }
+
+ ret = &FetchGroupsRet{Next: resp.Next, Total: resp.Total, HasMore: resp.HasMore}
+
+ if len(resp.List) > 0 {
+ if ret.List, err = a.GetGroups(resp.List, filter); err != nil {
+ return
+ }
+ }
+
+ return
+}
+
+// PullGroups 续拉取App中的所有群组
+// 本方法由“拉取App中的所有群组(FetchGroups)”拓展而来
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1614
+func (a *api) PullGroups(arg *PullGroupsArg, fn func(ret *FetchGroupsRet)) (err error) {
+ var (
+ limit = arg.Limit
+ groupType = arg.Type
+ filter = arg.Filter
+ next int
+ ret *FetchGroupsRet
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchGroups(limit, next, groupType, filter)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ next = ret.Next
+ break
+ }
+ }
+
+ return
+}
+
+// CreateGroup 创建群组
+// App管理员可以通过该接口创建群组。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1615
+func (a *api) CreateGroup(group *Group) (groupId string, err error) {
+ if err = group.checkCreateError(); err != nil {
+ return
+ }
+
+ req := &createGroupReq{}
+ req.GroupId = group.id
+ req.OwnerUserId = group.owner
+ req.Type = group.groupType
+ req.Name = group.name
+ req.FaceUrl = group.avatar
+ req.Introduction = group.introduction
+ req.Notification = group.notification
+ req.MaxMemberNum = group.maxMemberNum
+ req.ApplyJoinOption = group.applyJoinOption
+
+ if data := group.GetAllCustomData(); data != nil {
+ req.AppDefinedData = make([]*customDataItem, 0, len(data))
+ for key, val := range data {
+ req.AppDefinedData = append(req.AppDefinedData, &customDataItem{
+ Key: key,
+ Value: val,
+ })
+ }
+ }
+
+ if c := len(group.members); c > 0 {
+ req.MemberList = make([]*memberItem, 0, c)
+
+ var item *memberItem
+ for _, member := range group.members {
+ if err = member.checkError(); err != nil {
+ return
+ }
+
+ item = &memberItem{
+ UserId: member.userId,
+ Role: member.role,
+ JoinTime: member.joinTime,
+ NameCard: member.nameCard,
+ AppMemberDefinedData: make([]*customDataItem, 0, len(member.GetAllCustomData())),
+ }
+
+ for k, v := range member.GetAllCustomData() {
+ item.AppMemberDefinedData = append(item.AppMemberDefinedData, &customDataItem{
+ Key: k,
+ Value: v,
+ })
+ }
+
+ req.MemberList = append(req.MemberList, item)
+ }
+ }
+
+ resp := &createGroupResp{}
+
+ if err = a.client.Post(serviceGroup, commandCreateGroup, req, resp); err != nil {
+ return
+ } else {
+ groupId = resp.GroupId
+ }
+
+ return
+}
+
+// GetGroup 获取单个群详细资料
+// 本方法由“获取多个群详细资料(GetGroups)”拓展而来
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1616
+func (a *api) GetGroup(groupId string, filter ...*Filter) (group *Group, err error) {
+ var groups []*Group
+
+ if groups, err = a.GetGroups([]string{groupId}, filter...); err != nil {
+ return
+ }
+
+ if len(groups) > 0 {
+ if err = groups[0].err; err != nil {
+ return
+ }
+
+ group = groups[0]
+ }
+
+ return
+}
+
+// GetGroups 获取多个群详细资料
+// App 管理员可以根据群组 ID 获取群组的详细信息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1616
+func (a *api) GetGroups(groupIds []string, filters ...*Filter) (groups []*Group, err error) {
+ if c := len(groupIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the group's id is not set")
+ return
+ } else if c > batchGetGroupsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of group's id cannot exceed %d", batchGetGroupsLimit))
+ return
+ }
+
+ req := &getGroupsReq{GroupIds: groupIds}
+ resp := &getGroupsResp{}
+
+ if len(filters) > 0 {
+ if filter := filters[0]; filter != nil {
+ req.ResponseFilter = &responseFilter{
+ GroupBaseInfoFilter: filter.GetAllBaseInfoFilterFields(),
+ MemberInfoFilter: filter.GetAllMemberInfoFilterFields(),
+ GroupCustomDataFilter: filter.GetAllGroupCustomDataFilterFields(),
+ MemberCustomDataFilter: filter.GetAllMemberCustomDataFilterFields(),
+ }
+ }
+ }
+
+ if err = a.client.Post(serviceGroup, commandGetGroups, req, resp); err != nil {
+ return
+ }
+
+ groups = make([]*Group, 0, len(resp.GroupInfos))
+ for _, item := range resp.GroupInfos {
+ group := NewGroup()
+ group.setError(item.ErrorCode, item.ErrorInfo)
+ if group.err == nil {
+ group.id = item.GroupId
+ group.name = item.Name
+ group.groupType = item.Type
+ group.owner = item.OwnerUserId
+ group.avatar = item.FaceUrl
+ group.memberNum = item.MemberNum
+ group.maxMemberNum = item.MaxMemberNum
+ group.applyJoinOption = item.ApplyJoinOption
+ group.createTime = item.CreateTime
+ group.lastInfoTime = item.LastInfoTime
+ group.lastMsgTime = item.LastMsgTime
+ group.shutUpStatus = item.ShutUpAllMember
+ group.nextMsgSeq = item.NextMsgSeq
+
+ if item.AppDefinedData != nil && len(item.AppDefinedData) > 0 {
+ for _, v := range item.AppDefinedData {
+ group.SetCustomData(v.Key, v.Value)
+ }
+ }
+
+ if item.MemberList != nil && len(item.MemberList) > 0 {
+ for _, m := range item.MemberList {
+ member := &Member{
+ userId: m.UserId,
+ role: m.Role,
+ joinTime: m.JoinTime,
+ nameCard: m.NameCard,
+ msgSeq: m.MsgSeq,
+ msgFlag: MsgFlag(m.MsgFlag),
+ lastSendMsgTime: m.LastSendMsgTime,
+ }
+
+ if m.AppMemberDefinedData != nil && len(m.AppMemberDefinedData) > 0 {
+ for _, v := range m.AppMemberDefinedData {
+ member.SetCustomData(v.Key, v.Value)
+ }
+ }
+
+ group.AddMembers(member)
+ }
+ }
+
+ groups = append(groups, group)
+ }
+ }
+
+ return
+}
+
+// FetchMembers 拉取群成员详细资料
+// App管理员可以根据群组ID获取群组成员的资料。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1617
+func (a *api) FetchMembers(groupId string, limit, offset int, filters ...*Filter) (ret *FetchMembersRet, err error) {
+ req := &fetchMembersReq{GroupId: groupId, Limit: limit, Offset: offset}
+
+ if len(filters) > 0 {
+ if filter := filters[0]; filter != nil {
+ req.MemberInfoFilter = filter.GetAllMemberInfoFilterFields()
+ req.MemberRoleFilter = filter.GetAllMemberRoleFilterValues()
+ req.MemberCustomDataFilter = filter.GetAllMemberCustomDataFilterFields()
+ }
+ }
+
+ resp := &fetchMembersResp{}
+
+ if err = a.client.Post(serviceGroup, commandFetchGroupMembers, req, resp); err != nil {
+ return
+ }
+
+ ret = &FetchMembersRet{}
+ ret.Total = resp.MemberNum
+ ret.List = make([]*Member, 0, len(resp.MemberList))
+ ret.HasMore = resp.MemberNum > limit+offset
+
+ for _, m := range resp.MemberList {
+ member := &Member{
+ userId: m.UserId,
+ role: m.Role,
+ joinTime: m.JoinTime,
+ nameCard: m.NameCard,
+ msgSeq: m.MsgSeq,
+ msgFlag: MsgFlag(m.MsgFlag),
+ lastSendMsgTime: m.LastSendMsgTime,
+ }
+
+ if m.AppMemberDefinedData != nil && len(m.AppMemberDefinedData) > 0 {
+ for _, v := range m.AppMemberDefinedData {
+ member.SetCustomData(v.Key, v.Value)
+ }
+ }
+
+ ret.List = append(ret.List, member)
+ }
+
+ return
+}
+
+// PullMembers 续拉取群成员详细资料
+// 本方法由“拉取群成员详细资料(FetchMembers)”拓展而来
+// App管理员可以根据群组ID获取群组成员的资料。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1617
+func (a *api) PullMembers(arg *PullMembersArg, fn func(ret *FetchMembersRet)) (err error) {
+ var (
+ offset int
+ ret *FetchMembersRet
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchMembers(arg.GroupId, arg.Limit, offset, arg.Filter)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ offset += arg.Limit
+ }
+ }
+
+ return
+}
+
+// UpdateGroup 修改群基础资料
+// App管理员可以通过该接口修改指定群组的基础信息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1620
+func (a *api) UpdateGroup(group *Group) (err error) {
+ if err = group.checkUpdateError(); err != nil {
+ return
+ }
+
+ req := &updateGroupReq{}
+ req.GroupId = group.id
+ req.Name = group.name
+ req.FaceUrl = group.avatar
+ req.Introduction = group.introduction
+ req.Notification = group.notification
+ req.MaxMemberNum = group.maxMemberNum
+ req.ApplyJoinOption = group.applyJoinOption
+ req.ShutUpAllMember = group.shutUpStatus
+
+ if data := group.GetAllCustomData(); data != nil {
+ req.AppDefinedData = make([]customDataItem, 0, len(data))
+ for key, val := range data {
+ req.AppDefinedData = append(req.AppDefinedData, customDataItem{
+ Key: key,
+ Value: val,
+ })
+ }
+ }
+
+ if err = a.client.Post(serviceGroup, commandUpdateGroup, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// AddMembers 增加群成员
+// App管理员可以通过该接口向指定的群中添加新成员。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1621
+func (a *api) AddMembers(groupId string, userIds []string, silence ...bool) (results []AddMembersResult, err error) {
+ req := &addMembersReq{}
+ req.GroupId = groupId
+ req.MemberList = make([]addMemberItem, 0, len(userIds))
+ for _, userId := range userIds {
+ req.MemberList = append(req.MemberList, addMemberItem{
+ UserId: userId,
+ })
+ }
+ if len(silence) > 0 && silence[0] {
+ req.Silence = 1
+ }
+
+ resp := &addMembersResp{}
+
+ if err = a.client.Post(serviceGroup, commandAddGroupMembers, req, resp); err != nil {
+ return
+ }
+
+ results = resp.MemberList
+
+ return
+}
+
+// DeleteMembers 删除群成员
+// App管理员可以通过该接口删除群成员。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1622
+func (a *api) DeleteMembers(groupId string, userIds []string, reasonAndSilence ...interface{}) (err error) {
+ req := &deleteMembersReq{}
+ req.GroupId = groupId
+ req.UserIds = userIds
+
+ if len(reasonAndSilence) > 0 {
+ for i, val := range reasonAndSilence {
+ if i > 1 {
+ break
+ }
+
+ switch v := val.(type) {
+ case string:
+ req.Reason = v
+ case bool:
+ if v {
+ req.Silence = 1
+ }
+ }
+ }
+ }
+
+ if err = a.client.Post(serviceGroup, commandDeleteGroupMember, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// UpdateMember 修改群成员资料
+// App管理员可以通过该接口修改群成员资料。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1623
+func (a *api) UpdateMember(groupId string, member *Member) (err error) {
+ if err = member.checkError(); err != nil {
+ return
+ }
+
+ req := &updateMemberReq{}
+ req.GroupId = groupId
+ req.UserId = member.userId
+ req.Role = member.role
+ req.MsgFlag = string(member.msgFlag)
+ req.NameCard = member.nameCard
+ req.ShutUpUntil = member.shutUpUntil
+
+ if data := member.GetAllCustomData(); data != nil {
+ req.AppMemberDefinedData = make([]customDataItem, 0, len(data))
+ for key, val := range data {
+ req.AppMemberDefinedData = append(req.AppMemberDefinedData, customDataItem{
+ Key: key,
+ Value: val,
+ })
+ }
+ }
+
+ if err = a.client.Post(serviceGroup, commandModifyGroupMemberInfo, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// DestroyGroup 解散群组
+// App管理员通过该接口解散群。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1624
+func (a *api) DestroyGroup(groupId string) (err error) {
+ req := &destroyGroupReq{GroupId: groupId}
+
+ if err = a.client.Post(serviceGroup, commandDestroyGroup, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// FetchMemberGroups 拉取用户所加入的群组
+// App管理员可以通过本接口获取某一用户加入的群信息。默认不获取用户已加入但未激活好友工作群(Work)以及直播群(AVChatRoom)群信息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1625
+func (a *api) FetchMemberGroups(arg *FetchMemberGroupsArg) (ret *FetchMemberGroupsRet, err error) {
+ req := &fetchMemberGroupsReq{UserId: arg.UserId, Limit: arg.Limit, Offset: arg.Offset, Type: arg.Type}
+
+ if arg.Filter != nil {
+ req.ResponseFilter = &responseFilter{
+ GroupBaseInfoFilter: arg.Filter.GetAllBaseInfoFilterFields(),
+ SelfInfoFilter: arg.Filter.GetAllMemberInfoFilterFields(),
+ }
+ }
+
+ if arg.IsWithNoActiveGroups {
+ req.WithNoActiveGroups = 1
+ }
+
+ if arg.IsWithLiveRoomGroups {
+ req.WithHugeGroups = 1
+ }
+
+ resp := &fetchMemberGroupsResp{}
+
+ if err = a.client.Post(serviceGroup, commandFetchMemberGroups, req, resp); err != nil {
+ return
+ }
+
+ ret = &FetchMemberGroupsRet{}
+ ret.Total = resp.TotalCount
+ ret.List = make([]*Group, 0, len(resp.GroupList))
+
+ if arg.Limit == 0 {
+ ret.HasMore = false
+ } else {
+ ret.HasMore = arg.Limit+arg.Offset < resp.TotalCount
+ }
+
+ for _, item := range resp.GroupList {
+ group := NewGroup()
+ group.id = item.GroupId
+ group.name = item.Name
+ group.groupType = item.Type
+ group.owner = item.OwnerUserId
+ group.avatar = item.FaceUrl
+ group.memberNum = item.MemberNum
+ group.maxMemberNum = item.MaxMemberNum
+ group.applyJoinOption = item.ApplyJoinOption
+ group.createTime = item.CreateTime
+ group.lastInfoTime = item.LastInfoTime
+ group.lastMsgTime = item.LastMsgTime
+ group.shutUpStatus = item.ShutUpAllMember
+ group.nextMsgSeq = item.NextMsgSeq
+
+ if item.AppDefinedData != nil && len(item.AppDefinedData) > 0 {
+ for _, v := range item.AppDefinedData {
+ group.SetCustomData(v.Key, v.Value)
+ }
+ }
+
+ if item.MemberInfo != nil {
+ member := &Member{
+ userId: arg.UserId,
+ role: item.MemberInfo.Role,
+ joinTime: item.MemberInfo.JoinTime,
+ nameCard: item.MemberInfo.NameCard,
+ msgSeq: item.MemberInfo.MsgSeq,
+ msgFlag: MsgFlag(item.MemberInfo.MsgFlag),
+ lastSendMsgTime: item.MemberInfo.LastSendMsgTime,
+ }
+
+ if item.MemberInfo.AppMemberDefinedData != nil && len(item.MemberInfo.AppMemberDefinedData) > 0 {
+ for _, v := range item.MemberInfo.AppMemberDefinedData {
+ member.SetCustomData(v.Key, v.Value)
+ }
+ }
+
+ group.AddMembers(member)
+ }
+
+ ret.List = append(ret.List, group)
+ }
+
+ return
+}
+
+// PullMemberGroups 续拉取用户所加入的群组
+// 本方法由“拉取用户所加入的群组(FetchMemberGroups)”拓展而来
+// App管理员可以通过本接口获取某一用户加入的群信息。默认不获取用户已加入但未激活好友工作群(Work)以及直播群(AVChatRoom)群信息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1625
+func (a *api) PullMemberGroups(arg *PullMemberGroupsArg, fn func(ret *FetchMemberGroupsRet)) (err error) {
+ var (
+ ret *FetchMemberGroupsRet
+ req = &FetchMemberGroupsArg{
+ UserId: arg.UserId,
+ Limit: arg.Limit,
+ Type: arg.Type,
+ Filter: arg.Filter,
+ IsWithNoActiveGroups: arg.IsWithNoActiveGroups,
+ IsWithLiveRoomGroups: arg.IsWithLiveRoomGroups,
+ }
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchMemberGroups(req)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ req.Offset += arg.Limit
+ }
+ }
+
+ return
+}
+
+// GetRolesInGroup 查询用户在群组中的身份
+// App管理员可以通过该接口获取一批用户在群内的身份,即“成员角色”。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1626
+func (a *api) GetRolesInGroup(groupId string, userIds []string) (roles map[string]string, err error) {
+ req := &getRolesInGroupReq{GroupId: groupId, UserIds: userIds}
+ resp := &getRolesInGroupResp{}
+
+ if err = a.client.Post(serviceGroup, commandGetRoleInGroup, req, resp); err != nil {
+ return
+ }
+
+ roles = make(map[string]string, len(resp.MemberRoleList))
+ for _, item := range resp.MemberRoleList {
+ roles[item.UserId] = item.Role
+ }
+
+ return
+}
+
+// ForbidSendMessage 批量禁言
+// App 管理员禁止指定群组中某些用户在一段时间内发言。
+// App 管理员取消对某些用户的禁言。
+// 被禁言用户退出群组之后再进入同一群组,禁言仍然有效。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1627
+func (a *api) ForbidSendMessage(groupId string, userIds []string, shutUpTime int64) (err error) {
+ req := &forbidSendMessageReq{
+ GroupId: groupId,
+ UserIds: userIds,
+ ShutUpTime: shutUpTime,
+ }
+
+ if err = a.client.Post(serviceGroup, commandForbidSendMsg, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// AllowSendMessage 取消禁言
+// 本方法由“批量禁言(ForbidSendMessage)”拓展而来
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1627
+func (a *api) AllowSendMessage(groupId string, userIds []string) (err error) {
+ return a.ForbidSendMessage(groupId, userIds, 0)
+}
+
+// GetShuttedUpMembers 获取被禁言群成员列表
+// App管理员可以根据群组ID获取群组中被禁言的用户列表。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2925
+func (a *api) GetShuttedUpMembers(groupId string) (shuttedUps map[string]int64, err error) {
+ req := &getShuttedUpMembersReq{GroupId: groupId}
+ resp := &getShuttedUpMembersResp{}
+
+ if err = a.client.Post(serviceGroup, commandGetGroupShuttedUin, req, resp); err != nil {
+ return
+ }
+
+ shuttedUps = make(map[string]int64)
+ for _, item := range resp.ShuttedUpList {
+ shuttedUps[item.UserId] = item.ShuttedUntil
+ }
+
+ return
+}
+
+// SendMessage 在群组中发送普通消息
+// App管理员可以通过该接口在群组中发送普通消息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1629
+func (a *api) SendMessage(groupId string, message *Message) (ret *SendMessageRet, err error) {
+ if err = message.checkSendError(); err != nil {
+ return
+ }
+
+ req := &sendMessageReq{}
+ req.GroupId = groupId
+ req.FromUserId = message.GetSender()
+ req.OfflinePushInfo = message.GetOfflinePushInfo()
+ req.MsgPriority = string(message.GetPriority())
+ req.MsgBody = message.GetBody()
+ req.Random = message.GetRandom()
+ req.CloudCustomData = conv.String(message.GetCustomData())
+ req.SendMsgControl = message.GetSendMsgControl()
+ req.ForbidCallbackControl = message.GetForbidCallbackControl()
+ req.OnlineOnlyFlag = int(message.GetOnlineOnlyFlag())
+
+ if message.atMembers != nil && len(message.atMembers) > 0 {
+ req.GroupAtInfo = make([]atInfo, 0, len(message.atMembers))
+
+ for userId, _ := range message.atMembers {
+ if userId == AtAllMembersFlag {
+ req.GroupAtInfo = append(req.GroupAtInfo, atInfo{
+ GroupAtAllFlag: 1,
+ })
+ } else {
+ req.GroupAtInfo = append(req.GroupAtInfo, atInfo{
+ GroupAtAllFlag: 0,
+ GroupAtUserId: userId,
+ })
+ }
+ }
+ }
+
+ resp := &sendMessageResp{}
+
+ if err = a.client.Post(serviceGroup, commandSendGroupMsg, req, resp); err != nil {
+ return
+ } else {
+ ret = &SendMessageRet{
+ MsgSeq: resp.MsgSeq,
+ MsgTime: resp.MsgTime,
+ }
+ }
+
+ return
+}
+
+// SendNotification 在群组中发送系统通知
+// App 管理员可以通过该接口在群组中发送系统通知。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1630
+func (a *api) SendNotification(groupId, content string, userIds ...string) (err error) {
+ req := &sendNotificationReq{GroupId: groupId, Content: content, UserIds: userIds}
+
+ if err = a.client.Post(serviceGroup, commandSendGroupSystemNotification, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// ChangeGroupOwner 转让群主
+// App 管理员可以通过该接口将群主身份转移给他人。
+// 没有群主的群,App 管理员可以通过此接口指定他人作为群主。
+// 新群主必须为群内成员。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1633
+func (a *api) ChangeGroupOwner(groupId, userId string) (err error) {
+ req := &changeGroupOwnerReq{GroupId: groupId, OwnerUserId: userId}
+
+ if err = a.client.Post(serviceGroup, commandChangeGroupOwner, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// RevokeMessage 撤回单条群消息
+// 本方法由“撤回多条群消息(RevokeMessages)”拓展而来
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/12341
+func (a *api) RevokeMessage(groupId string, msgSeq int) (err error) {
+ var results map[int]int
+
+ if results, err = a.RevokeMessages(groupId, msgSeq); err != nil {
+ return
+ }
+
+ if results[msgSeq] != enum.SuccessCode {
+ err = core.NewError(results[msgSeq], "message revoke failed")
+ return
+ }
+
+ return
+}
+
+// RevokeMessages 撤回多条群消息
+// App 管理员通过该接口撤回指定群组的消息,消息需要在漫游有效期以内。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/12341
+func (a *api) RevokeMessages(groupId string, msgSeq ...int) (results map[int]int, err error) {
+ req := revokeMessagesReq{}
+ req.GroupId = groupId
+ req.MsgSeqList = make([]msgSeqItem, 0, len(msgSeq))
+ for _, seq := range msgSeq {
+ req.MsgSeqList = append(req.MsgSeqList, msgSeqItem{
+ MsgSeq: seq,
+ })
+ }
+
+ resp := &revokeMessagesResp{}
+
+ if err = a.client.Post(serviceGroup, commandRecallGroupMsg, req, resp); err != nil {
+ return
+ }
+
+ results = make(map[int]int)
+ for _, item := range resp.Results {
+ results[item.MsgSeq] = item.RetCode
+ }
+
+ return
+}
+
+// ImportGroup 导入群基础资料
+// App 管理员可以通过该接口导入群组,不会触发回调、不会下发通知;当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群组数据。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1634
+func (a *api) ImportGroup(group *Group) (groupId string, err error) {
+ if err = group.checkImportError(); err != nil {
+ return
+ }
+
+ req := &importGroupReq{}
+ req.GroupId = group.id
+ req.OwnerUserId = group.owner
+ req.Type = group.groupType
+ req.Name = group.name
+ req.FaceUrl = group.avatar
+ req.Introduction = group.introduction
+ req.Notification = group.notification
+ req.MaxMemberNum = group.maxMemberNum
+ req.ApplyJoinOption = group.applyJoinOption
+ req.CreateTime = group.createTime
+
+ if data := group.GetAllCustomData(); data != nil {
+ req.AppDefinedData = make([]*customDataItem, 0, len(data))
+ for key, val := range data {
+ req.AppDefinedData = append(req.AppDefinedData, &customDataItem{
+ Key: key,
+ Value: val,
+ })
+ }
+ }
+
+ resp := &importGroupResp{}
+
+ if err = a.client.Post(serviceGroup, commandImportGroup, req, resp); err != nil {
+ return
+ } else {
+ groupId = resp.GroupId
+ }
+
+ return
+}
+
+// ImportMessages 导入群消息
+// 该 API 接口的作用是导入群组的消息,不会触发回调、不会下发通知。
+// 当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群消息数据。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1635
+func (a *api) ImportMessages(groupId string, messages ...*Message) (results []ImportMessagesResult, err error) {
+ req := &importMessagesReq{GroupId: groupId, Messages: make([]messageItem, 0, len(messages))}
+
+ for _, message := range messages {
+ if err = message.checkImportError(); err != nil {
+ return
+ }
+
+ req.Messages = append(req.Messages, messageItem{
+ FromUserId: message.GetSender(),
+ MsgBody: message.GetBody(),
+ SendTime: message.GetSendTime(),
+ Random: message.GetRandom(),
+ })
+ }
+
+ resp := &importMessagesResp{}
+
+ if err = a.client.Post(serviceGroup, commandImportGroupMsg, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// ImportMembers 导入多个群成员
+// 该 API 接口的作用是导入群组成员,不会触发回调、不会下发通知。
+// 当 App 需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议导入存量群成员数据。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1636
+func (a *api) ImportMembers(groupId string, members ...*Member) (results []ImportMemberResult, err error) {
+ req := &importMembersReq{GroupId: groupId, Members: make([]*memberItem, 0, len(members))}
+ resp := &importMembersResp{}
+
+ for _, member := range members {
+ req.Members = append(req.Members, &memberItem{
+ UserId: member.userId,
+ Role: member.role,
+ JoinTime: member.joinTime,
+ UnreadMsgNum: member.unreadMsgNum,
+ })
+ }
+
+ if err = a.client.Post(serviceGroup, commandImportGroupMember, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// SetMemberUnreadMsgNum 设置成员未读消息计数
+// App管理员使用该接口设置群组成员未读消息数,不会触发回调、不会下发通知。
+// 当App需要从其他即时通信系统迁移到即时通信 IM 时,使用该协议设置群成员的未读消息计数。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1637
+func (a *api) SetMemberUnreadMsgNum(groupId, userId string, unreadMsgNum int) (err error) {
+ req := &setMemberUnreadMsgNumReq{GroupId: groupId, UserId: userId, UnreadMsgNum: unreadMsgNum}
+
+ if err = a.client.Post(serviceGroup, commandSetUnreadMsgNum, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// RevokeMemberMessages 撤回指定用户发送的消息
+// 该API接口的作用是撤回最近1000条消息中指定用户发送的消息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2359
+func (a *api) RevokeMemberMessages(groupId, userId string) (err error) {
+ req := &revokeMemberMessagesReq{GroupId: groupId, UserId: userId}
+
+ if err = a.client.Post(serviceGroup, commandDeleteGroupMsgBySender, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// FetchMessages 拉取群历史消息
+// 即时通信 IM 的群消息是按 Seq 排序的,按照 server 收到群消息的顺序分配 Seq,先发的群消息 Seq 小,后发的 Seq 大。
+// 如果用户想拉取一个群的全量消息,首次拉取时不用填拉取 Seq,Server 会自动返回最新的消息,以后拉取时拉取 Seq 填上次返回的最小 Seq 减1。
+// 如果返回消息的 IsPlaceMsg 为1,表示这个 Seq 的消息或者过期、或者存储失败、或者被删除了。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2738
+func (a *api) FetchMessages(groupId string, limit int, msgSeq ...int) (ret *FetchMessagesRet, err error) {
+ req := &fetchMessagesReq{GroupId: groupId, ReqMsgNumber: limit}
+
+ if len(msgSeq) > 0 {
+ req.ReqMsgSeq = msgSeq[0]
+ }
+
+ resp := &fetchMessagesResp{}
+
+ if err = a.client.Post(serviceGroup, commandGetGroupSimpleMsg, req, resp); err != nil {
+ return
+ }
+
+ ret = &FetchMessagesRet{}
+ ret.IsFinished = resp.IsFinished
+
+ if ret.IsFinished == 0 {
+ ret.HasMore = true
+ }
+
+ if count := len(resp.RspMsgList); count > 0 {
+ ret.NextSeq = resp.RspMsgList[count-1].MsgSeq - 1
+
+ if ret.IsFinished == 1 && count == limit {
+ ret.HasMore = true
+ }
+ }
+
+ ret.List = make([]*Message, 0, len(resp.RspMsgList))
+ for _, item := range resp.RspMsgList {
+ message := NewMessage()
+ message.SetSender(item.FromUserId)
+ message.SetRandom(item.MsgRandom)
+ message.seq = item.MsgSeq
+ message.timestamp = item.MsgTimeStamp
+ message.status = MsgStatus(item.IsPlaceMsg)
+ switch item.MsgPriority {
+ case 1:
+ message.priority = MsgPriorityHigh
+ case 2:
+ message.priority = MsgPriorityNormal
+ case 3:
+ message.priority = MsgPriorityLow
+ case 4:
+ message.priority = MsgPriorityLowest
+ }
+ }
+
+ return
+}
+
+// PullMessages 续拉取群历史消息
+// 本方法由“拉取群历史消息(FetchMessages)”拓展而来
+// 即时通信 IM 的群消息是按 Seq 排序的,按照 server 收到群消息的顺序分配 Seq,先发的群消息 Seq 小,后发的 Seq 大。
+// 如果用户想拉取一个群的全量消息,首次拉取时不用填拉取 Seq,Server 会自动返回最新的消息,以后拉取时拉取 Seq 填上次返回的最小 Seq 减1。
+// 如果返回消息的 IsPlaceMsg 为1,表示这个 Seq 的消息或者过期、或者存储失败、或者被删除了。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2738
+func (a *api) PullMessages(groupId string, limit int, fn func(ret *FetchMessagesRet)) (err error) {
+ var (
+ ret *FetchMessagesRet
+ msgSeq int
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchMessages(groupId, limit, msgSeq)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ msgSeq = ret.NextSeq
+ }
+ }
+
+ return
+}
+
+// GetOnlineMemberNum 获取直播群在线人数
+// App 管理员可以根据群组 ID 获取直播群在线人数。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/49180
+func (a *api) GetOnlineMemberNum(groupId string) (num int, err error) {
+ req := &getOnlineMemberNumReq{GroupId: groupId}
+ resp := &getOnlineMemberNumResp{}
+
+ if err = a.client.Post(serviceGroup, commandGetOnlineMemberNum, req, resp); err != nil {
+ return
+ }
+
+ num = resp.OnlineMemberNum
+
+ return
+}
diff --git a/group/filter.go b/group/filter.go
new file mode 100644
index 0000000..c732c04
--- /dev/null
+++ b/group/filter.go
@@ -0,0 +1,210 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/8 10:17
+ * @Desc: 群组响应过滤器
+ */
+
+package group
+
+type (
+ // BaseInfoField 群基础信息字段
+ BaseInfoField string
+
+ // MemberInfoField 群成员信息字段
+ MemberInfoField string
+)
+
+const (
+ BaseFieldGroupId BaseInfoField = "GroupId" // 群组的唯一标识
+ BaseFieldType BaseInfoField = "Type" // 群组类型
+ BaseFieldName BaseInfoField = "Name" // 群组名称
+ BaseFieldIntroduction BaseInfoField = "Introduction" // 群组简介
+ BaseFieldNotification BaseInfoField = "Notification" // 群组公告
+ BaseFieldAvatar BaseInfoField = "FaceUrl" // 群组头像URL
+ BaseFieldOwner BaseInfoField = "Owner_Account" // 群主ID
+ BaseFieldCreateTime BaseInfoField = "CreateTime" // 群组的创建时间
+ BaseFieldInfoSeq BaseInfoField = "InfoSeq" // 群资料变更次数
+ BaseFieldLastInfoTime BaseInfoField = "LastInfoTime" // 群组最后一次信息变更时间
+ BaseFieldLastMsgTime BaseInfoField = "LastMsgTime" // 群组内最后发消息的时间
+ BaseFieldNextMsgSeq BaseInfoField = "NextMsgSeq" // 群内下一条消息的Seq
+ BaseFieldMemberNum BaseInfoField = "MemberNum" // 当前成员数量
+ BaseFieldMaxMemberNum BaseInfoField = "MaxMemberNum" // 最大成员数量
+ BaseFieldApplyJoinOption BaseInfoField = "ApplyJoinOption" // 申请加群选项
+
+ MemberFieldUserId MemberInfoField = "Member_Account" // 群成员ID
+ MemberFieldRole MemberInfoField = "Role" // 群内身份
+ MemberFieldJoinTime MemberInfoField = "JoinTime" // 入群时间
+ MemberFieldMsgSeq MemberInfoField = "MsgSeq" // 该成员当前已读消息Seq
+ MemberFieldMsgFlag MemberInfoField = "MsgFlag" // 消息接收选项
+ MemberFieldLastSendMsgTime MemberInfoField = "LastSendMsgTime" // 最后发送消息的时间
+ MemberFieldNameCard MemberInfoField = "NameCard" // 群名片
+)
+
+type Filter struct {
+ baseInfo map[string]bool
+ memberInfo map[string]bool
+ memberRole map[string]bool
+ groupCustomData map[string]bool
+ memberCustomData map[string]bool
+}
+
+// AddBaseInfoFilter 添加基础信息过滤器
+func (f *Filter) AddBaseInfoFilter(field BaseInfoField) {
+ if f.baseInfo == nil {
+ f.baseInfo = make(map[string]bool)
+ }
+
+ f.baseInfo[string(field)] = true
+}
+
+// RemBaseInfoFilter 移除基础信息过滤器
+func (f *Filter) RemBaseInfoFilter(field BaseInfoField) {
+ if f.baseInfo == nil {
+ return
+ }
+
+ delete(f.baseInfo, string(field))
+}
+
+// GetAllBaseInfoFilterFields 获取所有基础信息过滤器字段
+func (f *Filter) GetAllBaseInfoFilterFields() (filters []string) {
+ if f.baseInfo == nil {
+ return
+ }
+
+ filters = make([]string, 0, len(f.baseInfo))
+ for k, _ := range f.baseInfo {
+ filters = append(filters, k)
+ }
+
+ return
+}
+
+// AddMemberInfoFilter 添加成员信息过滤器
+func (f *Filter) AddMemberInfoFilter(field MemberInfoField) {
+ if f.memberInfo == nil {
+ f.memberInfo = make(map[string]bool)
+ }
+
+ f.memberInfo[string(field)] = true
+}
+
+// RemMemberInfoFilter 移除成员信息过滤器
+func (f *Filter) RemMemberInfoFilter(field MemberInfoField) {
+ if f.memberInfo == nil {
+ return
+ }
+
+ delete(f.memberInfo, string(field))
+}
+
+// GetAllMemberInfoFilterFields 获取所有成员信息过滤器字段
+func (f *Filter) GetAllMemberInfoFilterFields() (filters []string) {
+ if f.memberInfo == nil {
+ return
+ }
+
+ filters = make([]string, 0, len(f.memberInfo))
+ for k, _ := range f.memberInfo {
+ filters = append(filters, k)
+ }
+
+ return
+}
+
+// AddMemberRoleFilter 添加群成员角色过滤器
+func (f *Filter) AddMemberRoleFilter(field string) {
+ if f.memberRole == nil {
+ f.memberRole = make(map[string]bool)
+ }
+
+ f.memberRole[field] = true
+}
+
+// RemMemberRoleFilter 移除群成员角色过滤器
+func (f *Filter) RemMemberRoleFilter(field string) {
+ if f.memberRole == nil {
+ return
+ }
+
+ delete(f.memberRole, field)
+}
+
+// GetAllMemberRoleFilterValues 获取所有群成员角色过滤器值
+func (f *Filter) GetAllMemberRoleFilterValues() (filters []string) {
+ if f.memberRole == nil {
+ return
+ }
+
+ filters = make([]string, 0, len(f.memberRole))
+ for k, _ := range f.memberRole {
+ filters = append(filters, k)
+ }
+
+ return
+}
+
+// AddGroupCustomDataFilter 添加群自定义数据过滤器
+func (f *Filter) AddGroupCustomDataFilter(field string) {
+ if f.groupCustomData == nil {
+ f.groupCustomData = make(map[string]bool)
+ }
+
+ f.groupCustomData[field] = true
+}
+
+// RemGroupCustomDataFilter 移除群自定义数据过滤器
+func (f *Filter) RemGroupCustomDataFilter(field string) {
+ if f.groupCustomData == nil {
+ return
+ }
+
+ delete(f.groupCustomData, field)
+}
+
+// GetAllGroupCustomDataFilterFields 获取所有群自定义数据过滤器字段
+func (f *Filter) GetAllGroupCustomDataFilterFields() (filters []string) {
+ if f.groupCustomData == nil {
+ return
+ }
+
+ filters = make([]string, 0, len(f.groupCustomData))
+ for k, _ := range f.groupCustomData {
+ filters = append(filters, k)
+ }
+
+ return
+}
+
+// AddMemberCustomDataFilter 添加群成员自定义数据过滤器
+func (f *Filter) AddMemberCustomDataFilter(field string) {
+ if f.memberCustomData == nil {
+ f.memberCustomData = make(map[string]bool)
+ }
+
+ f.memberCustomData[field] = true
+}
+
+// RemMemberCustomDataFilter 移除群成员自定义数据过滤器
+func (f *Filter) RemMemberCustomDataFilter(field string) {
+ if f.memberCustomData == nil {
+ return
+ }
+
+ delete(f.memberCustomData, field)
+}
+
+// GetAllMemberCustomDataFilterFields 获取所有群成员自定义数据过滤器字段
+func (f *Filter) GetAllMemberCustomDataFilterFields() (filters []string) {
+ if f.memberCustomData == nil {
+ return
+ }
+
+ filters = make([]string, 0, len(f.memberCustomData))
+ for k, _ := range f.memberCustomData {
+ filters = append(filters, k)
+ }
+
+ return
+}
diff --git a/group/group.go b/group/group.go
new file mode 100644
index 0000000..82bbc28
--- /dev/null
+++ b/group/group.go
@@ -0,0 +1,383 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 17:11
+ * @Desc: TODO
+ */
+
+package group
+
+import (
+ "time"
+
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+)
+
+var (
+ errNotSetGroupType = core.NewError(enum.InvalidParamsCode, "group type is not set")
+ errNotSetGroupName = core.NewError(enum.InvalidParamsCode, "group name is not set")
+ errGroupNameTooLong = core.NewError(enum.InvalidParamsCode, "group name is too long")
+ errInvalidGroupType = core.NewError(enum.InvalidParamsCode, "invalid group type")
+ errGroupIntroductionTooLong = core.NewError(enum.InvalidParamsCode, "group introduction is too long")
+ errGroupNotificationTooLong = core.NewError(enum.InvalidParamsCode, "group notification is too long")
+)
+
+type (
+ // Type 群类型
+ Type string
+
+ // ApplyJoinOption 申请加群处理方式
+ ApplyJoinOption string
+
+ // ShutUpStatus 全员禁言状态
+ ShutUpStatus string
+)
+
+const (
+ TypePublic Type = "Public" // Public(陌生人社交群)
+ TypePrivate Type = "Private" // Private(即 Work,好友工作群)
+ TypeChatRoom Type = "ChatRoom" // ChatRoom(即 Meeting,会议群)
+ TypeLiveRoom Type = "AVChatRoom" // AVChatRoom(直播群)
+
+ ApplyJoinOptionFreeAccess ApplyJoinOption = "FreeAccess" // 自由加入
+ ApplyJoinOptionNeedPermission ApplyJoinOption = "NeedPermission" // 需要验证
+ ApplyJoinOptionDisableApply ApplyJoinOption = "DisableApply" // 禁止加群
+
+ ShutUpStatusOn ShutUpStatus = "On" // 开启
+ ShutUpStatusOff ShutUpStatus = "Off" // 关闭
+)
+
+type Group struct {
+ err error
+ id string // 群ID
+ name string // 群名称
+ groupType Type // 群类型
+ owner string // 群主ID
+ introduction string // 群简介
+ notification string // 群公告
+ avatar string // 群头像
+ memberNum uint // 群成员数
+ maxMemberNum uint // 最大群成员数量
+ applyJoinOption string // 申请加群处理方式
+ members []*Member // 群成员
+ customData map[string]interface{} // 群自定义数据
+ createTime int64 // 群创建时间
+ lastInfoTime int64 // 最后群资料变更时间
+ lastMsgTime int64 // 群内最后一条消息的时间
+ nextMsgSeq int // 群内下一条消息的Seq
+ shutUpStatus string // 群全员禁言状态
+}
+
+func NewGroup(id ...string) *Group {
+ group := &Group{}
+ if len(id) > 0 {
+ group.SetGroupId(id[0])
+ }
+ return group
+}
+
+// SetGroupId 设置群ID
+func (g *Group) SetGroupId(id string) {
+ g.id = id
+}
+
+// GetGroupId 获取群ID
+func (g *Group) GetGroupId() string {
+ return g.id
+}
+
+// SetOwner 设置群主ID
+func (g *Group) SetOwner(owner string) {
+ g.owner = owner
+}
+
+// GetOwner 获取群主ID
+func (g *Group) GetOwner() string {
+ return g.owner
+}
+
+// SetName 设置群名称
+func (g *Group) SetName(name string) {
+ g.name = name
+}
+
+// GetName 获取群名称
+func (g *Group) GetName() string {
+ return g.name
+}
+
+// SetGroupType 设置群类型
+func (g *Group) SetGroupType(groupType Type) {
+ g.groupType = groupType
+}
+
+// GetGroupType 获取群类型
+func (g *Group) GetGroupType() Type {
+ return g.groupType
+}
+
+// SetIntroduction 设置群简介
+func (g *Group) SetIntroduction(introduction string) {
+ g.introduction = introduction
+}
+
+// GetIntroduction 获取群简介
+func (g *Group) GetIntroduction() string {
+ return g.introduction
+}
+
+// SetNotification 设置群公告
+func (g *Group) SetNotification(notification string) {
+ g.notification = notification
+}
+
+// GetNotification 获取群公告
+func (g *Group) GetNotification() string {
+ return g.notification
+}
+
+// SetAvatar 设置群头像
+func (g *Group) SetAvatar(avatar string) {
+ g.avatar = avatar
+}
+
+// GetAvatar 获取群头像
+func (g *Group) GetAvatar() string {
+ return g.avatar
+}
+
+// SetMaxMemberNum 设置最大群成员数量
+func (g *Group) SetMaxMemberNum(maxMemberNum uint) {
+ g.maxMemberNum = maxMemberNum
+}
+
+// GetMaxMemberNum 获取最大群成员数量
+func (g *Group) GetMaxMemberNum() uint {
+ return g.maxMemberNum
+}
+
+// GetMemberNum 获取群成员数
+func (g *Group) GetMemberNum() uint {
+ return g.memberNum
+}
+
+// SetApplyJoinOption 设置申请加群处理方式
+func (g *Group) SetApplyJoinOption(applyJoinOption ApplyJoinOption) {
+ g.applyJoinOption = string(applyJoinOption)
+}
+
+// GetApplyJoinOption 获取申请加群处理方式
+func (g *Group) GetApplyJoinOption() string {
+ return g.applyJoinOption
+}
+
+// AddMembers 添加群成员
+func (g *Group) AddMembers(member ...*Member) {
+ if g.members == nil {
+ g.members = make([]*Member, 0)
+ }
+
+ g.members = append(g.members, member...)
+}
+
+// SetMembers 设置群成员
+func (g *Group) SetMembers(member ...*Member) {
+ if g.members != nil {
+ g.members = g.members[0:0]
+ }
+
+ g.AddMembers(member...)
+}
+
+// SetCustomData 设置自定义数据
+func (g *Group) SetCustomData(name string, value interface{}) {
+ if g.customData == nil {
+ g.customData = make(map[string]interface{})
+ }
+
+ g.customData[name] = value
+}
+
+// GetCustomData 获取自定义数据
+func (g *Group) GetCustomData(name string) (val interface{}, exist bool) {
+ if g.customData == nil {
+ return
+ }
+
+ val, exist = g.customData[name]
+
+ return
+}
+
+// GetAllCustomData 获取所有自定义数据
+func (g *Group) GetAllCustomData() map[string]interface{} {
+ return g.customData
+}
+
+// GetMembers 获取群成员
+func (g *Group) GetMembers() []*Member {
+ return g.members
+}
+
+// GetGroupCreateTime 获取群创建时间
+func (g *Group) GetGroupCreateTime() time.Time {
+ return time.Unix(g.createTime, 0)
+}
+
+// GetLastInfoTime 获取最后群资料变更时间
+func (g *Group) GetLastInfoTime() time.Time {
+ return time.Unix(g.lastInfoTime, 0)
+}
+
+// GetLastMsgTime 获取群内最后一条消息的时间
+func (g *Group) GetLastMsgTime() time.Time {
+ return time.Unix(g.lastMsgTime, 0)
+}
+
+// GetNextMsgSeq 获取群内下一条消息的Seq
+func (g *Group) GetNextMsgSeq() int {
+ return g.nextMsgSeq
+}
+
+// SetShutUpStatus 设置全员禁言状态
+func (g *Group) SetShutUpStatus(shutUpStatus ShutUpStatus) {
+ g.shutUpStatus = string(shutUpStatus)
+}
+
+// GetShutUpStatus 获取群全员禁言状态
+func (g *Group) GetShutUpStatus() string {
+ return g.shutUpStatus
+}
+
+// SetCreateTime 设置群组创建时间
+func (g *Group) SetCreateTime(createTime int64) {
+ g.createTime = createTime
+}
+
+// GetCreateTime 获取群组创建时间
+func (g *Group) GetCreateTime() int64 {
+ return g.createTime
+}
+
+// IsValid 检测用户是否有效
+func (g *Group) IsValid() bool {
+ return g.err == nil
+}
+
+// GetError 获取异常错误
+func (g *Group) GetError() error {
+ return g.err
+}
+
+// 设置异常错误
+func (g *Group) setError(code int, message string) {
+ if code != enum.SuccessCode {
+ g.err = core.NewError(code, message)
+ }
+}
+
+// 检测创建错误
+func (g *Group) checkCreateError() (err error) {
+ if err = g.checkTypeArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkNameArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkIntroductionArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkNotificationArgError(); err != nil {
+ return
+ }
+
+ return
+}
+
+// 检测导入错误
+func (g *Group) checkImportError() (err error) {
+ if err = g.checkTypeArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkNameArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkIntroductionArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkNotificationArgError(); err != nil {
+ return
+ }
+
+ return
+}
+
+// 检测更新错误
+func (g *Group) checkUpdateError() (err error) {
+ if err = g.checkNameArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkIntroductionArgError(); err != nil {
+ return
+ }
+
+ if err = g.checkNotificationArgError(); err != nil {
+ return
+ }
+
+ return
+}
+
+// 检测群名称参数错误
+func (g *Group) checkNameArgError() error {
+ if g.name == "" {
+ return errNotSetGroupName
+ }
+
+ if len(g.name) > 30 {
+ return errGroupNameTooLong
+ }
+
+ return nil
+}
+
+// 检测群类型参数错误
+func (g *Group) checkTypeArgError() error {
+ if g.groupType == "" {
+ return errNotSetGroupType
+ }
+
+ switch Type(g.groupType) {
+ case TypePublic, TypePrivate, TypeChatRoom, TypeLiveRoom:
+ default:
+ return errInvalidGroupType
+ }
+
+ return nil
+}
+
+// 检测群简介参数错误
+func (g *Group) checkIntroductionArgError() error {
+ if len(g.introduction) > 240 {
+ return errGroupIntroductionTooLong
+ }
+
+ return nil
+}
+
+// 检测群公告参数错误
+func (g *Group) checkNotificationArgError() error {
+ if len(g.notification) > 300 {
+ return errGroupNotificationTooLong
+ }
+
+ return nil
+}
diff --git a/group/member.go b/group/member.go
new file mode 100644
index 0000000..195b57c
--- /dev/null
+++ b/group/member.go
@@ -0,0 +1,160 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 18:06
+ * @Desc: TODO
+ */
+
+package group
+
+import (
+ "errors"
+ "time"
+)
+
+var errNotSetUserId = errors.New("member's userid is not set")
+
+type (
+ // MsgFlag 消息接收选项
+ MsgFlag string
+)
+
+const (
+ MsgFlagAcceptAndNotify MsgFlag = "AcceptAndNotify" // 接收并提示
+ MsgFlagAcceptNotNotify MsgFlag = "AcceptNotNotify" // 接收不提示(不会触发 APNs 远程推送)
+ MsgFlagDiscard MsgFlag = "Discard" // 屏蔽群消息(不会向客户端推送消息)
+)
+
+type Member struct {
+ userId string // 成员ID
+ role string // 群内身份
+ joinTime int64 // 加入时间
+ nameCard string // 群名片
+ msgSeq int // 成员当前已读消息Seq
+ msgFlag MsgFlag // 消息接收选项
+ lastSendMsgTime int64 // 最后发送消息的时间
+ shutUpUntil *int64 // 需禁言时间,单位为秒,0表示取消禁言
+ unreadMsgNum int // 未读入群成员的未读消息计数
+ customData map[string]interface{} // 自定义数据
+}
+
+func NewMember(userId ...string) *Member {
+ member := &Member{}
+ if len(userId) > 0 {
+ member.SetUserId(userId[0])
+ }
+ return member
+}
+
+// SetUserId 设置群成员ID
+func (m *Member) SetUserId(userId string) {
+ m.userId = userId
+}
+
+// GetUserId 获取群成员ID
+func (m *Member) GetUserId() string {
+ return m.userId
+}
+
+// SetRole 设置群内身份
+func (m *Member) SetRole(role string) {
+ m.role = role
+}
+
+// GetRole 获取群内身份
+func (m *Member) GetRole() string {
+ return m.role
+}
+
+// SetJoinTime 设置加入时间
+func (m *Member) SetJoinTime(joinTime time.Time) {
+ m.joinTime = joinTime.Unix()
+}
+
+// GetJoinTime 获取加入时间
+func (m *Member) GetJoinTime() time.Time {
+ return time.Unix(m.joinTime, 0)
+}
+
+// SetNameCard 设置群名片
+func (m *Member) SetNameCard(nameCard string) {
+ m.nameCard = nameCard
+}
+
+// GetNameCard 获取群名片
+func (m *Member) GetNameCard() string {
+ return m.nameCard
+}
+
+// GetMsgSeq 获取成员当前已读消息Seq
+func (m *Member) GetMsgSeq() int {
+ return m.msgSeq
+}
+
+// SetMsgFlag 设置消息接收选项
+func (m *Member) SetMsgFlag(msgFlag MsgFlag) {
+ m.msgFlag = msgFlag
+}
+
+// GetMsgFlag 获取消息接收选项
+func (m *Member) GetMsgFlag() MsgFlag {
+ return m.msgFlag
+}
+
+// SetShutUpUntil 设置需禁言时间,单位为秒,0表示取消禁言
+func (m *Member) SetShutUpUntil(shutUpUntil int64) {
+ m.shutUpUntil = &shutUpUntil
+}
+
+// GetShutUpUntil 获取需禁言时间,单位为秒,0表示取消禁言
+func (m *Member) GetShutUpUntil() int64 {
+ if m.shutUpUntil == nil {
+ return 0
+ } else {
+ return *m.shutUpUntil
+ }
+}
+
+// SetUnreadMsgNum 设置成员的未读消息计数
+func (m *Member) SetUnreadMsgNum(unreadMsgNum int) {
+ m.unreadMsgNum = unreadMsgNum
+}
+
+// GetUnreadMsgNum 获取成员的未读消息计数
+func (m *Member) GetUnreadMsgNum() int {
+ return m.unreadMsgNum
+}
+
+// SetCustomData 设置自定义数据
+func (m *Member) SetCustomData(name string, value interface{}) {
+ if m.customData == nil {
+ m.customData = make(map[string]interface{})
+ }
+
+ m.customData[name] = value
+}
+
+// GetCustomData 获取自定义数据
+func (m *Member) GetCustomData(name string) (val interface{}, exist bool) {
+ if m.customData == nil {
+ return
+ }
+
+ val, exist = m.customData[name]
+
+ return
+}
+
+// GetAllCustomData 获取所有自定义数据
+func (m *Member) GetAllCustomData() map[string]interface{} {
+ return m.customData
+}
+
+// 检测参数错误
+func (m *Member) checkError() (err error) {
+ if m.userId == "" {
+ return errNotSetUserId
+ }
+
+ return nil
+}
diff --git a/group/message.go b/group/message.go
new file mode 100644
index 0000000..22922fc
--- /dev/null
+++ b/group/message.go
@@ -0,0 +1,221 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/8/31 18:04
+ * @Desc: 私聊消息实体
+ */
+
+package group
+
+import (
+ "errors"
+
+ "git.echol.cn/loser/tencent-im/internal/entity"
+)
+
+var (
+ errNotSetSender = errors.New("message's sender not set")
+ errNotSetSendTime = errors.New("message's send time not set")
+)
+
+type (
+ // MsgOnlineOnlyFlag 只发送在线成员标识
+ MsgOnlineOnlyFlag int
+
+ // MsgPriority 消息优先级
+ MsgPriority string
+
+ // MsgStatus 消息状态
+ MsgStatus int
+)
+
+const (
+ MsgOnlineOnlyFlagNo MsgOnlineOnlyFlag = 0 // 发送所有成员
+ MsgOnlineOnlyFlagYes MsgOnlineOnlyFlag = 1 // 仅发送在线成员
+
+ MsgPriorityHigh MsgPriority = "High" // 高优先级消息
+ MsgPriorityNormal MsgPriority = "Normal" // 普通优先级消息
+ MsgPriorityLow MsgPriority = "Low" // 低优先级消息
+ MsgPriorityLowest MsgPriority = "Lowest" // 最低优先级消息
+
+ MsgStatusNormal MsgStatus = 0 // 正常消息
+ MsgStatusInvalid MsgStatus = 1 // 被删除或者消息过期的消息
+ MsgStatusRevoked MsgStatus = 2 // 被撤回的消息
+
+ AtAllMembersFlag = "@all" // @所有成员的标识
+)
+
+type Message struct {
+ entity.Message
+ priority MsgPriority // 消息的优先级
+ onlineOnlyFlag MsgOnlineOnlyFlag // 仅发送在线成员标识
+ sendTime int64 // 消息发送时间
+ timestamp int64 // 消息时间戳,UNIX 时间戳(单位:秒)
+ seq int // 消息序列号
+ status MsgStatus // 消息状态
+ customData interface{} // 自定义数据
+ sendControls map[string]bool // 发送消息控制
+ callbackControls map[string]bool // 禁用回调
+ atMembers map[string]bool // @用户
+}
+
+func NewMessage() *Message {
+ return &Message{}
+}
+
+// SetPriority 设置消息优先级
+func (m *Message) SetPriority(priority MsgPriority) {
+ m.priority = priority
+}
+
+// GetPriority 获取消息优先级
+func (m *Message) GetPriority() MsgPriority {
+ return m.priority
+}
+
+// SetCustomData 设置自定义数据
+func (m *Message) SetCustomData(data interface{}) {
+ m.customData = data
+}
+
+// GetCustomData 获取自定义数据
+func (m *Message) GetCustomData() interface{} {
+ return m.customData
+}
+
+// SetOnlineOnlyFlag 设置仅发送在线成员标识
+func (m *Message) SetOnlineOnlyFlag(flag MsgOnlineOnlyFlag) {
+ m.onlineOnlyFlag = flag
+}
+
+// GetOnlineOnlyFlag 获取仅发送在线成员标识
+func (m *Message) GetOnlineOnlyFlag() MsgOnlineOnlyFlag {
+ return m.onlineOnlyFlag
+}
+
+// SetSendTime 设置发送时间
+func (m *Message) SetSendTime(sendTime int64) {
+ m.sendTime = sendTime
+}
+
+// GetSendTime 获取发送时间
+func (m *Message) GetSendTime() int64 {
+ return m.sendTime
+}
+
+// GetStatus 获取消息状态
+func (m *Message) GetStatus() MsgStatus {
+ return m.status
+}
+
+// SetForbidBeforeSendMsgCallback 设置禁止发消息前回调
+func (m *Message) SetForbidBeforeSendMsgCallback() {
+ if m.callbackControls == nil {
+ m.callbackControls = make(map[string]bool, 0)
+ }
+ m.callbackControls["ForbidBeforeSendMsgCallback"] = true
+}
+
+// SetForbidAfterSendMsgCallback 设置禁止发消息后回调
+func (m *Message) SetForbidAfterSendMsgCallback() {
+ if m.callbackControls == nil {
+ m.callbackControls = make(map[string]bool, 0)
+ }
+ m.callbackControls["ForbidAfterSendMsgCallback"] = true
+}
+
+// GetForbidCallbackControl 获取消息回调禁止开关
+func (m *Message) GetForbidCallbackControl() (controls []string) {
+ if m.callbackControls != nil {
+ if n := len(m.callbackControls); n > 0 {
+ controls = make([]string, 0, n)
+ for k := range m.callbackControls {
+ controls = append(controls, k)
+ }
+ }
+ }
+
+ return
+}
+
+// SetNoUnread 设置该条消息不计入未读数
+func (m *Message) SetNoUnread() {
+ if m.sendControls == nil {
+ m.sendControls = make(map[string]bool, 0)
+ }
+ m.sendControls["NoUnread"] = true
+}
+
+// SetNoLastMsg 设置该条消息不更新会话列表
+func (m *Message) SetNoLastMsg() {
+ if m.sendControls == nil {
+ m.sendControls = make(map[string]bool, 0)
+ }
+ m.sendControls["NoLastMsg"] = true
+}
+
+// GetSendMsgControl 获取消息发送控制选项
+func (m *Message) GetSendMsgControl() (controls []string) {
+ if m.sendControls != nil {
+ if n := len(m.sendControls); n > 0 {
+ controls = make([]string, 0, n)
+ for k := range m.sendControls {
+ controls = append(controls, k)
+ }
+ }
+ }
+
+ return
+}
+
+// AtMembers @某个成员
+func (m *Message) AtMembers(userId ...string) {
+ if m.atMembers == nil {
+ m.atMembers = make(map[string]bool)
+ }
+
+ for _, id := range userId {
+ m.atMembers[id] = true
+ }
+}
+
+// AtAllMembers @所有成员
+func (m *Message) AtAllMembers() {
+ m.AtMembers(AtAllMembersFlag)
+}
+
+// ClearAtMembers 清空所有的的@成员
+func (m *Message) ClearAtMembers() {
+ m.atMembers = nil
+}
+
+// GetTimestamp 获取消息的时间戳
+func (m *Message) GetTimestamp() int64 {
+ return m.timestamp
+}
+
+// 检测发送错误
+func (m *Message) checkSendError() (err error) {
+ if err = m.CheckBodyArgError(); err != nil {
+ return
+ }
+
+ return
+}
+
+// 检测导入错误
+func (m *Message) checkImportError() (err error) {
+ if m.GetSender() == "" {
+ return errNotSetSender
+ }
+
+ if m.sendTime == 0 {
+ return errNotSetSendTime
+ }
+
+ if err = m.CheckBodyArgError(); err != nil {
+ return
+ }
+
+ return
+}
diff --git a/group/types.go b/group/types.go
new file mode 100644
index 0000000..8a5e318
--- /dev/null
+++ b/group/types.go
@@ -0,0 +1,508 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 17:43
+ * @Desc: 群组管理
+ */
+
+package group
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ // 拉取App中的所有群组(请求)
+ fetchGroupIdsReq struct {
+ Limit int `json:"Limit,omitempty"` // (选填)本次获取的群组 ID 数量的上限,不得超过 10000。如果不填,默认为最大值 10000
+ Next int `json:"Next,omitempty"` // (选填)群太多时分页拉取标志,第一次填0,以后填上一次返回的值,返回的 Next 为0代表拉完了
+ Type string `json:"Type,omitempty"` // (选填)如果仅需要返回特定群组形态的群组,可以通过 Type 进行过滤,但此时返回的 TotalCount 的含义就变成了 App 中属于该群组形态的群组总数。不填为获取所有类型的群组。
+ }
+
+ // 拉取App中的所有群组(响应)
+ fetchGroupIdsResp struct {
+ types.ActionBaseResp
+ Next int `json:"Next"` // 分页拉取的标志
+ TotalCount int `json:"TotalCount"` // App 当前的群组总数。
+ GroupIdList []groupIdItem `json:"GroupIdList"` // 获取到的群组 ID 的集合
+ }
+
+ // FetchGroupIdsRet 拉取App中的所有群组ID返回
+ FetchGroupIdsRet struct {
+ Total int // App 当前的群组总数
+ Next int // 分页拉取的标志
+ HasMore bool // 是否还有更多数据
+ List []string // 群组ID列表
+ }
+
+ // FetchGroupsRet 拉取APP中的所有群返回
+ FetchGroupsRet struct {
+ Total int // App 当前的群组总数
+ Next int // 分页拉取的标志
+ HasMore bool // 是否还有更多数据
+ List []*Group // 群组列表
+ }
+
+ // PullGroupsArg 续拉取群信息(参数)
+ PullGroupsArg struct {
+ Limit int // 分页限制
+ Type Type // 群组类型
+ Filter *Filter // 过滤器
+ }
+
+ // 群ID
+ groupIdItem struct {
+ GroupId string `json:"GroupId"` // 群ID
+ }
+
+ // 自定义数据
+ customDataItem struct {
+ Key string `json:"Key"`
+ Value interface{} `json:"Value"`
+ }
+
+ // 创建群(请求)
+ createGroupReq struct {
+ OwnerUserId string `json:"Owner_Account,omitempty"` // (选填)群主 ID(需是 已导入 的账号)。填写后自动添加到群成员中;如果不填,群没有群主
+ GroupId string `json:"GroupId,omitempty"` // (选填)为了使得群组 ID 更加简单,便于记忆传播,腾讯云支持 App 在通过 REST API 创建群组时 自定义群组 ID
+ Type Type `json:"Type"` // (必填)群组形态,包括 Public(陌生人社交群),Private(即 Work,好友工作群),ChatRoom(即 Meeting,会议群),AVChatRoom(直播群)
+ Name string `json:"Name"` // (必填)群名称,最长30字节,使用 UTF-8 编码,1个汉字占3个字节
+ Introduction string `json:"Introduction,omitempty"` // (选填)群简介,最长240字节,使用 UTF-8 编码,1个汉字占3个字节
+ Notification string `json:"Notification,omitempty"` // (选填)群公告,最长300字节,使用 UTF-8 编码,1个汉字占3个字节
+ FaceUrl string `json:"FaceUrl,omitempty"` // (选填)群头像 URL,最长100字节
+ MaxMemberNum uint `json:"MaxMemberCount,omitempty"` // (选填)最大群成员数量,缺省时的默认值:付费套餐包上限,例如体验版是20,如果升级套餐包,需按照修改群基础资料修改这个字段
+ ApplyJoinOption string `json:"ApplyJoinOption,omitempty"` // (选填)申请加群处理方式。包含 FreeAccess(自由加入),NeedPermission(需要验证),DisableApply(禁止加群),不填默认为 NeedPermission(需要验证) 仅当创建支持申请加群的 群组 时,该字段有效
+ AppDefinedData []*customDataItem `json:"AppDefinedData,omitempty"` // (选填)群组维度的自定义字段,默认情况是没有的,可以通过 即时通信 IM 控制台 进行配置,详情请参阅 自定义字段
+ MemberList []*memberItem `json:"MemberList,omitempty"` // (选填)初始群成员列表,最多100个;成员信息字段详情请参阅 群成员资料
+ }
+
+ // 创建群(响应)
+ createGroupResp struct {
+ types.ActionBaseResp
+ GroupId string `json:"GroupId"` // 群ID
+ }
+
+ // 群成员信息
+ memberItem struct {
+ UserId string `json:"Member_Account"` // 群成员ID
+ Role string `json:"Role,omitempty"` // 群内身份
+ JoinTime int64 `json:"JoinTime,omitempty"` // 入群时间
+ MsgSeq int `json:"MsgSeq,omitempty"` // 该成员当前已读消息Seq
+ MsgFlag string `json:"MsgFlag,omitempty"` // 消息接收选项
+ LastSendMsgTime int64 `json:"LastSendMsgTime,omitempty"` // 最后发送消息的时间
+ NameCard string `json:"NameCard,omitempty"` // 群名片
+ ShutUpUntil int64 `json:"ShutUpUntil"` // 禁言截至时间
+ UnreadMsgNum int `json:"UnreadMsgNum,omitempty"` // 待导入群成员的未读消息计数
+ AppMemberDefinedData []*customDataItem `json:"AppMemberDefinedData,omitempty"` // 群成员自定义数据
+ }
+
+ // 解散群(请求)
+ destroyGroupReq struct {
+ GroupId string `json:"GroupId"` // (必填)操作的群 ID
+ }
+
+ // 响应过滤器
+ responseFilter struct {
+ GroupBaseInfoFilter []string `json:"GroupBaseInfoFilter,omitempty"`
+ MemberInfoFilter []string `json:"MemberInfoFilter,omitempty"`
+ GroupCustomDataFilter []string `json:"AppDefinedDataFilter_Group,omitempty"`
+ MemberCustomDataFilter []string `json:"AppDefinedDataFilter_GroupMember,omitempty"`
+ SelfInfoFilter []string `json:"SelfInfoFilter,omitempty"`
+ }
+
+ // 获取群详细资料(请求)
+ getGroupsReq struct {
+ GroupIds []string `json:"GroupIdList"`
+ ResponseFilter *responseFilter `json:"ResponseFilter,omitempty"`
+ }
+
+ // 获取群详细资料(响应)
+ getGroupsResp struct {
+ types.ActionBaseResp
+ GroupInfos []*groupInfo `json:"GroupInfo"`
+ }
+
+ groupInfo struct {
+ GroupId string `json:"GroupId"`
+ ErrorCode int `json:"ErrorCode"`
+ ErrorInfo string `json:"ErrorInfo"`
+ Type Type `json:"Type"`
+ Name string `json:"Name"`
+ AppId int `json:"Appid"`
+ Introduction string `json:"Introduction"`
+ Notification string `json:"Notification"`
+ FaceUrl string `json:"FaceUrl"`
+ OwnerUserId string `json:"Owner_Account"`
+ CreateTime int64 `json:"CreateTime"`
+ LastInfoTime int64 `json:"LastInfoTime"`
+ LastMsgTime int64 `json:"LastMsgTime"`
+ NextMsgSeq int `json:"NextMsgSeq"`
+ MemberNum uint `json:"MemberNum"`
+ MaxMemberNum uint `json:"MaxMemberNum"`
+ ApplyJoinOption string `json:"ApplyJoinOption"`
+ ShutUpAllMember string `json:"ShutUpAllMember"`
+ AppDefinedData []customDataItem `json:"AppDefinedData"`
+ MemberList []memberItem `json:"MemberList"`
+ MemberInfo *memberItem `json:"SelfInfo,omitempty"` // 成员在群中的信息(仅在获取用户所加入的群组接口返回)
+ }
+
+ // 获取群成员详细资料(请求)
+ fetchMembersReq struct {
+ GroupId string `json:"GroupId"` // (必填)需要拉取成员信息的群组的 ID
+ Limit int `json:"Limit"` // (选填)一次最多获取多少个成员的资料,不得超过6000。如果不填,则获取群内全部成员的信息
+ Offset int `json:"Offset"` // (选填)从第几个成员开始获取,如果不填则默认为0,表示从第一个成员开始获取
+ MemberInfoFilter []string `json:"MemberInfoFilter"` // (选填)需要获取哪些信息, 如果没有该字段则为群成员全部资料,成员信息字段详情请参阅 群成员资料
+ MemberRoleFilter []string `json:"MemberRoleFilter"` // (选填)拉取指定身份的群成员资料。如没有填写该字段,默认为所有身份成员资料,成员身份可以为:“Owner”,“Admin”,“Member”
+ MemberCustomDataFilter []string `json:"AppDefinedDataFilter_GroupMember"` // (选填)默认情况是没有的。该字段用来群成员维度的自定义字段过滤器,指定需要获取的群成员维度的自定义字段,群成员维度的自定义字段详情请参阅 自定义字段
+ }
+
+ // 获取群成员详细资料(响应)
+ fetchMembersResp struct {
+ types.ActionBaseResp
+ MemberNum int `json:"MemberNum"` // 本群组的群成员总数
+ MemberList []memberItem `json:"MemberList"` // 获取到的群成员列表,其中包含了全部或者指定的群成员信息,成员信息字段详情请参阅 群成员资料
+ }
+
+ // FetchMembersRet 拉取群成员结果
+ FetchMembersRet struct {
+ Total int // 成员数量
+ HasMore bool // 是否还有更多数据
+ List []*Member // 成员列表
+ }
+
+ // PullMembersArg 续拉取群成员(参数)
+ PullMembersArg struct {
+ GroupId string // (必填)需要拉取成员信息的群组的 ID
+ Limit int // (选填)一次最多获取多少个成员的资料,不得超过6000。如果不填,则获取群内全部成员的信息
+ Filter *Filter // (选填)返回过滤器
+ }
+
+ // 修改群基础资料(请求)
+ updateGroupReq struct {
+ GroupId string `json:"GroupId"`
+ Name string `json:"Name,omitempty"`
+ Introduction string `json:"Introduction,omitempty"`
+ Notification string `json:"Notification,omitempty"`
+ FaceUrl string `json:"FaceUrl,omitempty"`
+ MaxMemberNum uint `json:"MaxMemberNum,omitempty"`
+ ApplyJoinOption string `json:"ApplyJoinOption,omitempty"`
+ ShutUpAllMember string `json:"ShutUpAllMember,omitempty"`
+ AppDefinedData []customDataItem `json:"AppDefinedData,omitempty"`
+ }
+
+ // 添加群成员(请求)
+ addMembersReq struct {
+ GroupId string `json:"GroupId"`
+ Silence int `json:"Silence,omitempty"`
+ MemberList []addMemberItem `json:"MemberList"`
+ }
+
+ // 添加群成员(响应)
+ addMembersResp struct {
+ types.ActionBaseResp
+ MemberList []AddMembersResult `json:"MemberList"`
+ }
+
+ addMemberItem struct {
+ UserId string `json:"Member_Account"`
+ }
+
+ // AddMembersResult 添加群成员结果
+ AddMembersResult struct {
+ UserId string `json:"Member_Account"`
+ Result int `json:"Result"`
+ }
+
+ // 删除群成员(请求)
+ deleteMembersReq struct {
+ GroupId string `json:"GroupId"` // (必填)操作的群ID
+ Silence int `json:"Silence"` // (选填)是否静默删人
+ Reason string `json:"Reason"` // (选填)踢出用户原因
+ UserIds []string `json:"MemberToDel_Account"` // (必填)待删除的群成员
+ }
+
+ // 修改群成员资料(请求)
+ updateMemberReq struct {
+ GroupId string `json:"GroupId"` // (必填)群ID
+ UserId string `json:"Member_Account"` // (必填)群成员ID
+ Role string `json:"Role,omitempty"` // (选填)群内身份
+ NameCard string `json:"NameCard,omitempty"` // (选填)群名片
+ MsgFlag string `json:"MsgFlag,omitempty"` // (选填)消息接收选项
+ ShutUpUntil *int64 `json:"ShutUpUntil,omitempty"` // (选填)禁言截至时间
+ AppMemberDefinedData []customDataItem `json:"AppMemberDefinedData,omitempty"` // (选填)群成员自定义数据
+ }
+
+ // FetchMemberGroupsArg 拉取用户所加入的群组(参数)
+ FetchMemberGroupsArg struct {
+ UserId string // (必填)用户ID
+ Limit int // (选填)单次拉取的群组数量,如果不填代表所有群组
+ Offset int // (选填)从第多少个群组开始拉取
+ Type Type // (选填)拉取哪种群组类型
+ Filter *Filter // (选填)过滤器
+ IsWithNoActiveGroups bool // (选填)是否获取用户已加入但未激活的 Private(即新版本中 Work,好友工作群) 群信息
+ IsWithLiveRoomGroups bool // (选填)是否获取用户加入的 AVChatRoom(直播群)
+ }
+
+ // FetchMemberGroupsRet 拉取用户所加入的群组(返回)
+ FetchMemberGroupsRet struct {
+ Total int // 群组总数
+ HasMore bool // 是否还有更多数据
+ List []*Group // 列表
+ }
+
+ // PullMemberGroupsArg 续拉取用户所加入的群组(参数)
+ PullMemberGroupsArg struct {
+ UserId string // (必填)用户ID
+ Limit int // (选填)单次拉取的群组数量,如果不填代表所有群组
+ Type Type // (选填)拉取哪种群组类型
+ Filter *Filter // (选填)过滤器
+ IsWithNoActiveGroups bool // (选填)是否获取用户已加入但未激活的 Private(即新版本中 Work,好友工作群) 群信息
+ IsWithLiveRoomGroups bool // (选填)是否获取用户加入的 AVChatRoom(直播群)
+ }
+
+ // 拉取用户所加入的群组(请求)
+ fetchMemberGroupsReq struct {
+ UserId string `json:"Member_Account"` // (必填)用户ID
+ Limit int `json:"Limit,omitempty"` // (选填)单次拉取的群组数量,如果不填代表所有群组
+ Offset int `json:"Offset,omitempty"` // (选填)从第多少个群组开始拉取
+ Type Type `json:"Type,omitempty"` // (选填)拉取哪种群组类型
+ WithHugeGroups int `json:"WithHugeGroups,omitempty"`
+ WithNoActiveGroups int `json:"WithNoActiveGroups,omitempty"`
+ ResponseFilter *responseFilter `json:"ResponseFilter,omitempty"` // (选填)响应过滤
+ }
+
+ // 拉取用户所加入的群组(响应)
+ fetchMemberGroupsResp struct {
+ types.ActionBaseResp
+ TotalCount int `json:"TotalCount"`
+ GroupList []groupInfo `json:"GroupIdList"`
+ }
+
+ // 获取
+ getRolesInGroupReq struct {
+ GroupId string `json:"GroupId"`
+ UserIds []string `json:"User_Account"`
+ }
+
+ getRolesInGroupResp struct {
+ types.ActionBaseResp
+ MemberRoleList []memberRole `json:"UserIdList"`
+ }
+
+ memberRole struct {
+ UserId string `json:"Member_Account"`
+ Role string `json:"Role"`
+ }
+
+ // 批量禁言(请求)
+ forbidSendMessageReq struct {
+ GroupId string `json:"GroupId"` // (必填)需要查询的群组 ID
+ UserIds []string `json:"Members_Account"` // (必填)需要禁言的用户帐号,最多支持500个帐号
+ ShutUpTime int64 `json:"ShutUpTime"` // (必填)需禁言时间,单位为秒,为0时表示取消禁言,4294967295为永久禁言。
+ }
+
+ // 获取被禁言群成员列表(请求)
+ getShuttedUpMembersReq struct {
+ GroupId string `json:"GroupId"` // (必填)需要获取被禁言成员列表的群组 ID
+ }
+
+ // 获取被禁言群成员列表(响应)
+ getShuttedUpMembersResp struct {
+ types.ActionBaseResp
+ ShuttedUpList []shuttedUp `json:"ShuttedUinList"`
+ }
+
+ // 被禁言信息
+ shuttedUp struct {
+ UserId string `json:"Member_Account"` // 用户ID
+ ShuttedUntil int64 `json:"ShuttedUntil"` // 禁言到的时间(使用 UTC 时间,即世界协调时间)
+ }
+
+ // 在群组中发送普通消息(请求)
+ sendMessageReq struct {
+ GroupId string `json:"GroupId"` // (必填)向哪个群组发送消息
+ Random uint32 `json:"Random"` // (必填)无符号32位整数
+ MsgPriority string `json:"MsgPriority,omitempty"` // (选填)消息的优先级
+ FromUserId string `json:"From_Account,omitempty"` // (选填)消息来源帐号
+ MsgBody []*types.MsgBody `json:"MsgBody"` // (必填)消息体
+ OnlineOnlyFlag int `json:"MsgOnlineOnlyFlag,omitempty"` // (选填)1表示消息仅发送在线成员,默认0表示发送所有成员,AVChatRoom(直播群)不支持该参数
+ SendMsgControl []string `json:"SendMsgControl,omitempty"` // (选填)消息发送权限,NoLastMsg 只对单条消息有效,表示不更新最近联系人会话;NoUnread 不计未读,只对单条消息有效。(如果该消息 MsgOnlineOnlyFlag 设置为1,则不允许使用该字段。)
+ ForbidCallbackControl []string `json:"ForbidCallbackControl,omitempty"` // (选填)消息回调禁止开关,只对单条消息有效
+ OfflinePushInfo *types.OfflinePushInfo `json:"OfflinePushInfo,omitempty"` // (选填)离线推送信息配置
+ CloudCustomData string `json:"CloudCustomData,omitempty"` // (选填)消息自定义数据(云端保存,会发送到对端,程序卸载重装后还能拉取到)
+ GroupAtInfo []atInfo `json:"GroupAtInfo,omitempty"` // (选填)@某个用户或者所有人
+ }
+
+ // 在群组中发送普通消息(响应)
+ sendMessageResp struct {
+ types.ActionBaseResp
+ MsgTime int `json:"MsgTime"`
+ MsgSeq int `json:"MsgSeq"`
+ }
+
+ // SendMessageRet 发送消息结果
+ SendMessageRet struct {
+ MsgSeq int // 消息唯一标识,用于撤回。长度不超过50个字符
+ MsgTime int // 消息时间戳,UNIX 时间戳
+ }
+
+ atInfo struct {
+ GroupAtAllFlag int `json:"GroupAtAllFlag"`
+ GroupAtUserId string `json:"GroupAt_Account,omitempty"`
+ }
+
+ // 在群组中发送系统通知(请求)
+ sendNotificationReq struct {
+ GroupId string `json:"GroupId"` // (必填)向哪个群组发送系统通知
+ Content string `json:"Content"` // (必填)系统通知的内容
+ UserIds []string `json:"ToMembers_Account,omitempty"` // (选填)接收者群成员列表,请填写接收者 UserID,不填或为空表示全员下发
+ }
+
+ // 转让群主(请求)
+ changeGroupOwnerReq struct {
+ GroupId string `json:"GroupId"`
+ OwnerUserId string `json:"NewOwner_Account"`
+ }
+
+ msgSeqItem struct {
+ MsgSeq int `json:"MsgSeq"` // 请求撤回的消息seq
+ }
+
+ // 撤销消息(请求)
+ revokeMessagesReq struct {
+ GroupId string `json:"GroupId"` // (必填)操作的群ID
+ MsgSeqList []msgSeqItem `json:"MsgSeqList"` // (必填)被撤回的消息 seq 列表
+ }
+
+ // 撤销消息(响应)
+ revokeMessagesResp struct {
+ types.ActionBaseResp
+ Results []revokeMessageResult `json:"Results"` // 撤销结果列表
+ }
+
+ // 撤销消息结果
+ revokeMessageResult struct {
+ MsgSeq int `json:"MsgSeq"` // 单个被撤回消息的 seq
+ RetCode int `json:"RetCode"` // 单个消息的被撤回结果:0表示成功;其它表示失败
+ }
+
+ // 导入群基础资料(请求)
+ importGroupReq struct {
+ OwnerUserId string `json:"Owner_Account,omitempty"` // (选填)群主 ID(需是 已导入 的账号)。填写后自动添加到群成员中;如果不填,群没有群主
+ GroupId string `json:"GroupId,omitempty"` // (选填)为了使得群组 ID 更加简单,便于记忆传播,腾讯云支持 App 在通过 REST API 创建群组时 自定义群组 ID
+ Type Type `json:"Type"` // (必填)群组形态,包括 Public(陌生人社交群),Private(即 Work,好友工作群),ChatRoom(即 Meeting,会议群),AVChatRoom(直播群)
+ Name string `json:"Name"` // (必填)群名称,最长30字节,使用 UTF-8 编码,1个汉字占3个字节
+ Introduction string `json:"Introduction,omitempty"` // (选填)群简介,最长240字节,使用 UTF-8 编码,1个汉字占3个字节
+ Notification string `json:"Notification,omitempty"` // (选填)群公告,最长300字节,使用 UTF-8 编码,1个汉字占3个字节
+ FaceUrl string `json:"FaceUrl,omitempty"` // (选填)群头像 URL,最长100字节
+ MaxMemberNum uint `json:"MaxMemberCount,omitempty"` // (选填)最大群成员数量,缺省时的默认值:付费套餐包上限,例如体验版是20,如果升级套餐包,需按照修改群基础资料修改这个字段
+ ApplyJoinOption string `json:"ApplyJoinOption,omitempty"` // (选填)申请加群处理方式。包含 FreeAccess(自由加入),NeedPermission(需要验证),DisableApply(禁止加群),不填默认为 NeedPermission(需要验证) 仅当创建支持申请加群的 群组 时,该字段有效
+ AppDefinedData []*customDataItem `json:"AppDefinedData,omitempty"` // (选填)群组维度的自定义字段,默认情况是没有的,可以通过 即时通信 IM 控制台 进行配置,详情请参阅 自定义字段
+ CreateTime int64 `json:"CreateTime"` // (选填)群组的创建时间
+ }
+
+ // 导入群基础资料(响应)
+ importGroupResp struct {
+ types.ActionBaseResp
+ GroupId string `json:"GroupId"` // 群ID
+ }
+
+ // 消息信息
+ messageItem struct {
+ FromUserId string `json:"From_Account"` // (必填)消息来源帐号
+ MsgBody []*types.MsgBody `json:"MsgBody"` // (必填)消息体
+ SendTime int64 `json:"SendTime"` // (必填)消息发送时间
+ Random uint32 `json:"Random,omitempty"` // (选填)无符号32位整数
+ }
+
+ // 导入群消息(请求)
+ importMessagesReq struct {
+ GroupId string `json:"GroupId"` // (必填)要导入消息的群ID
+ Messages []messageItem `json:"MsgList"` // (必填)导入的消息列表
+ }
+
+ // 导入群消息(响应)
+ importMessagesResp struct {
+ types.ActionBaseResp
+ Results []ImportMessagesResult `json:"ImportMsgResult"` // 导入群消息结果
+ }
+
+ // ImportMessagesResult 导入群消息结果
+ ImportMessagesResult struct {
+ MsgSeq int `json:"MsgSeq"` // 消息序列号,唯一标示一条消息
+ MsgTime int `json:"MsgTime"` // 消息的时间戳
+ Result int `json:"Result"` // 单条消息导入结果 0表示单条消息成功 10004表示单条消息发送时间无效 80001表示单条消息包含脏字,拒绝存储此消息 80002表示为消息内容过长,目前支持8000字节的消息,请调整消息长度
+ }
+
+ // 导入群成员(请求)
+ importMembersReq struct {
+ GroupId string `json:"GroupId"` // (必填)操作的群ID
+ Members []*memberItem `json:"MemberList"` // (必填)添加的群成员数组
+ }
+
+ // 导入群成员(响应)
+ importMembersResp struct {
+ types.ActionBaseResp
+ Results []ImportMemberResult `json:"MemberList"` // 添加的群成员结果
+ }
+
+ // ImportMemberResult 导入成员结果
+ ImportMemberResult struct {
+ UserId string `json:"Member_Account"` // 群成员帐号
+ Result int `json:"Result"` // 导入结果:0表示失败;1表示成功;2表示已经是群成员
+ }
+
+ // 设置成员未读消息计数(请求)
+ setMemberUnreadMsgNumReq struct {
+ GroupId string `json:"GroupId"` // (必填)操作的群 ID
+ UserId string `json:"Member_Account"` // (必填)要操作的群成员
+ UnreadMsgNum int `json:"UnreadMsgNum"` // (必填)成员未读消息数
+ }
+
+ // 撤回指定用户发送的消息(请求)
+ revokeMemberMessagesReq struct {
+ GroupId string `json:"GroupId"` // (必填)要撤回消息的群 ID
+ UserId string `json:"Sender_Account"` // (必填)被撤回消息的发送者 ID
+ }
+
+ // 拉取群历史消息(请求)
+ fetchMessagesReq struct {
+ GroupId string `json:"GroupId"` // (必填)要拉取历史消息的群组 ID
+ ReqMsgSeq int `json:"ReqMsgSeq"` // (选填)拉取消息的最大seq
+ ReqMsgNumber int `json:"ReqMsgNumber,omitempty"` // (必填)拉取的历史消息的条数,目前一次请求最多返回20条历史消息,所以这里最好小于等于20
+ }
+
+ // 拉取群历史消息(响应)
+ fetchMessagesResp struct {
+ types.ActionBaseResp
+ GroupId string `json:"GroupId"`
+ IsFinished int `json:"IsFinished"`
+ RspMsgList []rspMsgItem `json:"RspMsgList"`
+ }
+
+ FetchMessagesRet struct {
+ IsFinished int // 是否返回了请求区间的全部消息 当成功返回了请求区间的全部消息时,值为1; 当消息长度太长或者区间太大(超过20)导致无法返回全部消息时,值为0; 当消息长度太长或者区间太大(超过20)且所有消息都过期时,值为2
+ HasMore bool // 是否还有更多数据
+ NextSeq int // 下一个消息Seq
+ List []*Message // 列表
+ }
+
+ rspMsgItem struct {
+ FromUserId string `json:"From_Account"`
+ IsPlaceMsg int `json:"IsPlaceMsg"`
+ MsgBody []types.MsgBody `json:"MsgBody"`
+ MsgPriority int `json:"MsgPriority"`
+ MsgRandom uint32 `json:"MsgRandom"`
+ MsgSeq int `json:"MsgSeq"`
+ MsgTimeStamp int64 `json:"MsgTimeStamp"`
+ }
+
+ // 获取直播群在线人数(请求)
+ getOnlineMemberNumReq struct {
+ GroupId string `json:"GroupId"` // (必填)操作的群ID
+ }
+
+ // 获取直播群在线人数(响应)
+ getOnlineMemberNumResp struct {
+ types.ActionBaseResp
+ OnlineMemberNum int `json:"OnlineMemberNum"` // 该群组的在线人数
+ }
+)
diff --git a/im.go b/im.go
new file mode 100644
index 0000000..8ef56a8
--- /dev/null
+++ b/im.go
@@ -0,0 +1,212 @@
+/**
+ * @Author: Echo
+ * @Author: 1711788888@qq.com
+ * @Date: 2022/9/26 20:29
+ * @Desc: 腾讯云IM
+ */
+
+package im
+
+import (
+ "sync"
+ "time"
+
+ "git.echol.cn/loser/tencent-im/account"
+ "git.echol.cn/loser/tencent-im/callback"
+ "git.echol.cn/loser/tencent-im/group"
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/sign"
+ "git.echol.cn/loser/tencent-im/mute"
+ "git.echol.cn/loser/tencent-im/operation"
+ "git.echol.cn/loser/tencent-im/private"
+ "git.echol.cn/loser/tencent-im/profile"
+ "git.echol.cn/loser/tencent-im/push"
+ "git.echol.cn/loser/tencent-im/recentcontact"
+ "git.echol.cn/loser/tencent-im/sns"
+)
+
+type Error = core.Error
+
+type (
+ IM interface {
+ // GetUserSig 获取UserSig签名
+ GetUserSig(userId string, expiration ...int) UserSig
+ // SNS 获取关系链管理接口
+ SNS() sns.API
+ // Mute 获取全局禁言管理接口
+ Mute() mute.API
+ // Push 获取全员推送接口
+ Push() push.API
+ // Group 获取群组管理接口
+ Group() group.API
+ // Account 获取账号管理接口
+ Account() account.API
+ // Profile 获取资料管理接口
+ Profile() profile.API
+ // Private 获取私聊消息接口
+ Private() private.API
+ // Operation 获取运营管理接口
+ Operation() operation.API
+ // RecentContact 获取最近联系人接口
+ RecentContact() recentcontact.API
+ // Callback 获取回调接口
+ Callback() callback.Callback
+ }
+
+ Options struct {
+ AppId int // 应用SDKAppID,可在即时通信 IM 控制台 的应用卡片中获取。
+ AppSecret string // 密钥信息,可在即时通信 IM 控制台 的应用详情页面中获取,具体操作请参见 获取密钥
+ UserId string // 用户ID
+ Expiration int // UserSig过期时间
+ }
+
+ UserSig struct {
+ UserSig string // 用户签名
+ ExpireAt int64 // 签名过期时间
+ }
+
+ im struct {
+ opt *Options
+ client core.Client
+ sns struct {
+ once sync.Once
+ instance sns.API
+ }
+ mute struct {
+ once sync.Once
+ instance mute.API
+ }
+ push struct {
+ once sync.Once
+ instance push.API
+ }
+ group struct {
+ once sync.Once
+ instance group.API
+ }
+ account struct {
+ once sync.Once
+ instance account.API
+ }
+ profile struct {
+ once sync.Once
+ instance profile.API
+ }
+ private struct {
+ once sync.Once
+ instance private.API
+ }
+ operation struct {
+ once sync.Once
+ instance operation.API
+ }
+ recentcontact struct {
+ once sync.Once
+ instance recentcontact.API
+ }
+ callback struct {
+ once sync.Once
+ instance callback.Callback
+ }
+ }
+)
+
+func NewIM(opt *Options) IM {
+ return &im{opt: opt, client: core.NewClient(&core.Options{
+ AppId: opt.AppId,
+ AppSecret: opt.AppSecret,
+ UserId: opt.UserId,
+ Expiration: opt.Expiration,
+ })}
+}
+
+// GetUserSig 获取UserSig签名
+func (i *im) GetUserSig(userId string, expiration ...int) UserSig {
+ if len(expiration) == 0 {
+ expiration = append(expiration, i.opt.Expiration)
+ }
+
+ userSig, _ := sign.GenUserSig(i.opt.AppId, i.opt.AppSecret, userId, expiration[0])
+ expireAt := time.Now().Add(time.Duration(i.opt.Expiration) * time.Second).Unix()
+ return UserSig{UserSig: userSig, ExpireAt: expireAt}
+}
+
+// SNS 获取关系链管理接口ok
+func (i *im) SNS() sns.API {
+ i.sns.once.Do(func() {
+ i.sns.instance = sns.NewAPI(i.client)
+ })
+ return i.sns.instance
+}
+
+// Mute 获取全局禁言管理接口ok
+func (i *im) Mute() mute.API {
+ i.mute.once.Do(func() {
+ i.mute.instance = mute.NewAPI(i.client)
+ })
+ return i.mute.instance
+}
+
+// Push 获取全员推送接口
+func (i *im) Push() push.API {
+ i.push.once.Do(func() {
+ i.push.instance = push.NewAPI(i.client)
+ })
+ return i.push.instance
+}
+
+// Group 获取群组管理接口
+func (i *im) Group() group.API {
+ i.group.once.Do(func() {
+ i.group.instance = group.NewAPI(i.client)
+ })
+ return i.group.instance
+}
+
+// Account 获取账号管理接口ok
+func (i *im) Account() account.API {
+ i.account.once.Do(func() {
+ i.account.instance = account.NewAPI(i.client)
+ })
+ return i.account.instance
+}
+
+// Profile 获取资料管理接口ok
+func (i *im) Profile() profile.API {
+ i.profile.once.Do(func() {
+ i.profile.instance = profile.NewAPI(i.client)
+ })
+ return i.profile.instance
+}
+
+// Private 获取私聊消息接口ok
+func (i *im) Private() private.API {
+ i.private.once.Do(func() {
+ i.private.instance = private.NewAPI(i.client)
+ })
+ return i.private.instance
+}
+
+// Operation 获取运营管理接口ok
+func (i *im) Operation() operation.API {
+ i.operation.once.Do(func() {
+ i.operation.instance = operation.NewAPI(i.client)
+ })
+ return i.operation.instance
+}
+
+// RecentContact 获取最近联系人接口ok
+func (i *im) RecentContact() recentcontact.API {
+ i.recentcontact.once.Do(func() {
+ i.recentcontact.instance = recentcontact.NewAPI(i.client)
+ })
+ return i.recentcontact.instance
+}
+
+// Callback 获取回调接口
+func (i *im) Callback() callback.Callback {
+ i.callback.once.Do(func() {
+ i.callback.instance = callback.NewCallback(i.opt.AppId)
+ })
+ return i.callback.instance
+}
diff --git a/im_test.go b/im_test.go
new file mode 100644
index 0000000..01b435a
--- /dev/null
+++ b/im_test.go
@@ -0,0 +1,1511 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/27 1:40 下午
+ * @Desc: TODO
+ */
+
+package im_test
+
+import (
+ "fmt"
+ "math/rand"
+ "strconv"
+ "testing"
+ "time"
+
+ "git.echol.cn/loser/tencent-im"
+ "git.echol.cn/loser/tencent-im/account"
+ "git.echol.cn/loser/tencent-im/group"
+ "git.echol.cn/loser/tencent-im/operation"
+ "git.echol.cn/loser/tencent-im/private"
+ "git.echol.cn/loser/tencent-im/profile"
+ "git.echol.cn/loser/tencent-im/push"
+ "git.echol.cn/loser/tencent-im/recentcontact"
+ "git.echol.cn/loser/tencent-im/sns"
+)
+
+const (
+ assistant = "assistant"
+ accountPrefix = "test"
+ test1 = "test1"
+ test2 = "test2"
+ test3 = "test3"
+ test4 = "test4"
+ test5 = "test5"
+ test6 = "test6"
+ test7 = "test7"
+ test8 = "test8"
+ test9 = "test9"
+)
+
+func NewIM() im.IM {
+ return im.NewIM(&im.Options{
+ AppId: 1400564830,
+ AppSecret: "0d2a321b087fdb8fd5ed5ea14fe0489139086eb1b03541774fc9feeab8f2bfd3",
+ UserId: "administrator",
+ Expiration: 3600,
+ })
+}
+
+// 处理错误
+func handleError(t *testing.T, callName string, err error) {
+ if e, ok := err.(im.Error); ok {
+ t.Fatalf("call %s failed, code:%d, message:%s.", callName, e.Code(), e.Message())
+ } else {
+ t.Fatalf("call %s failed, err:%s.", callName, e.Error())
+ }
+}
+
+func testUserIds() []string {
+ return []string{
+ test1,
+ test2,
+ test3,
+ test4,
+ test5,
+ test6,
+ test7,
+ test8,
+ test9,
+ }
+}
+
+// GetUserSig 获取UserSig签名
+func TestIm_GetUserSig(t *testing.T) {
+ tim := NewIM()
+
+ for i := 0; i < 1000; i++ {
+ tim.GetUserSig(assistant)
+ }
+
+ t.Log("Success")
+}
+
+// 导入单个账号
+func TestIm_Account_ImportAccount(t *testing.T) {
+ if err := NewIM().Account().ImportAccount(&account.Account{
+ UserId: assistant,
+ Nickname: "小助手",
+ FaceUrl: "http://www.qq.com",
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log("Success")
+}
+
+// 导入多个帐号
+func TestIm_Account_ImportAccounts(t *testing.T) {
+ failedAccounts, err := NewIM().Account().ImportAccounts(
+ test1,
+ test2,
+ test3,
+ test4,
+ test5,
+ test6,
+ test7,
+ test8,
+ test9,
+ )
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log(failedAccounts)
+}
+
+// 删除单个账号
+func TestIm_Account_DeleteAccount(t *testing.T) {
+ err := NewIM().Account().DeleteAccount(test1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log("Success")
+}
+
+// 删除多个帐号
+func TestIm_Account_DeleteAccounts(t *testing.T) {
+ deleteResults, err := NewIM().Account().DeleteAccounts(
+ test1,
+ test2,
+ test3,
+ test4,
+ test5,
+ test6,
+ test7,
+ test8,
+ test9,
+ )
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log(deleteResults)
+}
+
+// 查询多个帐号
+func TestIm_Account_CheckAccounts(t *testing.T) {
+ checkResults, err := NewIM().Account().CheckAccounts(
+ test1,
+ test2,
+ test3,
+ test4,
+ test5,
+ test6,
+ test7,
+ test8,
+ test9,
+ )
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log(checkResults)
+}
+
+// 使帐号登录状态失效
+func TestIm_Account_KickAccount(t *testing.T) {
+ if err := NewIM().Account().KickAccount(test1); err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log("Success")
+}
+
+// 查询帐号在线状态
+func TestIm_Account_QueryAccountOnlineStatus(t *testing.T) {
+ ret, err := NewIM().Account().GetAccountOnlineState(test2, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log(ret)
+}
+
+// 查询多个帐号在线状态
+func TestIm_Account_QueryAccountsOnlineStatus(t *testing.T) {
+ resp, err := NewIM().Account().GetAccountsOnlineState([]string{
+ test1,
+ test2,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log(resp.Results)
+ t.Log(resp.Errors)
+}
+
+// 全员推送
+func TestIm_Push_PushMessage(t *testing.T) {
+ message := push.NewMessage()
+ message.SetSender(assistant)
+ message.SetLifeTime(5000)
+ message.SetContent(push.MsgTextContent{
+ Text: "Hello Tencent IM",
+ })
+ message.OfflinePush().SetTitle("你好腾讯IM")
+ message.OfflinePush().SetDesc("你好腾讯IM,我来了~~~")
+ message.OfflinePush().SetPushFlag(push.PushFlagYes)
+ message.OfflinePush().SetExt(map[string]interface{}{
+ "url": "http://www.tencent.com",
+ })
+ message.OfflinePush().SetAndroidExtAsHuaweiIntentParam(push.HuaweiIntentParamIntent)
+ message.OfflinePush().SetApnsBadgeMode(push.BadgeModeNormal)
+
+ taskId, err := NewIM().Push().PushMessage(message)
+ if err != nil {
+ handleError(t, "push.PushMessage", err)
+ }
+
+ t.Log(taskId)
+}
+
+// 设置应用属性名称
+func TestIm_Push_SetAttrNames(t *testing.T) {
+ if err := NewIM().Push().SetAttrNames(map[int]string{
+ 0: "age",
+ 1: "city",
+ }); err != nil {
+ handleError(t, "push.SetAttrNames", err)
+ }
+
+ t.Log("Success")
+}
+
+// 获取应用属性名称
+func TestIm_Push_GetAttrNames(t *testing.T) {
+ ret, err := NewIM().Push().GetAttrNames()
+ if err != nil {
+ handleError(t, "push.GetAttrNames", err)
+ }
+
+ t.Log(ret)
+}
+
+// 获取用户属性
+func TestIm_Push_GetUserAttrs(t *testing.T) {
+ ret, err := NewIM().Push().GetUserAttrs(test1)
+ if err != nil {
+ handleError(t, "push.GetUserAttrs", err)
+ }
+
+ t.Log(ret)
+}
+
+// 设置用户属性
+func TestIm_Push_SetUserAttrs(t *testing.T) {
+ err := NewIM().Push().SetUserAttrs(map[string]map[string]interface{}{
+ test1: {
+ "age": 20,
+ "city": "成都",
+ },
+ })
+ if err != nil {
+ handleError(t, "push.SetUserAttrs", err)
+ }
+
+ t.Log("Success")
+}
+
+// 删除用户属性
+func TestIm_Push_DeleteUserAttrs(t *testing.T) {
+ err := NewIM().Push().DeleteUserAttrs(map[string][]string{
+ test1: {"age", "city"},
+ })
+ if err != nil {
+ handleError(t, "push.DeleteUserAttrs", err)
+ }
+
+ t.Log("Success")
+}
+
+// 获取用户标签
+func TestIm_Push_GetUserTags(t *testing.T) {
+ ret, err := NewIM().Push().GetUserTags(test1)
+ if err != nil {
+ handleError(t, "push.GetUserTags", err)
+ }
+
+ t.Log(ret)
+}
+
+// 添加用户标签
+func TestIm_Push_AddUserTags(t *testing.T) {
+ err := NewIM().Push().AddUserTags(map[string][]string{
+ test1: {"chengdu"},
+ })
+ if err != nil {
+ handleError(t, "push.AddUserTags", err)
+ }
+
+ t.Log("Success")
+}
+
+// 删除用户标签
+func TestIm_Push_DeleteUserTags(t *testing.T) {
+ err := NewIM().Push().DeleteUserTags(map[string][]string{
+ test1: {"chengdu"},
+ })
+ if err != nil {
+ handleError(t, "push.DeleteUserTags", err)
+ }
+
+ t.Log("Success")
+}
+
+// 删除用户所有标签
+func TestIm_Push_DeleteUserAllTags(t *testing.T) {
+ err := NewIM().Push().DeleteUserAllTags(test1, test2)
+ if err != nil {
+ handleError(t, "push.DeleteUserAllTags", err)
+ }
+
+ t.Log("Success")
+}
+
+// 设置资料
+func TestIm_Profile_SetProfile(t *testing.T) {
+ p := profile.NewProfile()
+ p.SetUserId(assistant)
+ p.SetNickname("小助手")
+ p.SetAvatar("http://www.qq.com")
+ p.SetGender(profile.GenderTypeMale)
+ p.SetLocation(1, 23, 7465, 92)
+ p.SetLanguage(20)
+
+ if err := NewIM().Profile().SetProfile(p); err != nil {
+ handleError(t, "profile.SetProfile", err)
+ }
+
+ t.Log("Success")
+}
+
+// 获取资料
+func TestIm_Profile_GetProfile(t *testing.T) {
+ profiles, err := NewIM().Profile().GetProfiles([]string{
+ assistant,
+ }, []string{
+ profile.StandardAttrNickname,
+ profile.StandardAttrGender,
+ profile.StandardAttrBirthday,
+ profile.StandardAttrLocation,
+ profile.StandardAttrLanguage,
+ })
+ if err != nil {
+ handleError(t, "profile.GetProfiles", err)
+ }
+
+ for _, p := range profiles {
+ t.Log(p.GetUserId())
+ t.Log(p.GetNickname())
+ t.Log(p.GetGender())
+ t.Log(p.GetBirthday())
+ t.Log(p.GetLocation())
+ t.Log(p.GetLanguage())
+ }
+}
+
+// 拉取运营数据
+func TestIm_Operation_GetOperationData(t *testing.T) {
+ data, err := NewIM().Operation().GetOperationData()
+ if err != nil {
+ handleError(t, "operation.GetOperationData", err)
+ }
+
+ t.Log(data[0].AppId)
+ t.Log(data[0].AppName)
+ t.Log(data[0].ActiveUserNum)
+ t.Log("Success")
+}
+
+// 拉取运营数据
+func TestIm_Operation_GetHistoryData(t *testing.T) {
+ files, err := NewIM().Operation().GetHistoryData(operation.ChatTypeC2C, time.Date(2021, time.November, 4, 14, 0, 0, 0, time.Local))
+ if err != nil {
+ handleError(t, "operation.GetHistoryData", err)
+ }
+
+ t.Log(files)
+ t.Log("Success")
+}
+
+// 获取服务器IP地址
+func TestIm_Operation_GetIpList(t *testing.T) {
+ ips, err := NewIM().Operation().GetIPList()
+ if err != nil {
+ handleError(t, "operation.GetIPList", err)
+ }
+
+ t.Log(ips)
+ t.Log("Success")
+}
+
+// 设置全局禁言
+func TestIm_Mute_SetNoSpeaking(t *testing.T) {
+ var privateMuteTime uint = 400
+ var groupMuteTime uint = 200
+ if err := NewIM().Mute().SetNoSpeaking(assistant, &privateMuteTime, &groupMuteTime); err != nil {
+ handleError(t, "mute.SetNoSpeaking", err)
+ }
+
+ t.Log("Success")
+}
+
+// 查询全局禁言
+func TestIm_Mute_GetNoSpeaking(t *testing.T) {
+ ret, err := NewIM().Mute().GetNoSpeaking(assistant)
+ if err != nil {
+ handleError(t, "mute.GetNoSpeaking", err)
+ }
+
+ t.Log(ret.PrivateMuteTime)
+ t.Log(ret.GroupMuteTime)
+}
+
+// 添加好友
+func TestIm_SNS_AddFriends(t *testing.T) {
+ var (
+ userIds = testUserIds()
+ friends = make([]*sns.Friend, 0, len(userIds))
+ friend *sns.Friend
+ )
+
+ for _, userId := range userIds {
+ friend = sns.NewFriend(userId)
+ friend.SetAddSource("android")
+ friends = append(friends, friend)
+ }
+
+ failUserIds, err := NewIM().Account().ImportAccounts(userIds...)
+ if err != nil {
+ handleError(t, "account.ImportAccounts", err)
+ }
+
+ t.Log(failUserIds)
+
+ results, err := NewIM().SNS().AddFriends(assistant, true, false, friends...)
+ if err != nil {
+ handleError(t, "sns.AddFriends", err)
+ }
+
+ t.Log(results)
+}
+
+// 导入好友
+func TestIm_SNS_ImportFriends(t *testing.T) {
+ var (
+ userIds = testUserIds()
+ friends = make([]*sns.Friend, 0, len(userIds))
+ friend *sns.Friend
+ now = time.Now().Unix()
+ )
+
+ for _, userId := range userIds {
+ friend = sns.NewFriend(userId)
+ friend.SetAddSource("android")
+ friend.SetGroup("测试组")
+ friend.SetAddWording("测试一下")
+ friend.SetAddTime(now)
+ friend.SetRemark("测试好友")
+ friend.SetRemarkTime(now)
+ friends = append(friends, friend)
+ }
+
+ failUserIds, err := NewIM().Account().ImportAccounts(userIds...)
+ if err != nil {
+ handleError(t, "account.ImportAccounts", err)
+ }
+
+ t.Log(failUserIds)
+
+ results, err := NewIM().SNS().ImportFriends(assistant, friends...)
+ if err != nil {
+ handleError(t, "sns.ImportFriends", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 更新单个好友
+func TestIm_SNS_UpdateFriend(t *testing.T) {
+ friend := sns.NewFriend(test1)
+ friend.SetAddSource("android") // 忽略更新
+ friend.SetGroup("测试组")
+ friend.SetAddWording("更新单个好友") // 忽略更新
+ friend.SetAddTime(time.Now().Unix()) // 忽略更新
+ friend.SetRemark("更新单个好友")
+ friend.SetRemarkTime(time.Now().Unix()) // 忽略更新
+
+ err := NewIM().SNS().UpdateFriend(assistant, friend)
+ if err != nil {
+ handleError(t, "sns.UpdateFriend", err)
+ }
+
+ t.Log("Success")
+}
+
+// 更新好友
+func TestIm_SNS_UpdateFriends(t *testing.T) {
+ var (
+ total = 10
+ friends = make([]*sns.Friend, 0, total)
+ friend *sns.Friend
+ now = time.Now().Unix()
+ )
+
+ for i := 0; i < total; i++ {
+ friend = sns.NewFriend(accountPrefix + strconv.Itoa(i))
+ friend.SetAddSource("android")
+ friend.SetGroup("测试组")
+ friend.SetAddWording("测试一下")
+ friend.SetAddTime(now)
+ friend.SetRemark("测试好友")
+ friend.SetRemarkTime(now)
+ friends = append(friends, friend)
+ }
+
+ results, err := NewIM().SNS().UpdateFriends(assistant, friends...)
+ if err != nil {
+ handleError(t, "sns.UpdateFriends", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 删除好友
+func TestIm_SNS_DeleteFriends(t *testing.T) {
+ var userIds = testUserIds()
+
+ results, err := NewIM().SNS().DeleteFriends(assistant, false, userIds...)
+ if err != nil {
+ handleError(t, "sns.DeleteFriends", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 删除所有好友
+func TestIm_SNS_DeleteAllFriends(t *testing.T) {
+ err := NewIM().SNS().DeleteAllFriends(assistant)
+ if err != nil {
+ handleError(t, "sns.DeleteAllFriends", err)
+ }
+
+ t.Log("Success")
+}
+
+// 校验好友
+func TestIm_SNS_CheckFriends(t *testing.T) {
+ var userIds = testUserIds()
+
+ results, err := NewIM().SNS().CheckFriends(assistant, sns.CheckTypeSingle, userIds...)
+ if err != nil {
+ handleError(t, "sns.CheckFriends", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 拉取指定好友
+func TestIm_SNS_GetFriends(t *testing.T) {
+ var userIds = testUserIds()
+
+ friends, err := NewIM().SNS().GetFriends(assistant, []string{
+ sns.FriendAttrAddSource,
+ sns.FriendAttrRemark,
+ sns.FriendAttrRemarkTime, // 此Tag无效,GetFriends内部忽略了
+ sns.FriendAttrAddTime,
+ sns.FriendAttrAddWording,
+ sns.FriendAttrGroup,
+ sns.StandardAttrNickname,
+ sns.StandardAttrBirthday,
+ }, userIds...)
+ if err != nil {
+ handleError(t, "sns.GetFriends", err)
+ }
+
+ // 第一种获取方式
+ for _, friend := range friends {
+ if friend.IsValid() {
+ t.Log(friend.GetUserId())
+ t.Log(friend.GetAddSource())
+ t.Log(friend.GetRemark())
+ t.Log(friend.GetRemarkTime())
+ t.Log(friend.GetGroup())
+ t.Log(friend.GetNickname())
+ t.Log(friend.GetBirthday())
+ fmt.Println()
+ }
+ }
+
+ // 第二种获取方式
+ for _, friend := range friends {
+ if err = friend.GetError(); err != nil {
+ t.Log(fmt.Sprintf("获取账号%s失败:%s", friend.GetUserId(), err.Error()))
+ } else {
+ t.Log(friend.GetUserId())
+ t.Log(friend.GetAddSource())
+ t.Log(friend.GetRemark())
+ t.Log(friend.GetRemarkTime())
+ t.Log(friend.GetGroup())
+ t.Log(friend.GetNickname())
+ t.Log(friend.GetBirthday())
+ fmt.Println()
+ }
+ }
+}
+
+// 拉取好友
+func TestIm_SNS_FetchFriends(t *testing.T) {
+ var (
+ err error
+ ret *sns.FetchFriendsRet
+ s = NewIM().SNS()
+ startIndex = 0
+ standardSequence = 0
+ customSequence = 0
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = s.FetchFriends(assistant, startIndex, standardSequence, customSequence)
+ if err != nil {
+ handleError(t, "sns.FetchFriends", err)
+ }
+
+ startIndex = ret.StartIndex
+ standardSequence = ret.StandardSequence
+ customSequence = ret.CustomSequence
+
+ t.Log("下一个开始点:", ret.StartIndex)
+ t.Log("标准排序:", ret.StandardSequence)
+ t.Log("自定义排序:", ret.CustomSequence)
+ t.Log("好友总数:", ret.Total)
+ t.Log("是否还有数据:", ret.HasMore)
+ t.Log("好友列表:")
+ fmt.Println()
+ for _, friend := range ret.List {
+ if err = friend.GetError(); err != nil {
+ t.Log(fmt.Sprintf("获取账号%s失败:%s", friend.GetUserId(), err.Error()))
+ } else {
+ t.Log(friend.GetUserId())
+ t.Log(friend.GetAddSource())
+ t.Log(friend.GetRemark())
+ t.Log(friend.GetRemarkTime())
+ t.Log(friend.GetGroup())
+ t.Log(friend.GetNickname())
+ t.Log(friend.GetBirthday())
+ fmt.Println()
+ }
+ }
+ }
+}
+
+// 续拉取好友
+func TestIm_SNS_PullFriends(t *testing.T) {
+ err := NewIM().SNS().PullFriends(assistant, func(ret *sns.FetchFriendsRet) {
+ t.Log("下一个开始点:", ret.StartIndex)
+ t.Log("标准排序:", ret.StandardSequence)
+ t.Log("自定义排序:", ret.CustomSequence)
+ t.Log("好友总数:", ret.Total)
+ t.Log("是否还有数据:", ret.HasMore)
+ t.Log("好友列表:")
+ fmt.Println()
+ for _, friend := range ret.List {
+ if err := friend.GetError(); err != nil {
+ t.Log(fmt.Sprintf("获取账号%s失败:%s", friend.GetUserId(), err.Error()))
+ } else {
+ t.Log(friend.GetUserId())
+ t.Log(friend.GetAddSource())
+ t.Log(friend.GetRemark())
+ t.Log(friend.GetRemarkTime())
+ t.Log(friend.GetGroup())
+ t.Log(friend.GetNickname())
+ t.Log(friend.GetBirthday())
+ fmt.Println()
+ }
+ }
+ })
+ if err != nil {
+ handleError(t, "sns.PullFriends", err)
+ }
+ t.Log("Success")
+}
+
+// 添加黑名单
+func TestIm_SNS_AddBlacklist(t *testing.T) {
+ var userIds = testUserIds()
+
+ results, err := NewIM().SNS().AddBlacklist(assistant, userIds...)
+ if err != nil {
+ handleError(t, "sns.AddBlacklist", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 删除黑名单
+func TestIm_SNS_DeleteBlacklist(t *testing.T) {
+ var userIds = testUserIds()
+
+ results, err := NewIM().SNS().DeleteBlacklist(assistant, userIds...)
+ if err != nil {
+ handleError(t, "sns.DeleteBlacklist", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 拉取黑名单
+func TestIm_SNS_FetchBlacklist(t *testing.T) {
+ var (
+ err error
+ ret *sns.FetchBlacklistRet
+ s = NewIM().SNS()
+ startIndex = 0
+ maxLimited = 2
+ standardSequence = 0
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = s.FetchBlacklist(assistant, startIndex, maxLimited, standardSequence)
+ if err != nil {
+ handleError(t, "sns.FetchBlacklist", err)
+ }
+
+ startIndex = ret.StartIndex
+ standardSequence = ret.StandardSequence
+
+ t.Log("下一个开始点:", startIndex)
+ t.Log("标准排序:", standardSequence)
+ t.Log("黑名单列表:")
+ fmt.Println()
+ for _, blacklist := range ret.List {
+ t.Log(blacklist.UserId)
+ t.Log(blacklist.Time)
+ fmt.Println()
+ }
+ }
+
+ t.Log("Success")
+}
+
+// 续拉取黑名单
+func TestIm_SNS_PullBlacklist(t *testing.T) {
+ err := NewIM().SNS().PullBlacklist(assistant, 2, func(ret *sns.FetchBlacklistRet) {
+ t.Log("下一个开始点:", ret.StartIndex)
+ t.Log("标准排序:", ret.StandardSequence)
+ t.Log("黑名单列表:")
+ fmt.Println()
+ for _, blacklist := range ret.List {
+ t.Log(blacklist.UserId)
+ t.Log(blacklist.Time)
+ fmt.Println()
+ }
+ })
+ if err != nil {
+ handleError(t, "sns.PullBlacklist", err)
+ }
+
+ t.Log("Success")
+}
+
+// 校验黑名单
+func TestIm_SNS_CheckBlacklist(t *testing.T) {
+ var userIds = testUserIds()
+
+ results, err := NewIM().SNS().CheckBlacklist(assistant, sns.BlacklistCheckTypeSingle, userIds...)
+ if err != nil {
+ handleError(t, "sns.CheckBlacklist", err)
+ }
+
+ for _, result := range results {
+ if result.ResultCode == 0 {
+ t.Log(result.UserId)
+ t.Log(result.Relation)
+ switch result.Relation {
+ case sns.BlackCheckResultTypeNO:
+ t.Log("From_Account 的黑名单中没有 To_Account,但无法确定 To_Account 的黑名单中是否有 From_Account")
+ case sns.BlackCheckResultTypeAWithB:
+ t.Log("From_Account 的黑名单中有 To_Account,但无法确定 To_Account 的黑名单中是否有 From_Account")
+ }
+ } else {
+ t.Log(result.ResultCode)
+ t.Log(result.ResultInfo)
+ }
+ fmt.Println()
+ }
+
+ t.Log("Success")
+}
+
+// 添加分组
+func TestIm_SNS_AddGroups(t *testing.T) {
+ var userIds = testUserIds()
+
+ _, results, err := NewIM().SNS().AddGroups("assistant", []string{
+ "测试5",
+ "测试6",
+ }, userIds)
+ if err != nil {
+ handleError(t, "sns.AddGroups", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 删除分组
+func TestIm_SNS_DeleteGroups(t *testing.T) {
+ _, err := NewIM().SNS().DeleteGroups("assistant", "测试3", "测试4")
+ if err != nil {
+ handleError(t, "sns.DeleteGroups", err)
+ }
+
+ t.Log("Success")
+}
+
+// 拉取分组
+func TestIm_SNS_GetGroups(t *testing.T) {
+ var (
+ err error
+ lastSequence int
+ results []*sns.GroupResult
+ )
+
+ lastSequence, results, err = NewIM().SNS().GetGroups("assistant", lastSequence, true, "测试1", "测试2")
+ if err != nil {
+ handleError(t, "sns.GetGroups", err)
+ }
+
+ t.Log(results)
+ t.Log("Success")
+}
+
+// 发送单聊消息
+func TestIm_Private_SendMessage(t *testing.T) {
+ message := private.NewMessage()
+ message.SetSender(assistant)
+ message.SetReceivers(test1)
+ message.SetLifeTime(30000)
+ message.SetTimestamp(time.Now().Unix())
+ message.SetContent(private.MsgTextContent{
+ Text: "Hello world",
+ })
+ message.OfflinePush().SetTitle("你好腾讯IM")
+ message.OfflinePush().SetDesc("你好腾讯IM,我来了~~~")
+ message.OfflinePush().SetPushFlag(private.PushFlagYes)
+ message.OfflinePush().SetExt(map[string]interface{}{
+ "url": "http://www.tencent.com",
+ })
+ message.OfflinePush().SetAndroidExtAsHuaweiIntentParam(private.HuaweiIntentParamIntent)
+ message.OfflinePush().SetApnsBadgeMode(private.BadgeModeNormal)
+
+ ret, err := NewIM().Private().SendMessage(message)
+ if err != nil {
+ handleError(t, "private.SendMessage", err)
+ }
+
+ t.Log(ret.MsgKey)
+ t.Log(ret.MsgTime)
+ t.Log("Success")
+}
+
+// 批量发单聊消息
+func TestIm_Private_SendMessages(t *testing.T) {
+ message := private.NewMessage()
+ message.SetSender("assistant")
+ message.AddReceivers("test1", "test2")
+ message.SetContent(private.MsgTextContent{
+ Text: "Hello world",
+ })
+
+ ret, err := NewIM().Private().SendMessages(message)
+ if err != nil {
+ handleError(t, "private.SendMessages", err)
+ }
+
+ t.Log(ret.MsgKey)
+ t.Log(ret.Errors)
+}
+
+// 导入单聊消息
+func TestIm_Private_ImportMessage(t *testing.T) {
+ message := private.NewMessage()
+ message.SetSender("assistant")
+ message.SetReceivers("test1")
+ message.SetTimestamp(time.Now().Unix())
+ message.SetSyncOtherMachine(private.SyncOtherMachineYes)
+ message.SetContent(private.MsgTextContent{
+ Text: "Hello world",
+ })
+
+ err := NewIM().Private().ImportMessage(message)
+ if err != nil {
+ handleError(t, "private.ImportMessage", err)
+ }
+
+ t.Log("Success")
+}
+
+// 查询单聊消息
+func TestIm_Private_FetchMessages(t *testing.T) {
+ var (
+ err error
+ p = NewIM().Private()
+ ret *private.FetchMessagesRet
+ arg = &private.FetchMessagesArg{
+ FromUserId: test1,
+ ToUserId: assistant,
+ MaxLimited: 5,
+ MinTime: time.Now().Add(-20 * time.Hour).Unix(),
+ MaxTime: time.Now().Unix(),
+ }
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = p.FetchMessages(arg)
+ if err != nil {
+ handleError(t, "private.FetchMessages", err)
+ }
+
+ if ret.HasMore {
+ arg.LastMsgKey = ret.LastMsgKey
+ arg.MaxTime = ret.LastMsgTime
+ }
+
+ t.Log(ret.HasMore)
+ t.Log(ret.LastMsgKey)
+ t.Log(ret.LastMsgTime)
+ t.Log(ret.Count)
+ t.Log(ret.List)
+ fmt.Println()
+ }
+}
+
+// 分页拉取所有消息
+func TestIm_Private_PullMessages(t *testing.T) {
+ err := NewIM().Private().PullMessages(&private.PullMessagesArg{
+ FromUserId: "test1",
+ ToUserId: "assistant",
+ MaxLimited: 5,
+ MinTime: time.Now().Add(-30 * time.Hour).Unix(),
+ MaxTime: time.Now().Unix(),
+ }, func(ret *private.FetchMessagesRet) {
+ t.Log(ret.HasMore)
+ t.Log(ret.LastMsgKey)
+ t.Log(ret.LastMsgTime)
+ t.Log(ret.Count)
+ t.Log(ret.List)
+ fmt.Println()
+ })
+ if err != nil {
+ handleError(t, "private.PullMessages", err)
+ }
+
+ t.Log("Success")
+}
+
+// 撤销消息
+func TestIm_Private_RevokeMessage(t *testing.T) {
+ err := NewIM().Private().RevokeMessage(assistant, test1, "31906_833502_1572869830")
+ if err != nil {
+ handleError(t, "private.RevokeMessage", err)
+ }
+
+ t.Log("Success")
+}
+
+// 设置单聊消息已读
+func TestIm_Private_SetMessageRead(t *testing.T) {
+ err := NewIM().Private().SetMessageRead(assistant, test1)
+ if err != nil {
+ handleError(t, "private.SetMessageRead", err)
+ }
+
+ t.Log("Success")
+}
+
+// 获取未读消息数
+func TestIm_Private_GetUnreadMessageNum(t *testing.T) {
+ ret, err := NewIM().Private().GetUnreadMessageNum(assistant, test1, test2)
+ if err != nil {
+ handleError(t, "private.GetUnreadMessageNum", err)
+ }
+
+ t.Log(ret.Total)
+ t.Log(ret.Results)
+ t.Log(ret.Errors)
+}
+
+// 创建群组
+func TestIm_Group_CreateGroup(t *testing.T) {
+ g := group.NewGroup()
+ g.SetName("测试群2")
+ g.SetGroupType(group.TypePrivate)
+ g.SetMaxMemberNum(30)
+ g.SetAvatar("http://www.baidu.com")
+ g.SetGroupId("test_group2")
+ g.SetIntroduction("这是一个测试群")
+ g.SetNotification("这是一个测试群公告")
+
+ for i := 1; i < 10; i++ {
+ member := group.NewMember(accountPrefix + strconv.Itoa(i))
+ member.SetJoinTime(time.Now())
+ g.AddMembers(member)
+ }
+
+ groupId, err := NewIM().Group().CreateGroup(g)
+ if err != nil {
+ handleError(t, "group.CreateGroup", err)
+ }
+
+ t.Log(groupId)
+}
+
+// 解散单个群
+func TestIm_Group_DestroyGroup(t *testing.T) {
+ err := NewIM().Group().DestroyGroup("test_group2")
+ if err != nil {
+ handleError(t, "group.DestroyGroup", err)
+ }
+
+ t.Log("Success")
+}
+
+// 获取单个群详细资料
+func TestIm_Group_GetGroup(t *testing.T) {
+ g, err := NewIM().Group().GetGroup("test_group2")
+ if err != nil {
+ handleError(t, "group.GetGroup", err)
+ }
+
+ if g != nil {
+ t.Log(g.GetGroupId())
+ t.Log(g.GetName())
+ t.Log(g.GetGroupType())
+ t.Log(g.GetOwner())
+ t.Log(g.GetAvatar())
+ }
+}
+
+// 获取多个群详细资料
+func TestIm_Group_GetGroups(t *testing.T) {
+ groups, err := NewIM().Group().GetGroups([]string{
+ "test_group2",
+ })
+ if err != nil {
+ handleError(t, "group.GetGroups", err)
+ }
+
+ for _, g := range groups {
+ if err = g.GetError(); err != nil {
+ t.Error(err)
+ } else {
+ t.Log(g.GetGroupId())
+ t.Log(g.GetName())
+ t.Log(g.GetGroupType())
+ t.Log(g.GetOwner())
+ t.Log(g.GetAvatar())
+ }
+ }
+}
+
+// 添加群成员
+func TestIm_Group_AddGroupMembers(t *testing.T) {
+ results, err := NewIM().Group().AddMembers("test_group2", []string{
+ test1,
+ test2,
+ }, true)
+ if err != nil {
+ handleError(t, "group.AddMembers", err)
+ }
+
+ t.Log(results)
+}
+
+// 删除群成员
+func TestIm_Group_DeleteGroupMembers(t *testing.T) {
+ err := NewIM().Group().DeleteMembers("test_group2", []string{
+ test1,
+ test2,
+ test3,
+ }, "测试删除", true)
+ if err != nil {
+ handleError(t, "group.DeleteMembers", err)
+ }
+
+ t.Log("Success")
+}
+
+// 转让群组
+func TestIm_Group_ChangeGroupOwner(t *testing.T) {
+ err := NewIM().Group().ChangeGroupOwner("test_group2", test1)
+ if err != nil {
+ handleError(t, "group.ChangeGroupOwner", err)
+ }
+
+ t.Log("Success")
+}
+
+// 修改群基础资料
+func TestIm_Group_UpdateGroup(t *testing.T) {
+ g := group.NewGroup()
+ g.SetName("测试群1")
+ g.SetGroupType(group.TypePublic)
+ g.SetMaxMemberNum(30)
+ g.SetAvatar("http://www.baidu.com")
+ g.SetGroupId("test_group2")
+ g.SetIntroduction("这是一个测试群")
+ g.SetNotification("这是一个测试群公告")
+
+ err := NewIM().Group().UpdateGroup(g)
+ if err != nil {
+ handleError(t, "group.UpdateGroup", err)
+ }
+
+ t.Log("Success")
+}
+
+// 查询用户在群组中的身份
+func TestIm_Group_GetRolesInGroup(t *testing.T) {
+ ret, err := NewIM().Group().GetRolesInGroup("test_group2", []string{
+ test1,
+ test2,
+ test3,
+ })
+ if err != nil {
+ handleError(t, "group.GetRolesInGroup", err)
+ }
+
+ t.Log(ret)
+}
+
+// 拉取群成员详细资料
+func TestIm_Group_FetchGroupMembers(t *testing.T) {
+ ret, err := NewIM().Group().FetchMembers("test_group1", 3, 2)
+ if err != nil {
+ handleError(t, "group.FetchMembers", err)
+ }
+
+ t.Log(ret.HasMore)
+ t.Log(ret.Total)
+
+ for _, member := range ret.List {
+ t.Log(member.GetUserId())
+ }
+}
+
+// 拉取群成员详细资料
+func TestIm_Group_PullGroupMembers(t *testing.T) {
+ err := NewIM().Group().PullMembers(&group.PullMembersArg{
+ GroupId: "test_group2",
+ Limit: 3,
+ }, func(ret *group.FetchMembersRet) {
+ t.Log(ret.HasMore)
+ t.Log(ret.Total)
+
+ for _, member := range ret.List {
+ t.Log(member.GetUserId())
+ }
+ })
+ if err != nil {
+ handleError(t, "group.PullMembers", err)
+ }
+}
+
+// 拉取App中的所有群组
+func TestIm_Group_FetchGroupIds(t *testing.T) {
+ ret, err := NewIM().Group().FetchGroupIds(3, 7964653962)
+ if err != nil {
+ handleError(t, "group.FetchGroupIds", err)
+ }
+
+ t.Log(ret.Total)
+ t.Log(ret.Next)
+ t.Log(ret.HasMore)
+ t.Log(ret.List)
+}
+
+// 拉取App中的所有群组
+func TestIm_Group_FetchGroups(t *testing.T) {
+ ret, err := NewIM().Group().FetchGroups(50, 7964653962)
+ if err != nil {
+ handleError(t, "group.FetchGroups", err)
+ }
+
+ t.Log(ret.Total)
+ t.Log(ret.Next)
+ t.Log(ret.HasMore)
+
+ for _, g := range ret.List {
+ t.Log(g.GetGroupId())
+ t.Log(g.GetOwner())
+ t.Log(g.GetName())
+ }
+}
+
+// 续拉取App中的所有群组
+func TestIm_Group_PullGroups(t *testing.T) {
+ err := NewIM().Group().PullGroups(&group.PullGroupsArg{
+ Limit: 50,
+ }, func(ret *group.FetchGroupsRet) {
+ t.Log(ret.Total)
+ t.Log(ret.Next)
+ t.Log(ret.HasMore)
+
+ for _, g := range ret.List {
+ t.Log(g.GetGroupId())
+ t.Log(g.GetOwner())
+ t.Log(g.GetName())
+ }
+ })
+ if err != nil {
+ handleError(t, "group.PullGroups", err)
+ }
+}
+
+// 修改群成员资料
+func TestIm_Group_UpdateGroupMember(t *testing.T) {
+ member := group.NewMember(test1)
+ member.SetRole("Admin")
+ member.SetNameCard("这是一个测试名片信息")
+ member.SetMsgFlag(group.MsgFlagAcceptAndNotify)
+
+ err := NewIM().Group().UpdateMember("test_group2", member)
+ if err != nil {
+ handleError(t, "group.UpdateMember", err)
+ }
+
+ t.Log("Success")
+}
+
+// 拉取用户所加入的群组
+func TestIm_Group_FetchMemberGroups(t *testing.T) {
+ ret, err := NewIM().Group().FetchMemberGroups(&group.FetchMemberGroupsArg{
+ UserId: test1,
+ Limit: 3,
+ Offset: 0,
+ IsWithLiveRoomGroups: true,
+ IsWithNoActiveGroups: true,
+ })
+ if err != nil {
+ handleError(t, "group.FetchMemberGroups", err)
+ }
+
+ t.Log(ret.Total)
+ t.Log(ret.HasMore)
+ t.Log(ret.List)
+}
+
+// 续拉取用户所加入的群组
+func TestIm_Group_PullMemberGroups(t *testing.T) {
+ err := NewIM().Group().PullMemberGroups(&group.PullMemberGroupsArg{
+ UserId: test1,
+ Limit: 3,
+ IsWithLiveRoomGroups: true,
+ IsWithNoActiveGroups: true,
+ }, func(ret *group.FetchMemberGroupsRet) {
+ t.Log(ret.Total)
+ t.Log(ret.HasMore)
+ t.Log(ret.List)
+ })
+ if err != nil {
+ handleError(t, "group.PullMemberGroups", err)
+ }
+}
+
+// 批量禁言
+func TestIm_Group_ForbidSendMessage(t *testing.T) {
+ err := NewIM().Group().ForbidSendMessage("test_group1", []string{
+ test1,
+ }, 1000)
+ if err != nil {
+ handleError(t, "group.ForbidSendMessage", err)
+ }
+
+ t.Log("Success")
+}
+
+// 取消禁言
+func TestIm_Group_AllowSendMessage(t *testing.T) {
+ err := NewIM().Group().AllowSendMessage("test_group2", []string{
+ test1,
+ test2,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log("Success")
+}
+
+// 获取被禁言群成员列表
+func TestIm_Group_GetShuttedUpMembers(t *testing.T) {
+ shuttedUps, err := NewIM().Group().GetShuttedUpMembers("test_group2")
+ if err != nil {
+ handleError(t, "group.GetShuttedUpMembers", err)
+ }
+
+ t.Log(shuttedUps)
+}
+
+// 撤回单条群消息
+func TestIm_Group_RevokeMessage(t *testing.T) {
+ err := NewIM().Group().RevokeMessage("test_group2", 100)
+ if err != nil {
+ handleError(t, "group.RevokeMessage", err)
+ }
+
+ t.Log("Success")
+}
+
+// 撤回多条群消息
+func TestIm_Group_RevokeMessages(t *testing.T) {
+ results, err := NewIM().Group().RevokeMessages("test_group2", 100)
+ if err != nil {
+ handleError(t, "group.RevokeMessages", err)
+ }
+
+ t.Log(results)
+}
+
+// 设置成员未读消息计数
+func TestIm_Group_SetMemberUnreadMsgNum(t *testing.T) {
+ err := NewIM().Group().SetMemberUnreadMsgNum("test_group2", test1, 100)
+ if err != nil {
+ handleError(t, "group.SetMemberUnreadMsgNum", err)
+ }
+
+ t.Log("Success")
+}
+
+// 撤回指定用户发送的消息
+func TestIm_Group_RevokeMemberMessages(t *testing.T) {
+ err := NewIM().Group().RevokeMemberMessages("test_group2", test1)
+ if err != nil {
+ handleError(t, "group.RevokeMemberMessages", err)
+ }
+
+ t.Log("Success")
+}
+
+// 在群组中发送普通消息
+func TestIm_Group_SendMessage(t *testing.T) {
+ message := group.NewMessage()
+ message.SetSender(test1)
+ message.SetNoLastMsg()
+ message.SetPriority("first")
+ // message.SetOnlineOnlyFlag(group.OnlineOnlyFlagYes)
+ message.SetContent(private.MsgTextContent{
+ Text: "Hello world",
+ })
+ message.OfflinePush().SetTitle("你好腾讯IM")
+ message.OfflinePush().SetDesc("你好腾讯IM,我来了~~~")
+ message.OfflinePush().SetPushFlag(private.PushFlagYes)
+ message.OfflinePush().SetExt(map[string]interface{}{
+ "url": "http://www.tencent.com",
+ })
+ message.OfflinePush().SetAndroidExtAsHuaweiIntentParam(private.HuaweiIntentParamIntent)
+ message.OfflinePush().SetApnsBadgeMode(private.BadgeModeNormal)
+ message.AtAllMembers()
+ message.AtMembers(test2)
+ message.ClearAtMembers()
+
+ ret, err := NewIM().Group().SendMessage("test_group2", message)
+ if err != nil {
+ handleError(t, "group.SendMessage", err)
+ }
+
+ t.Log(ret.MsgSeq)
+ t.Log(ret.MsgTime)
+}
+
+// 在群组中发送普通消息
+func TestIm_Group_SendNotification(t *testing.T) {
+ err := NewIM().Group().SendNotification("test_group2", "Hello welcome to the test group", test1)
+ if err != nil {
+ handleError(t, "group.SendNotification", err)
+ }
+
+ t.Log("Success")
+}
+
+// 导入群基础资料
+func TestIm_Group_ImportGroup(t *testing.T) {
+ g := group.NewGroup()
+ g.SetName("测试群1")
+ g.SetGroupType(group.TypePublic)
+ g.SetMaxMemberNum(30)
+ g.SetAvatar("http://www.baidu.com")
+ g.SetIntroduction("这是一个测试群")
+ g.SetNotification("这是一个测试群公告")
+
+ groupId, err := NewIM().Group().ImportGroup(g)
+ if err != nil {
+ handleError(t, "group.ImportGroup", err)
+ }
+
+ t.Log(groupId)
+}
+
+// 导入群消息
+func TestIm_Group_ImportMessages(t *testing.T) {
+ message := group.NewMessage()
+ message.SetSender(test1)
+ message.SetSendTime(time.Now().Unix())
+ message.SetRandom(rand.Uint32())
+ message.SetContent(private.MsgTextContent{
+ Text: "Hello world",
+ })
+
+ results, err := NewIM().Group().ImportMessages("test_group2", message)
+ if err != nil {
+ handleError(t, "group.ImportMessages", err)
+ }
+
+ t.Log(results)
+}
+
+// 导入多个群成员
+func TestIm_Group_ImportMembers(t *testing.T) {
+ members := make([]*group.Member, 0)
+ for i := 1; i < 10; i++ {
+ member := group.NewMember()
+ member.SetUserId("test" + strconv.Itoa(i))
+ member.SetRole("Admin")
+ member.SetUnreadMsgNum(10)
+ member.SetJoinTime(time.Now())
+ members = append(members, member)
+ }
+
+ results, err := NewIM().Group().ImportMembers("test_group2", members...)
+ if err != nil {
+ handleError(t, "group.ImportMembers", err)
+ }
+
+ t.Log(results)
+}
+
+// 拉取群历史消息
+func TestIm_Group_FetchMessages(t *testing.T) {
+ ret, err := NewIM().Group().FetchMessages("test_group2", 5)
+ if err != nil {
+ handleError(t, "group.FetchMessages", err)
+ }
+
+ t.Log(ret)
+}
+
+// 续拉取群历史消息
+func TestIm_Group_PullMessages(t *testing.T) {
+ err := NewIM().Group().PullMessages("test_group2", 5, func(ret *group.FetchMessagesRet) {
+ t.Log(ret)
+ })
+ if err != nil {
+ handleError(t, "group.PullMessages", err)
+ }
+}
+
+// 拉取会话列表
+func TestIm_RecentContact_FetchSessions(t *testing.T) {
+ ret, err := NewIM().RecentContact().FetchSessions(&recentcontact.FetchSessionsArg{
+ UserId: assistant,
+ })
+ if err != nil {
+ handleError(t, "recentcontact.FetchSessions", err)
+ }
+
+ t.Log(ret)
+ t.Log("Success")
+}
+
+// 拉取会话列表
+func TestIm_RecentContact_PullSessions(t *testing.T) {
+ err := NewIM().RecentContact().PullSessions(&recentcontact.PullSessionsArg{
+ UserId: assistant,
+ }, func(ret *recentcontact.FetchSessionsRet) {
+ t.Log(ret)
+ })
+ if err != nil {
+ handleError(t, "recentcontact.PullSessions", err)
+ }
+ t.Log("Success")
+}
+
+// 删除单个会话
+func TestIm_RecentContact_DeleteSession(t *testing.T) {
+ err := NewIM().RecentContact().DeleteSession(assistant, test1, recentcontact.SessionTypeC2C, true)
+ if err != nil {
+ handleError(t, "recentcontact.DeleteSession", err)
+ }
+ t.Log("Success")
+}
diff --git a/internal/conv/conv.go b/internal/conv/conv.go
new file mode 100644
index 0000000..785c076
--- /dev/null
+++ b/internal/conv/conv.go
@@ -0,0 +1,108 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/31 11:16 下午
+ * @Desc: TODO
+ */
+
+package conv
+
+import (
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "strconv"
+ "time"
+)
+
+type stringInterface interface {
+ String() string
+}
+
+type errorInterface interface {
+ Error() string
+}
+
+func String(any interface{}) string {
+ switch v := any.(type) {
+ case nil:
+ return ""
+ case string:
+ return v
+ case int:
+ return strconv.Itoa(v)
+ case int8:
+ return strconv.Itoa(int(v))
+ case int16:
+ return strconv.Itoa(int(v))
+ case int32:
+ return strconv.Itoa(int(v))
+ case int64:
+ return strconv.FormatInt(v, 10)
+ case uint:
+ return strconv.FormatUint(uint64(v), 10)
+ case uint8:
+ return strconv.FormatUint(uint64(v), 10)
+ case uint16:
+ return strconv.FormatUint(uint64(v), 10)
+ case uint64:
+ return strconv.FormatUint(v, 10)
+ case float32:
+ return strconv.FormatFloat(float64(v), 'f', -1, 32)
+ case float64:
+ return strconv.FormatFloat(v, 'f', -1, 64)
+ case bool:
+ return strconv.FormatBool(v)
+ case []byte:
+ return string(v)
+ case time.Time:
+ return v.String()
+ case *time.Time:
+ if v == nil {
+ return ""
+ }
+ return v.String()
+ default:
+ if v == nil {
+ return ""
+ }
+
+ if i, ok := v.(stringInterface); ok {
+ return i.String()
+ }
+
+ if i, ok := v.(errorInterface); ok {
+ return i.Error()
+ }
+
+ var (
+ rv = reflect.ValueOf(v)
+ kind = rv.Kind()
+ )
+
+ switch kind {
+ case reflect.Chan,
+ reflect.Map,
+ reflect.Slice,
+ reflect.Func,
+ reflect.Ptr,
+ reflect.Interface,
+ reflect.UnsafePointer:
+ if rv.IsNil() {
+ return ""
+ }
+ case reflect.String:
+ return rv.String()
+ }
+
+ if kind == reflect.Ptr {
+ return String(rv.Elem().Interface())
+ }
+
+ if b, e := json.Marshal(v); e != nil {
+ return fmt.Sprint(v)
+ } else {
+ return string(b)
+ }
+ }
+}
diff --git a/internal/core/client.go b/internal/core/client.go
new file mode 100644
index 0000000..21c618c
--- /dev/null
+++ b/internal/core/client.go
@@ -0,0 +1,146 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/27 11:31 上午
+ * @Desc: TODO
+ */
+
+package core
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+
+ "git.echol.cn/loser/http"
+
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/sign"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ defaultBaseUrl = "https://console.tim.qq.com"
+ defaultVersion = "v4"
+ defaultContentType = "json"
+ defaultExpiration = 3600
+)
+
+var invalidResponse = NewError(enum.InvalidResponseCode, "invalid response")
+
+type Client interface {
+ // Get GET请求
+ Get(serviceName string, command string, data interface{}, resp interface{}) error
+ // Post POST请求
+ Post(serviceName string, command string, data interface{}, resp interface{}) error
+ // Put PUT请求
+ Put(serviceName string, command string, data interface{}, resp interface{}) error
+ // Patch PATCH请求
+ Patch(serviceName string, command string, data interface{}, resp interface{}) error
+ // Delete DELETE请求
+ Delete(serviceName string, command string, data interface{}, resp interface{}) error
+}
+
+type client struct {
+ client *http.Client
+ opt *Options
+ userSig string
+ userSigExpireAt int64
+}
+
+type Options struct {
+ AppId int // 应用SDKAppID,可在即时通信 IM 控制台 的应用卡片中获取。
+ AppSecret string // 密钥信息,可在即时通信 IM 控制台 的应用详情页面中获取,具体操作请参见 获取密钥
+ UserId string // 用户ID
+ Expiration int // UserSig过期时间
+}
+
+func NewClient(opt *Options) Client {
+ rand.Seed(time.Now().UnixNano())
+ c := new(client)
+ c.opt = opt
+ c.client = http.NewClient()
+ c.client.SetContentType(http.ContentTypeJson)
+ c.client.SetBaseUrl(defaultBaseUrl)
+
+ return c
+}
+
+// Get GET请求
+func (c *client) Get(serviceName string, command string, data interface{}, resp interface{}) error {
+ return c.request(http.MethodGet, serviceName, command, data, resp)
+}
+
+// Post POST请求
+func (c *client) Post(serviceName string, command string, data interface{}, resp interface{}) error {
+ return c.request(http.MethodPost, serviceName, command, data, resp)
+}
+
+// Put PUT请求
+func (c *client) Put(serviceName string, command string, data interface{}, resp interface{}) error {
+ return c.request(http.MethodPut, serviceName, command, data, resp)
+}
+
+// Patch PATCH请求
+func (c *client) Patch(serviceName string, command string, data interface{}, resp interface{}) error {
+ return c.request(http.MethodPatch, serviceName, command, data, resp)
+}
+
+// Delete DELETE请求
+func (c *client) Delete(serviceName string, command string, data interface{}, resp interface{}) error {
+ return c.request(http.MethodDelete, serviceName, command, data, resp)
+}
+
+// request Request请求
+func (c *client) request(method, serviceName, command string, data, resp interface{}) error {
+ res, err := c.client.Request(method, c.buildUrl(serviceName, command), data)
+ if err != nil {
+ return err
+ }
+
+ if err = res.Scan(resp); err != nil {
+ return err
+ }
+
+ if r, ok := resp.(types.ActionBaseRespInterface); ok {
+ if r.GetActionStatus() == enum.FailActionStatus {
+ return NewError(r.GetErrorCode(), r.GetErrorInfo())
+ }
+
+ if r.GetErrorCode() != enum.SuccessCode {
+ return NewError(r.GetErrorCode(), r.GetErrorInfo())
+ }
+ } else if r, ok := resp.(types.BaseRespInterface); ok {
+ if r.GetErrorCode() != enum.SuccessCode {
+ return NewError(r.GetErrorCode(), r.GetErrorInfo())
+ }
+ } else {
+ return invalidResponse
+ }
+
+ return nil
+}
+
+// buildUrl 构建一个请求URL
+func (c *client) buildUrl(serviceName string, command string) string {
+ format := "/%s/%s/%s?sdkappid=%d&identifier=%s&usersig=%s&random=%d&contenttype=%s"
+ random := rand.Int31()
+ userSig := c.getUserSig()
+ return fmt.Sprintf(format, defaultVersion, serviceName, command, c.opt.AppId, c.opt.UserId, userSig, random, defaultContentType)
+}
+
+// getUserSig 获取签名
+func (c *client) getUserSig() string {
+ now, expiration := time.Now(), c.opt.Expiration
+
+ if expiration <= 0 {
+ expiration = defaultExpiration
+ }
+
+ if c.userSig == "" || c.userSigExpireAt <= now.Unix() {
+ c.userSig, _ = sign.GenUserSig(c.opt.AppId, c.opt.AppSecret, c.opt.UserId, expiration)
+ c.userSigExpireAt = now.Add(time.Duration(expiration) * time.Second).Unix()
+ }
+
+ return c.userSig
+}
diff --git a/internal/core/error.go b/internal/core/error.go
new file mode 100644
index 0000000..eb1dc4d
--- /dev/null
+++ b/internal/core/error.go
@@ -0,0 +1,38 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/27 1:12 下午
+ * @Desc: TODO
+ */
+
+package core
+
+type Error interface {
+ error
+ Code() int
+ Message() string
+}
+
+type respError struct {
+ code int
+ message string
+}
+
+func NewError(code int, message string) Error {
+ return &respError{
+ code: code,
+ message: message,
+ }
+}
+
+func (e *respError) Error() string {
+ return e.message
+}
+
+func (e *respError) Code() int {
+ return e.code
+}
+
+func (e *respError) Message() string {
+ return e.message
+}
diff --git a/internal/entity/mesage.go b/internal/entity/mesage.go
new file mode 100644
index 0000000..6490e7c
--- /dev/null
+++ b/internal/entity/mesage.go
@@ -0,0 +1,170 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/3 18:19
+ * @Desc: 基础消息实体
+ */
+
+package entity
+
+import (
+ "errors"
+ "math/rand"
+
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+var (
+ errInvalidMsgContent = errors.New("invalid message content")
+ errInvalidMsgLifeTime = errors.New("invalid message life time")
+ errNotSetMsgContent = errors.New("message content is not set")
+)
+
+type Message struct {
+ sender string // 发送方UserId
+ lifeTime int // 消息离线保存时长(单位:秒),最长为7天(604800秒)
+ random uint32 // 消息随机数,由随机函数产生
+ body []*types.MsgBody // 消息体
+ offlinePush *offlinePush // 推送实体
+}
+
+// SetSender 设置发送方UserId
+func (m *Message) SetSender(userId string) {
+ m.sender = userId
+}
+
+// GetSender 获取发送者
+func (m *Message) GetSender() string {
+ return m.sender
+}
+
+// SetLifeTime 设置消息离线保存时长
+func (m *Message) SetLifeTime(lifeTime int) {
+ m.lifeTime = lifeTime
+}
+
+// GetLifeTime 获取消息离线保存时长
+func (m *Message) GetLifeTime() int {
+ return m.lifeTime
+}
+
+// SetRandom 设置消息随机数
+func (m *Message) SetRandom(random uint32) {
+ m.random = random
+}
+
+// GetRandom 获取消息随机数
+func (m *Message) GetRandom() uint32 {
+ if m.random == 0 {
+ m.random = rand.Uint32()
+ }
+
+ return m.random
+}
+
+// AddContent 添加消息内容(添加会累加之前的消息内容)
+func (m *Message) AddContent(msgContent ...interface{}) {
+ if m.body == nil {
+ m.body = make([]*types.MsgBody, 0)
+ }
+
+ if len(msgContent) > 0 {
+ var msgType string
+ for _, content := range msgContent {
+ switch content.(type) {
+ case types.MsgTextContent, *types.MsgTextContent:
+ msgType = enum.MsgText
+ case types.MsgLocationContent, *types.MsgLocationContent:
+ msgType = enum.MsgLocation
+ case types.MsgFaceContent, *types.MsgFaceContent:
+ msgType = enum.MsgFace
+ case types.MsgCustomContent, *types.MsgCustomContent:
+ msgType = enum.MsgCustom
+ case types.MsgSoundContent, *types.MsgSoundContent:
+ msgType = enum.MsgSound
+ case types.MsgImageContent, *types.MsgImageContent:
+ msgType = enum.MsgImage
+ case types.MsgFileContent, *types.MsgFileContent:
+ msgType = enum.MsgFile
+ case types.MsgVideoContent, *types.MsgVideoContent:
+ msgType = enum.MsgVideo
+ default:
+ msgType = ""
+ }
+
+ m.body = append(m.body, &types.MsgBody{
+ MsgType: msgType,
+ MsgContent: content,
+ })
+ }
+ }
+}
+
+// SetContent 设置消息内容(设置会冲掉之前的消息内容)
+func (m *Message) SetContent(msgContent ...interface{}) {
+ if m.body != nil {
+ m.body = m.body[0:0]
+ }
+ m.AddContent(msgContent...)
+}
+
+// GetBody 获取消息体
+func (m *Message) GetBody() []*types.MsgBody {
+ return m.body
+}
+
+// OfflinePush 新建离线推送对象
+func (m *Message) OfflinePush() *offlinePush {
+ if m.offlinePush == nil {
+ m.offlinePush = newOfflinePush()
+ }
+
+ return m.offlinePush
+}
+
+// GetOfflinePushInfo 获取离线推送消息
+func (m *Message) GetOfflinePushInfo() *types.OfflinePushInfo {
+ if m.offlinePush == nil {
+ return nil
+ }
+
+ return &types.OfflinePushInfo{
+ PushFlag: m.offlinePush.pushFlag,
+ Title: m.offlinePush.title,
+ Desc: m.offlinePush.desc,
+ Ext: m.offlinePush.ext,
+ AndroidInfo: m.offlinePush.androidInfo,
+ ApnsInfo: m.offlinePush.apnsInfo,
+ }
+}
+
+// CheckLifeTimeArgError 检测参数错误
+func (m *Message) CheckLifeTimeArgError() error {
+ if m.body != nil && len(m.body) > 0 {
+ for _, item := range m.body {
+ if item.MsgType == "" {
+ return errInvalidMsgContent
+ }
+ }
+ } else {
+ return errNotSetMsgContent
+ }
+
+ return nil
+}
+
+// CheckBodyArgError 检测参数错误
+func (m *Message) CheckBodyArgError() error {
+ if m.body != nil && len(m.body) > 0 {
+ for _, item := range m.body {
+ if item.MsgType == "" {
+ return errInvalidMsgContent
+ }
+ }
+ } else {
+ return errNotSetMsgContent
+ }
+
+ return nil
+}
diff --git a/internal/entity/offline_push.go b/internal/entity/offline_push.go
new file mode 100644
index 0000000..8658051
--- /dev/null
+++ b/internal/entity/offline_push.go
@@ -0,0 +1,150 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/3 18:47
+ * @Desc: 离线推送
+ */
+
+package entity
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/conv"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+type offlinePush struct {
+ pushFlag int // 推送标识。0表示推送,1表示不离线推送。
+ title string // 离线推送标题。该字段为 iOS 和 Android 共用。
+ desc string // 离线推送内容。
+ ext string // 离线推送透传内容。
+ androidInfo *types.AndroidInfo // Android离线推送消息
+ apnsInfo *types.ApnsInfo // IOS离线推送消息
+}
+
+func newOfflinePush() *offlinePush {
+ return &offlinePush{}
+}
+
+// SetPushFlag 设置推送消息
+func (o *offlinePush) SetPushFlag(pushFlag types.PushFlag) {
+ o.pushFlag = int(pushFlag)
+}
+
+// SetTitle 设置离线推送标题
+func (o *offlinePush) SetTitle(title string) {
+ o.title = title
+}
+
+// SetDesc 设置离线推送内容
+func (o *offlinePush) SetDesc(desc string) {
+ o.desc = desc
+}
+
+// SetExt 设置离线推送透传内容
+func (o *offlinePush) SetExt(ext interface{}) {
+ o.ext = conv.String(ext)
+}
+
+// SetAndroidSound 设置Android离线推送声音文件路径
+func (o *offlinePush) SetAndroidSound(sound string) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.Sound = sound
+}
+
+// SetAndroidHuaWeiChannelId 设置华为手机 EMUI 10.0 及以上的通知渠道字段
+func (o *offlinePush) SetAndroidHuaWeiChannelId(channelId string) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.HuaWeiChannelID = channelId
+}
+
+// SetAndroidXiaoMiChannelId 设置小米手机 MIUI 10 及以上的通知类别(Channel)适配字段
+func (o *offlinePush) SetAndroidXiaoMiChannelId(channelId string) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.XiaoMiChannelID = channelId
+}
+
+// SetAndroidOppoChannelId 设置OPPO手机 Android 8.0 及以上的 NotificationChannel 通知适配字段
+func (o *offlinePush) SetAndroidOppoChannelId(channelId string) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.OPPOChannelID = channelId
+}
+
+// SetAndroidGoogleChannelId 设置Google 手机 Android 8.0 及以上的通知渠道字段
+func (o *offlinePush) SetAndroidGoogleChannelId(channelId string) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.GoogleChannelID = channelId
+}
+
+// SetAndroidVivoClassification 设置VIVO 手机推送消息分类,“0”代表运营消息,“1”代表系统消息,不填默认为1
+func (o *offlinePush) SetAndroidVivoClassification(classification types.VivoClassification) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.VIVOClassification = int(classification)
+}
+
+// SetAndroidHuaWeiImportance 设置华为推送通知消息分类
+func (o *offlinePush) SetAndroidHuaWeiImportance(importance types.HuaWeiImportance) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.HuaWeiImportance = string(importance)
+}
+
+// SetAndroidExtAsHuaweiIntentParam 设置在控制台配置华为推送为“打开应用内指定页面”的前提下,传“1”表示将透传内容 Ext 作为 Intent 的参数,“0”表示将透传内容 Ext 作为 Action 参数。不填默认为0。
+func (o *offlinePush) SetAndroidExtAsHuaweiIntentParam(param types.HuaweiIntentParam) {
+ if o.androidInfo == nil {
+ o.androidInfo = &types.AndroidInfo{}
+ }
+ o.androidInfo.ExtAsHuaweiIntentParam = int(param)
+}
+
+// SetApnsBadgeMode 设置IOS徽章计数模式
+func (o *offlinePush) SetApnsBadgeMode(badgeMode types.BadgeMode) {
+ if o.apnsInfo == nil {
+ o.apnsInfo = &types.ApnsInfo{}
+ }
+ o.apnsInfo.BadgeMode = int(badgeMode)
+}
+
+// SetApnsTitle 设置APNs推送的标题
+func (o *offlinePush) SetApnsTitle(title string) {
+ if o.apnsInfo == nil {
+ o.apnsInfo = &types.ApnsInfo{}
+ }
+ o.apnsInfo.Title = title
+}
+
+// SetApnsSubTitle 设置APNs推送的子标题
+func (o *offlinePush) SetApnsSubTitle(subTitle string) {
+ if o.apnsInfo == nil {
+ o.apnsInfo = &types.ApnsInfo{}
+ }
+ o.apnsInfo.SubTitle = subTitle
+}
+
+// SetApnsImage 设置APNs携带的图片地址
+func (o *offlinePush) SetApnsImage(image string) {
+ if o.apnsInfo == nil {
+ o.apnsInfo = &types.ApnsInfo{}
+ }
+ o.apnsInfo.Image = image
+}
+
+// SetApnsMutableContent 设置iOS10的推送扩展开关
+func (o *offlinePush) SetApnsMutableContent(mutable types.MutableContent) {
+ if o.apnsInfo == nil {
+ o.apnsInfo = &types.ApnsInfo{}
+ }
+ o.apnsInfo.MutableContent = int(mutable)
+}
diff --git a/internal/entity/user.go b/internal/entity/user.go
new file mode 100644
index 0000000..c868a76
--- /dev/null
+++ b/internal/entity/user.go
@@ -0,0 +1,330 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/30 4:23 下午
+ * @Desc: 用户
+ */
+
+package entity
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+type User struct {
+ userId string
+ attrs map[string]interface{}
+ err error
+}
+
+// SetUserId 设置用户账号
+func (u *User) SetUserId(userId string) {
+ u.userId = userId
+}
+
+// GetUserId 获取用户账号
+func (u *User) GetUserId() string {
+ return u.userId
+}
+
+// SetNickname 设置昵称
+func (u *User) SetNickname(nickname string) {
+ u.SetAttr(enum.StandardAttrNickname, nickname)
+}
+
+// GetNickname 获取昵称
+func (u *User) GetNickname() (nickname string, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrNickname); exist {
+ nickname = v.(string)
+ }
+
+ return
+}
+
+// SetGender 设置性别
+func (u *User) SetGender(gender types.GenderType) {
+ u.SetAttr(enum.StandardAttrGender, gender)
+}
+
+// GetGender 获取性别
+func (u *User) GetGender() (gender types.GenderType, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrGender); exist {
+ gender = types.GenderType(v.(string))
+ }
+
+ return
+}
+
+// SetBirthday 设置生日
+func (u *User) SetBirthday(birthday time.Time) {
+ b, _ := strconv.Atoi(birthday.Format("20060102"))
+ u.SetAttr(enum.StandardAttrBirthday, b)
+}
+
+// GetBirthday 获取昵称
+func (u *User) GetBirthday() (birthday time.Time, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrBirthday); exist {
+ if val := v.(string); val != "" {
+ birthday, _ = time.Parse("20060102", val)
+ }
+ }
+
+ return
+}
+
+// SetLocation 设置所在地
+func (u *User) SetLocation(country uint32, province uint32, city uint32, region uint32) {
+ var (
+ str string
+ location = []uint32{country, province, city, region}
+ builder strings.Builder
+ )
+
+ builder.Grow(16)
+
+ for _, v := range location {
+ str = strconv.Itoa(int(v))
+
+ if len(str) > 4 {
+ u.SetError(enum.InvalidParamsCode, "invalid location params")
+ break
+ }
+
+ builder.WriteString(strings.Repeat("0", 4-len(str)))
+ builder.WriteString(str)
+ }
+
+ u.SetAttr(enum.StandardAttrLocation, builder.String())
+}
+
+// GetLocation 获取所在地
+func (u *User) GetLocation() (country uint32, province uint32, city uint32, region uint32, exist bool) {
+ var v interface{}
+
+ if v, exist = u.attrs[enum.StandardAttrLocation]; exist {
+ str := v.(string)
+
+ if len(str) != 16 {
+ exist = false
+ return
+ }
+
+ if c, err := strconv.Atoi(str[0:4]); err != nil || c < 0 {
+ exist = false
+ return
+ } else {
+ country = uint32(c)
+ }
+
+ if c, err := strconv.Atoi(str[4:8]); err != nil || c < 0 {
+ exist = false
+ return
+ } else {
+ province = uint32(c)
+ }
+
+ if c, err := strconv.Atoi(str[8:12]); err != nil || c < 0 {
+ exist = false
+ return
+ } else {
+ city = uint32(c)
+ }
+
+ if c, err := strconv.Atoi(str[12:16]); err != nil || c < 0 {
+ exist = false
+ return
+ } else {
+ region = uint32(c)
+ }
+ }
+
+ return
+}
+
+// SetSignature 设置个性签名
+func (u *User) SetSignature(signature string) {
+ u.SetAttr(enum.StandardAttrSignature, signature)
+}
+
+// GetSignature 获取个性签名
+func (u *User) GetSignature() (signature string, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrSignature); exist {
+ signature = v.(string)
+ }
+
+ return
+}
+
+// SetAllowType 设置加好友验证方式
+func (u *User) SetAllowType(allowType types.AllowType) {
+ u.SetAttr(enum.StandardAttrAllowType, allowType)
+}
+
+// GetAllowType 获取加好友验证方式
+func (u *User) GetAllowType() (allowType types.AllowType, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrAllowType); exist {
+ allowType = types.AllowType(v.(string))
+ }
+
+ return
+}
+
+// SetLanguage 设置语言
+func (u *User) SetLanguage(language uint) {
+ u.SetAttr(enum.StandardAttrLanguage, language)
+}
+
+// GetLanguage 获取语言
+func (u *User) GetLanguage() (language uint, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrLanguage); exist {
+ language = uint(v.(float64))
+ }
+
+ return
+}
+
+// SetAvatar 设置头像URL
+func (u *User) SetAvatar(avatar string) {
+ u.SetAttr(enum.StandardAttrAvatar, avatar)
+}
+
+// GetAvatar 获取头像URL
+func (u *User) GetAvatar() (avatar string, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrAvatar); exist {
+ avatar = v.(string)
+ }
+
+ return
+}
+
+// SetMsgSettings 设置消息设置
+func (u *User) SetMsgSettings(settings uint) {
+ u.SetAttr(enum.StandardAttrMsgSettings, settings)
+}
+
+// GetMsgSettings 获取消息设置
+func (u *User) GetMsgSettings() (settings uint, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrMsgSettings); exist {
+ settings = uint(v.(float64))
+ }
+
+ return
+}
+
+// SetAdminForbidType 设置管理员禁止加好友标识
+func (u *User) SetAdminForbidType(forbidType types.AdminForbidType) {
+ u.SetAttr(enum.StandardAttrAdminForbidType, forbidType)
+}
+
+// GetAdminForbidType 获取管理员禁止加好友标识
+func (u *User) GetAdminForbidType() (forbidType types.AdminForbidType, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrAdminForbidType); exist {
+ forbidType = types.AdminForbidType(v.(string))
+ }
+
+ return
+}
+
+// SetLevel 设置等级
+func (u *User) SetLevel(level uint) {
+ u.SetAttr(enum.StandardAttrLevel, level)
+}
+
+// GetLevel 获取等级
+func (u *User) GetLevel() (level uint, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrLevel); exist {
+ level = uint(v.(float64))
+ }
+
+ return
+}
+
+// SetRole 设置角色
+func (u *User) SetRole(role uint) {
+ u.SetAttr(enum.StandardAttrRole, role)
+}
+
+// GetRole 获取角色
+func (u *User) GetRole() (role uint, exist bool) {
+ var v interface{}
+
+ if v, exist = u.GetAttr(enum.StandardAttrRole); exist {
+ role = uint(v.(float64))
+ }
+
+ return
+}
+
+// SetCustomAttr 设置自定义属性
+func (u *User) SetCustomAttr(name string, value interface{}) {
+ u.SetAttr(fmt.Sprintf("%s_%s", enum.CustomAttrPrefix, name), value)
+}
+
+// GetCustomAttr 获取自定义属性
+func (u *User) GetCustomAttr(name string) (val interface{}, exist bool) {
+ val, exist = u.GetAttr(fmt.Sprintf("%s_%s", enum.CustomAttrPrefix, name))
+ return
+}
+
+// IsValid 检测用户是否有效
+func (u *User) IsValid() bool {
+ return u.err == nil
+}
+
+// SetError 设置异常错误
+func (u *User) SetError(code int, message string) {
+ if code != enum.SuccessCode {
+ u.err = core.NewError(code, message)
+ }
+}
+
+// GetError 获取异常错误
+func (u *User) GetError() error {
+ return u.err
+}
+
+// SetAttr 设置属性
+func (u *User) SetAttr(name string, value interface{}) {
+ if u.attrs == nil {
+ u.attrs = make(map[string]interface{})
+ }
+ u.attrs[name] = value
+}
+
+// GetAttr 获取属性
+func (u *User) GetAttr(name string) (value interface{}, exist bool) {
+ value, exist = u.attrs[name]
+ return
+}
+
+// GetAttrs 获取所有属性
+func (u *User) GetAttrs() map[string]interface{} {
+ return u.attrs
+}
diff --git a/internal/enum/code.go b/internal/enum/code.go
new file mode 100644
index 0000000..6ee9e94
--- /dev/null
+++ b/internal/enum/code.go
@@ -0,0 +1,16 @@
+/**
+ * @Author: wanglin
+ * @Author: wanglin@vspn.com
+ * @Date: 2021/11/3 10:45
+ * @Desc: TODO
+ */
+
+package enum
+
+const (
+ SuccessActionStatus = "OK" // 成功状态
+ FailActionStatus = "FAIL" // 失败状态
+ SuccessCode = 0 // 成功
+ InvalidParamsCode = -1 // 无效参数(自定义)
+ InvalidResponseCode = -2 // 无效响应(自定义)
+)
diff --git a/internal/enum/enum.go b/internal/enum/enum.go
new file mode 100644
index 0000000..488d7e9
--- /dev/null
+++ b/internal/enum/enum.go
@@ -0,0 +1,95 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/28 1:14 上午
+ * @Desc: TODO
+ */
+
+package enum
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ // 消息类型
+ MsgText = "TIMTextElem" // 消息元素
+ MsgLocation = "TIMLocationElem" // 地理位置消息元素
+ MsgFace = "TIMFaceElem" // 表情消息元素
+ MsgCustom = "TIMCustomElem" // 自定义消息元素
+ MsgSound = "TIMSoundElem" // 语音消息元素
+ MsgImage = "TIMImageElem" // 图像消息元素
+ MsgFile = "TIMFileElem" // 文件消息元素
+ MsgVideo = "TIMVideoFileElem" // 视频消息元素
+
+ // 图片格式
+ ImageFormatJPG = 1 // JPG格式
+ ImageFormatGIF = 2 // GIF格式
+ ImageFormatPNG = 3 // PNG格式
+ ImageFormatBMP = 4 // BMP格式
+ ImageFormatOTHER = 255 // 其他格式
+
+ // 图片类型
+ ImageTypeOriginal = 1 // 原图
+ ImageTypePic = 2 // 大图
+ ImageTypeThumb = 3 // 缩略图
+
+ // 标准资料字段
+ StandardAttrNickname = "Tag_Profile_IM_Nick" // 昵称
+ StandardAttrGender = "Tag_Profile_IM_Gender" // 性别
+ StandardAttrBirthday = "Tag_Profile_IM_BirthDay" // 生日
+ StandardAttrLocation = "Tag_Profile_IM_Location" // 所在地
+ StandardAttrSignature = "Tag_Profile_IM_SelfSignature" // 个性签名
+ StandardAttrAllowType = "Tag_Profile_IM_AllowType" // 加好友验证方式
+ StandardAttrLanguage = "Tag_Profile_IM_Language" // 语言
+ StandardAttrAvatar = "Tag_Profile_IM_Image" // 头像URL
+ StandardAttrMsgSettings = "Tag_Profile_IM_MsgSettings" // 消息设置
+ StandardAttrAdminForbidType = "Tag_Profile_IM_AdminForbidType" // 管理员禁止加好友标识
+ StandardAttrLevel = "Tag_Profile_IM_Level" // 等级
+ StandardAttrRole = "Tag_Profile_IM_Role" // 角色
+
+ // 自定义属性前缀
+ CustomAttrPrefix = "Tag_Profile_Custom" // 自定义属性前缀
+
+ // 性别类型
+ GenderTypeUnknown types.GenderType = "Gender_Type_Unknown" // 没设置性别
+ GenderTypeFemale types.GenderType = "Gender_Type_Female" // 女性
+ GenderTypeMale types.GenderType = "Gender_Type_Male" // 男性
+
+ // 加好友验证方式
+ AllowTypeNeedConfirm types.AllowType = "AllowType_Type_NeedConfirm" // 需要经过自己确认对方才能添加自己为好友
+ AllowTypeAllowAny types.AllowType = "AllowType_Type_AllowAny" // 允许任何人添加自己为好友
+ AllowTypeDenyAny types.AllowType = "AllowType_Type_DenyAny" // 不允许任何人添加自己为好友
+
+ // 管理员禁止加好友标识类型
+ AdminForbidTypeNone types.AdminForbidType = "AdminForbid_Type_None" // 默认值,允许加好友
+ AdminForbidTypeSendOut types.AdminForbidType = "AdminForbid_Type_SendOut" // 禁止该用户发起加好友请求
+
+ // 同步至其他设备
+ SyncOtherMachineYes types.SyncOtherMachine = 1 // 把消息同步到From_Account在线终端和漫游上
+ SyncOtherMachineNo types.SyncOtherMachine = 2 // 消息不同步至From_Account
+
+ // 推送标识
+ PushFlagYes types.PushFlag = 0 // 正常推送
+ PushFlagNo types.PushFlag = 1 // 不离线推送
+
+ // 华为推送通知消息分类
+ HuaWeiImportanceLow types.HuaWeiImportance = "LOW" // LOW类消息
+ HuaWeiImportanceNormal types.HuaWeiImportance = "NORMAL" // NORMAL类消息
+
+ // 华为推送为“打开应用内指定页面”的前提下透传参数行为
+ HuaweiIntentParamAction types.HuaweiIntentParam = 0 // 将透传内容Ext作为Action参数
+ HuaweiIntentParamIntent types.HuaweiIntentParam = 1 // 将透传内容Ext作为Intent参数
+
+ // VIVO手机推送消息分类
+ VivoClassificationOperation types.VivoClassification = 0 // 运营类消息
+ VivoClassificationSystem types.VivoClassification = 1 // 系统类消息
+
+ // IOS徽章计数模式
+ BadgeModeNormal types.BadgeMode = 0 // 本条消息需要计数
+ BadgeModeIgnore types.BadgeMode = 1 // 本条消息不需要计数
+
+ // iOS10的推送扩展开关
+ MutableContentNormal types.MutableContent = 0 // 关闭iOS10的推送扩展
+ MutableContentEnable types.MutableContent = 1 // 开启iOS10的推送扩展
+)
diff --git a/internal/random/random.go b/internal/random/random.go
new file mode 100644
index 0000000..59acd97
--- /dev/null
+++ b/internal/random/random.go
@@ -0,0 +1,63 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/12 10:35
+ * @Desc: 随机数类库
+ */
+
+package random
+
+import (
+ "math/rand"
+ "time"
+)
+
+const (
+ AlphaStr = iota // 字母字
+ AlphaLowerStr // 小写字母
+ AlphaUpperStr // 大写字母
+ NumericStr // 数字
+ NoZeroNumericStr // 无0数字
+)
+
+// GenStr 生成指定长度的字符串
+func GenStr(mode, length int) string {
+ var (
+ pos int
+ lastStr string
+ seedStr string
+ )
+
+ rand.Seed(time.Now().UnixNano())
+ switch mode {
+ case AlphaStr:
+ seedStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ case AlphaLowerStr:
+ seedStr = "abcdefghijklmnopqrstuvwxyz"
+ case AlphaUpperStr:
+ seedStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ case NumericStr:
+ seedStr = "0123456789"
+ case NoZeroNumericStr:
+ seedStr = "123456789"
+ }
+
+ seedLen := len(seedStr)
+ for i := 0; i < length; i++ {
+ pos = rand.Intn(seedLen)
+ lastStr += seedStr[pos : pos+1]
+ }
+
+ return lastStr
+}
+
+// GenNumeric 生成指定范围的数字
+func GenNumeric(min int, max int) int {
+ rand.Seed(time.Now().UnixNano())
+
+ if min < max {
+ return rand.Intn(max-min) + min
+ } else {
+ return rand.Intn(min-max) + max
+ }
+}
diff --git a/internal/sign/base64.go b/internal/sign/base64.go
new file mode 100644
index 0000000..1960a3b
--- /dev/null
+++ b/internal/sign/base64.go
@@ -0,0 +1,30 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 19:15
+ * @Desc: BASE64
+ */
+
+package sign
+
+import (
+ "encoding/base64"
+ "strings"
+)
+
+// base64Encode base64 encode a string.
+func base64Encode(data []byte) string {
+ str := base64.StdEncoding.EncodeToString(data)
+ str = strings.Replace(str, "+", "*", -1)
+ str = strings.Replace(str, "/", "-", -1)
+ str = strings.Replace(str, "=", "_", -1)
+ return str
+}
+
+// base64Decode base64 decode a string.
+func base64Decode(str string) ([]byte, error) {
+ str = strings.Replace(str, "_", "=", -1)
+ str = strings.Replace(str, "-", "/", -1)
+ str = strings.Replace(str, "*", "+", -1)
+ return base64.StdEncoding.DecodeString(str)
+}
diff --git a/internal/sign/sign.go b/internal/sign/sign.go
new file mode 100644
index 0000000..3b8fd24
--- /dev/null
+++ b/internal/sign/sign.go
@@ -0,0 +1,178 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 19:11
+ * @Desc: Transmission signature.
+ */
+
+package sign
+
+import (
+ "bytes"
+ "compress/zlib"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/json"
+ "strconv"
+ "time"
+)
+
+// GenUserSig gen a user sign.
+func GenUserSig(sdkAppId int, key string, userid string, expire int) (string, error) {
+ return genUserSig(sdkAppId, key, userid, expire, nil)
+}
+
+// GenPrivateMapKey gen a private map.
+func GenPrivateMapKey(sdkAppId int, key string, userid string, expire int, roomId uint32, privilegeMap uint32) (string, error) {
+ var userBuf []byte = genUserBuf(userid, sdkAppId, roomId, expire, privilegeMap, 0, "")
+ return genUserSig(sdkAppId, key, userid, expire, userBuf)
+}
+
+// GenPrivateMapKeyWithRoomId gen a private map with room id.
+func GenPrivateMapKeyWithRoomId(sdkAppId int, key string, userid string, expire int, roomId string, privilegeMap uint32) (string, error) {
+ var userBuf []byte = genUserBuf(userid, sdkAppId, 0, expire, privilegeMap, 0, roomId)
+ return genUserSig(sdkAppId, key, userid, expire, userBuf)
+}
+
+// genUserBuf gen a user buffer.
+func genUserBuf(account string, dwSdkappid int, dwAuthID uint32,
+ dwExpTime int, dwPrivilegeMap uint32, dwAccountType uint32, roomStr string) []byte {
+
+ offset := 0
+ length := 1 + 2 + len(account) + 20 + len(roomStr)
+ if len(roomStr) > 0 {
+ length = length + 2
+ }
+
+ userBuf := make([]byte, length)
+
+ // ver
+ if len(roomStr) > 0 {
+ userBuf[offset] = 1
+ } else {
+ userBuf[offset] = 0
+ }
+
+ offset++
+ userBuf[offset] = (byte)((len(account) & 0xFF00) >> 8)
+ offset++
+ userBuf[offset] = (byte)(len(account) & 0x00FF)
+ offset++
+
+ for ; offset < len(account)+3; offset++ {
+ userBuf[offset] = account[offset-3]
+ }
+
+ // dwSdkAppid
+ userBuf[offset] = (byte)((int64(dwSdkappid) & 0xFF000000) >> 24)
+ offset++
+ userBuf[offset] = (byte)((dwSdkappid & 0x00FF0000) >> 16)
+ offset++
+ userBuf[offset] = (byte)((dwSdkappid & 0x0000FF00) >> 8)
+ offset++
+ userBuf[offset] = (byte)(dwSdkappid & 0x000000FF)
+ offset++
+
+ // dwAuthId
+ userBuf[offset] = (byte)((dwAuthID & 0xFF000000) >> 24)
+ offset++
+ userBuf[offset] = (byte)((dwAuthID & 0x00FF0000) >> 16)
+ offset++
+ userBuf[offset] = (byte)((dwAuthID & 0x0000FF00) >> 8)
+ offset++
+ userBuf[offset] = (byte)(dwAuthID & 0x000000FF)
+ offset++
+
+ // dwExpTime now+300;
+ currTime := time.Now().Unix()
+ var expire = currTime + int64(dwExpTime)
+ userBuf[offset] = (byte)((expire & 0xFF000000) >> 24)
+ offset++
+ userBuf[offset] = (byte)((expire & 0x00FF0000) >> 16)
+ offset++
+ userBuf[offset] = (byte)((expire & 0x0000FF00) >> 8)
+ offset++
+ userBuf[offset] = (byte)(expire & 0x000000FF)
+ offset++
+
+ // dwPrivilegeMap
+ userBuf[offset] = (byte)((dwPrivilegeMap & 0xFF000000) >> 24)
+ offset++
+ userBuf[offset] = (byte)((dwPrivilegeMap & 0x00FF0000) >> 16)
+ offset++
+ userBuf[offset] = (byte)((dwPrivilegeMap & 0x0000FF00) >> 8)
+ offset++
+ userBuf[offset] = (byte)(dwPrivilegeMap & 0x000000FF)
+ offset++
+
+ // dwAccountType
+ userBuf[offset] = (byte)((dwAccountType & 0xFF000000) >> 24)
+ offset++
+ userBuf[offset] = (byte)((dwAccountType & 0x00FF0000) >> 16)
+ offset++
+ userBuf[offset] = (byte)((dwAccountType & 0x0000FF00) >> 8)
+ offset++
+ userBuf[offset] = (byte)(dwAccountType & 0x000000FF)
+ offset++
+
+ if len(roomStr) > 0 {
+ userBuf[offset] = (byte)((len(roomStr) & 0xFF00) >> 8)
+ offset++
+ userBuf[offset] = (byte)(len(roomStr) & 0x00FF)
+ offset++
+
+ for ; offset < length; offset++ {
+ userBuf[offset] = roomStr[offset-(length-len(roomStr))]
+ }
+ }
+
+ return userBuf
+}
+
+// hmacSha256 encrypt with HMAC SHA256.
+func hmacSha256(sdkAppId int, key string, identifier string, currTime int64, expire int, base64UserBuf *string) string {
+ var contentToBeSigned string
+ contentToBeSigned = "TLS.identifier:" + identifier + "\n"
+ contentToBeSigned += "TLS.sdkappid:" + strconv.Itoa(sdkAppId) + "\n"
+ contentToBeSigned += "TLS.time:" + strconv.FormatInt(currTime, 10) + "\n"
+ contentToBeSigned += "TLS.expire:" + strconv.Itoa(expire) + "\n"
+ if nil != base64UserBuf {
+ contentToBeSigned += "TLS.userbuf:" + *base64UserBuf + "\n"
+ }
+
+ h := hmac.New(sha256.New, []byte(key))
+ h.Write([]byte(contentToBeSigned))
+ return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
+
+// genUserSig gen a sign
+func genUserSig(sdkAppId int, key string, identifier string, expire int, userBuf []byte) (string, error) {
+ currTime := time.Now().Unix()
+ var sigDoc map[string]interface{}
+ sigDoc = make(map[string]interface{})
+ sigDoc["TLS.ver"] = "2.0"
+ sigDoc["TLS.identifier"] = identifier
+ sigDoc["TLS.sdkappid"] = sdkAppId
+ sigDoc["TLS.expire"] = expire
+ sigDoc["TLS.time"] = currTime
+ var base64UserBuf string
+ if nil != userBuf {
+ base64UserBuf = base64.StdEncoding.EncodeToString(userBuf)
+ sigDoc["TLS.userbuf"] = base64UserBuf
+ sigDoc["TLS.sig"] = hmacSha256(sdkAppId, key, identifier, currTime, expire, &base64UserBuf)
+ } else {
+ sigDoc["TLS.sig"] = hmacSha256(sdkAppId, key, identifier, currTime, expire, nil)
+ }
+
+ data, err := json.Marshal(sigDoc)
+ if err != nil {
+ return "", err
+ }
+
+ var b bytes.Buffer
+ w := zlib.NewWriter(&b)
+ _, _ = w.Write(data)
+ _ = w.Close()
+ return base64Encode(b.Bytes()), nil
+}
diff --git a/internal/types/interface.go b/internal/types/interface.go
new file mode 100644
index 0000000..f6a5240
--- /dev/null
+++ b/internal/types/interface.go
@@ -0,0 +1,30 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/27 12:54 下午
+ * @Desc: TODO
+ */
+
+package types
+
+type BaseRespInterface interface {
+ GetErrorCode() int
+ GetErrorInfo() string
+}
+
+func (r *BaseResp) GetErrorCode() int {
+ return r.ErrorCode
+}
+
+func (r *BaseResp) GetErrorInfo() string {
+ return r.ErrorInfo
+}
+
+type ActionBaseRespInterface interface {
+ BaseRespInterface
+ GetActionStatus() string
+}
+
+func (r *ActionBaseResp) GetActionStatus() string {
+ return r.ActionStatus
+}
diff --git a/internal/types/types.go b/internal/types/types.go
new file mode 100644
index 0000000..cfc6c64
--- /dev/null
+++ b/internal/types/types.go
@@ -0,0 +1,171 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/28 19:24
+ * @Desc: TODO
+ */
+
+package types
+
+type (
+ BaseResp struct {
+ ErrorCode int `json:"ErrorCode"`
+ ErrorInfo string `json:"ErrorInfo"`
+ ErrorDisplay string `json:"ErrorDisplay,omitempty"`
+ }
+
+ ActionBaseResp struct {
+ BaseResp
+ ActionStatus string `json:"ActionStatus"`
+ }
+
+ // AndroidInfo Android离线推送消息
+ AndroidInfo struct {
+ Sound string `json:"Sound,omitempty"` // (选填)Android 离线推送声音文件路径。
+ HuaWeiChannelID string `json:"HuaWeiChannelID,omitempty"` // (选填)华为手机 EMUI 10.0 及以上的通知渠道字段。该字段不为空时,会覆盖控制台配置的 ChannelID 值;该字段为空时,不会覆盖控制台配置的 ChannelID 值。
+ XiaoMiChannelID string `json:"XiaoMiChannelID,omitempty"` // (选填)小米手机 MIUI 10 及以上的通知类别(Channel)适配字段。该字段不为空时,会覆盖控制台配置的 ChannelID 值;该字段为空时,不会覆盖控制台配置的 ChannelID 值。
+ OPPOChannelID string `json:"OPPOChannelID,omitempty"` // (选填)OPPO 手机 Android 8.0 及以上的 NotificationChannel 通知适配字段。该字段不为空时,会覆盖控制台配置的 ChannelID 值;该字段为空时,不会覆盖控制台配置的 ChannelID 值。
+ GoogleChannelID string `json:"GoogleChannelID,omitempty"` // (选填)Google 手机 Android 8.0 及以上的通知渠道字段。Google 推送新接口(上传证书文件)支持 channel id,旧接口(填写服务器密钥)不支持。
+ VIVOClassification int `json:"VIVOClassification,omitempty"` // (选填)VIVO 手机推送消息分类,“0”代表运营消息,“1”代表系统消息,不填默认为1。
+ HuaWeiImportance string `json:"HuaWeiImportance,omitempty"` // (选填)华为推送通知消息分类,取值为 LOW、NORMAL,不填默认为 NORMAL。
+ ExtAsHuaweiIntentParam int `json:"ExtAsHuaweiIntentParam,omitempty"` // (选填)在控制台配置华为推送为“打开应用内指定页面”的前提下,传“1”表示将透传内容 Ext 作为 Intent 的参数,“0”表示将透传内容 Ext 作为 Action 参数。不填默认为0。两种传参区别可参见 华为推送文档。
+ }
+
+ // ApnsInfo IOS离线推送消息
+ ApnsInfo struct {
+ BadgeMode int `json:"BadgeMode,omitempty"` // (选填)这个字段缺省或者为0表示需要计数,为1表示本条消息不需要计数,即右上角图标数字不增加。
+ Title string `json:"Title,omitempty"` // (选填)该字段用于标识 APNs 推送的标题,若填写则会覆盖最上层 Title。
+ SubTitle string `json:"SubTitle,omitempty"` // (选填)该字段用于标识 APNs 推送的子标题。
+ Image string `json:"Image,omitempty"` // (选填)该字段用于标识 APNs 携带的图片地址,当客户端拿到该字段时,可以通过下载图片资源的方式将图片展示在弹窗上。
+ MutableContent int `json:"MutableContent,omitempty"` // (选填)为1表示开启 iOS 10 的推送扩展,默认为0。
+ }
+
+ // OfflinePushInfo 离线推送消息
+ OfflinePushInfo struct {
+ PushFlag int `json:"PushFlag,omitempty"` // (选填)推送标识。0表示推送,1表示不离线推送。
+ Title string `json:"Title,omitempty"` // (选填)离线推送标题。该字段为 iOS 和 Android 共用。
+ Desc string `json:"Desc,omitempty"` // (选填)离线推送内容。该字段会覆盖上面各种消息元素 TIMMsgElement 的离线推送展示文本。若发送的消息只有一个 TIMCustomElem 自定义消息元素,该 Desc 字段会覆盖 TIMCustomElem 中的 Desc 字段。如果两个 Desc 字段都不填,将收不到该自定义消息的离线推送。
+ Ext string `json:"Ext,omitempty"` // (选填)离线推送透传内容。由于国内各 Android 手机厂商的推送平台要求各不一样,请保证此字段为 JSON 格式,否则可能会导致收不到某些厂商的离线推送。
+ AndroidInfo *AndroidInfo `json:"AndroidInfo,omitempty"` // (选填)Android 离线推送消息
+ ApnsInfo *ApnsInfo `json:"ApnsInfo,omitempty"` // (选填)IOS离线推送消息
+ }
+
+ // MsgBody 消息内容
+ MsgBody struct {
+ MsgType string `json:"MsgType"`
+ MsgContent interface{} `json:"MsgContent"`
+ }
+
+ // TagPair 标签对
+ TagPair struct {
+ Tag string `json:"Tag"` // 标签
+ Value interface{} `json:"Value"` // 标签值
+ }
+
+ // MsgTextContent 文本消息内容
+ MsgTextContent struct {
+ Text string `json:"Text"` // (必填)消息内容。当接收方为 iOS 或 Android 后台在线时,作为离线推送的文本展示。
+ }
+
+ // MsgLocationContent 地理位置消息元素
+ MsgLocationContent struct {
+ Desc string `json:"Desc"` // (必填)地理位置描述信息
+ Latitude float64 `json:"Latitude"` // (必填)纬度
+ Longitude float64 `json:"Longitude"` // (必填)经度
+ }
+
+ // MsgFaceContent 表情消息元素
+ MsgFaceContent struct {
+ Index int `json:"Index"` // (必填)表情索引,用户自定义
+ Data string `json:"Data"` // (选填)额外数据
+ }
+
+ // MsgCustomContent 自定义消息元素
+ MsgCustomContent struct {
+ Desc string `json:"Desc"` // (选填)自定义消息描述信息。当接收方为 iOS 或 Android 后台在线时,做离线推送文本展示。 若发送自定义消息的同时设置了 OfflinePushInfo.Desc 字段,此字段会被覆盖,请优先填 OfflinePushInfo.Desc 字段。
+ Data string `json:"Data"` // (必填)自定义消息数据。 不作为 APNs 的 payload 字段下发,故从 payload 中无法获取 Data 字段
+ Ext string `json:"Ext"` // (选填)扩展字段。当接收方为 iOS 系统且应用处在后台时,此字段作为 APNs 请求包 Payloads 中的 Ext 键值下发,Ext 的协议格式由业务方确定,APNs 只做透传。
+ Sound string `json:"Sound"` // (选填)自定义 APNs 推送铃音。
+ }
+
+ // MsgSoundContent 语音消息元素
+ MsgSoundContent struct {
+ UUID string `json:"UUID"` // (必填)语音的唯一标识,类型为 String。客户端用于索引语音的键值。无法通过该字段下载相应的语音。若需要获取该语音,请升级 IM SDK 版本至4.X。
+ Url string `json:"Url"` // (必填)语音下载地址,可通过该 URL 地址直接下载相应语音
+ Size int `json:"Size"` // (必填)语音数据大小,单位:字节。
+ Second int `json:"Second"` // (必填)语音时长,单位:秒。
+ DownloadFlag int `json:"Download_Flag"` // (必填)语音下载方式标记。目前 Download_Flag 取值只能为2,表示可通过Url字段值的 URL 地址直接下载语音。
+ }
+
+ // MsgImageContent 图像消息元素
+ MsgImageContent struct {
+ UUID string `json:"UUID"` // (必填)图片序列号。后台用于索引图片的键值。
+ ImageFormat int `json:"ImageFormat"` // (必填)图片格式。JPG = 1,GIF = 2,PNG = 3,BMP = 4,其他 = 255。
+ ImageInfos []*ImageInfo `json:"ImageInfoArray"` // (必填)原图、缩略图或者大图下载信息。
+ }
+
+ // MsgFileContent 文件消息元素
+ MsgFileContent struct {
+ Url string `json:"Url"` // (必填)文件下载地址,可通过该 URL 地址直接下载相应文件
+ UUID string `json:"UUID"` // (必填)文件的唯一标识,客户端用于索引文件的键值。
+ FileSize int `json:"FileSize"` // (必填)文件数据大小,单位:字节
+ FileName string `json:"FileName"` // (必填)文件名称
+ DownloadFlag int `json:"Download_Flag"` // (必填)文件下载方式标记。目前 Download_Flag 取值只能为2,表示可通过Url字段值的 URL 地址直接下载文件。
+ }
+
+ // MsgVideoContent 视频消息元素
+ MsgVideoContent struct {
+ VideoUUID string `json:"VideoUUID"` // (必填)视频的唯一标识,客户端用于索引视频的键值。
+ VideoUrl string `json:"VideoUrl"` // (必填)视频下载地址。可通过该 URL 地址直接下载相应视频
+ VideoSize int `json:"VideoSize"` // (必填)视频数据大小,单位:字节
+ VideoSecond int `json:"VideoSecond"` // (必填)视频时长,单位:秒
+ VideoFormat string `json:"VideoFormat"` // (必填)视频格式,例如 mp4
+ VideoDownloadFlag int `json:"VideoDownloadFlag"` // (必填)视频下载方式标记。目前 VideoDownloadFlag 取值只能为2,表示可通过VideoUrl字段值的 URL 地址直接下载视频。
+ ThumbUrl string `json:"ThumbUrl"` // (必填)视频缩略图下载地址。可通过该 URL 地址直接下载相应视频缩略图。
+ ThumbUUID string `json:"ThumbUUID"` // (必填)视频缩略图的唯一标识,客户端用于索引视频缩略图的键值。
+ ThumbSize int `json:"ThumbSize"` // (必填)缩略图大小,单位:字节
+ ThumbWidth int `json:"ThumbWidth"` // (必填)缩略图宽度
+ ThumbHeight int `json:"ThumbHeight"` // (必填)缩略图高度
+ ThumbFormat string `json:"ThumbFormat"` // (必填)缩略图格式,例如 JPG、BMP 等
+ ThumbDownloadFlag int `json:"ThumbDownloadFlag"` // (必填)视频缩略图下载方式标记。目前 ThumbDownloadFlag 取值只能为2,表示可通过ThumbUrl字段值的 URL 地址直接下载视频缩略图。
+ }
+
+ // ImageInfo 图片下载信息
+ ImageInfo struct {
+ Type int `json:"Type"` // (必填)图片类型: 1-原图,2-大图,3-缩略图。
+ Size int `json:"Size"` // (必填)图片数据大小,单位:字节。
+ Width int `json:"Width"` // (必填)图片宽度。
+ Height int `json:"Height"` // (必填)图片高度。
+ Url string `json:"URL"` // (必填)图片下载地址。
+ }
+
+ // GenderType 性别类型
+ GenderType string
+
+ // AllowType 加好友验证方式
+ AllowType string
+
+ // AdminForbidType 管理员禁止加好友标识类型
+ AdminForbidType string
+
+ // SyncOtherMachine 同步至其他设备
+ SyncOtherMachine int
+
+ // PushFlag 推送标识
+ PushFlag int
+
+ // HuaWeiImportance 华为推送通知消息分类
+ HuaWeiImportance string
+
+ // HuaweiIntentParam 华为推送为“打开应用内指定页面”的前提下透传参数行为
+ HuaweiIntentParam int
+
+ // VivoClassification VIVO手机推送消息分类
+ VivoClassification int
+
+ // BadgeMode IOS徽章计数模式
+ BadgeMode int
+
+ // MutableContent IOS10的推送扩展开关
+ MutableContent int
+)
diff --git a/mute/api.go b/mute/api.go
new file mode 100644
index 0000000..d30bff6
--- /dev/null
+++ b/mute/api.go
@@ -0,0 +1,83 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/30 2:41 上午
+ * @Desc: 全局禁言管理
+ */
+
+package mute
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ service = "openconfigsvr"
+ commandSetNoSpeaking = "setnospeaking"
+ commandGetNoSpeaking = "getnospeaking"
+)
+
+type API interface {
+ // SetNoSpeaking 设置全局禁言
+ // 设置帐号的单聊消息全局禁言。
+ // 设置帐号的群组消息全局禁言。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/4230
+ SetNoSpeaking(userId string, privateMuteTime, groupMuteTime *uint) (err error)
+
+ // GetNoSpeaking 查询全局禁言
+ // 查询帐号的单聊消息全局禁言。
+ // 查询帐号的群组消息全局禁言。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/4229
+ GetNoSpeaking(userId string) (ret *GetNoSpeakingRet, err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// SetNoSpeaking 设置全局禁言
+// 设置帐号的单聊消息全局禁言。
+// 设置帐号的群组消息全局禁言。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/4230
+func (a *api) SetNoSpeaking(userId string, privateMuteTime, groupMuteTime *uint) (err error) {
+ req := &setNoSpeakingReq{
+ UserId: userId,
+ PrivateMuteTime: privateMuteTime,
+ GroupMuteTime: groupMuteTime,
+ }
+
+ if err = a.client.Post(service, commandSetNoSpeaking, req, &types.BaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// GetNoSpeaking 查询全局禁言
+// 查询帐号的单聊消息全局禁言。
+// 查询帐号的群组消息全局禁言。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/4229
+func (a *api) GetNoSpeaking(userId string) (ret *GetNoSpeakingRet, err error) {
+ req := &getNoSpeakingReq{UserId: userId}
+ resp := &getNoSpeakingResp{}
+
+ if err = a.client.Post(service, commandGetNoSpeaking, req, resp); err != nil {
+ return
+ }
+
+ ret = &GetNoSpeakingRet{
+ PrivateMuteTime: resp.PrivateMuteTime,
+ GroupMuteTime: resp.GroupMuteTime,
+ }
+
+ return
+}
diff --git a/mute/types.go b/mute/types.go
new file mode 100644
index 0000000..f794cb6
--- /dev/null
+++ b/mute/types.go
@@ -0,0 +1,37 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/30 2:41 上午
+ * @Desc: 全局禁言数据类型
+ */
+
+package mute
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ // 设置全局禁言(请求)
+ setNoSpeakingReq struct {
+ UserId string `json:"Set_Account"` // (必填)设置禁言配置的帐号
+ PrivateMuteTime *uint `json:"C2CmsgNospeakingTime,omitempty"` // (选填)单聊消息禁言时间,单位为秒,非负整数,最大值为4294967295(十六进制 0xFFFFFFFF) 0表示取消该帐号的单聊消息禁言;4294967295表示该帐号被设置永久禁言;其它值表示该帐号具体的禁言时间
+ GroupMuteTime *uint `json:"GroupmsgNospeakingTime,omitempty"` // (选填)单聊消息禁言时间,单位为秒,非负整数,最大值为4294967295(十六进制 0xFFFFFFFF) 0表示取消该帐号的单聊消息禁言;4294967295表示该帐号被设置永久禁言;其它值表示该帐号具体的禁言时间
+ }
+
+ // 设置全局禁言(请求)
+ getNoSpeakingReq struct {
+ UserId string `json:"Get_Account"` // (必填)查询禁言信息的帐号
+ }
+
+ // 设置全局禁言(响应)
+ getNoSpeakingResp struct {
+ types.BaseResp
+ PrivateMuteTime uint `json:"C2CmsgNospeakingTime"` // 单聊消息禁言时长,单位为秒,非负整数。等于 0 代表没有被设置禁言;等于最大值4294967295(十六进制 0xFFFFFFFF)代表被设置永久禁言;其它代表该帐号禁言时长,如果等于3600表示该帐号被禁言一小时
+ GroupMuteTime uint `json:"GroupmsgNospeakingTime"` // 群组消息禁言时长,单位为秒,非负整数。等于0代表没有被设置禁言;等于最大值4294967295(十六进制 0xFFFFFFFF)代表被设置永久禁言;其它代表该帐号禁言时长,如果等于3600表示该帐号被禁言一小时
+ }
+
+ // GetNoSpeakingRet 获取全局禁言(返回)
+ GetNoSpeakingRet struct {
+ PrivateMuteTime uint // 单聊消息禁言时长,单位为秒,非负整数。等于 0 代表没有被设置禁言;等于最大值4294967295(十六进制 0xFFFFFFFF)代表被设置永久禁言;其它代表该帐号禁言时长,如果等于3600表示该帐号被禁言一小时
+ GroupMuteTime uint // 群组消息禁言时长,单位为秒,非负整数。等于0代表没有被设置禁言;等于最大值4294967295(十六进制 0xFFFFFFFF)代表被设置永久禁言;其它代表该帐号禁言时长,如果等于3600表示该帐号被禁言一小时
+ }
+)
diff --git a/operation/api.go b/operation/api.go
new file mode 100644
index 0000000..c57062a
--- /dev/null
+++ b/operation/api.go
@@ -0,0 +1,104 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 18:38
+ * @Desc: 运营管理
+ */
+
+package operation
+
+import (
+ "time"
+
+ "git.echol.cn/loser/tencent-im/internal/core"
+)
+
+const (
+ serviceOperation = "openconfigsvr"
+ serviceOpenMessage = "open_msg_svc"
+ serviceConfig = "ConfigSvc"
+ commandGetAppInfo = "getappinfo"
+ commandGetHistory = "get_history"
+ commandGetIPList = "GetIPList"
+)
+
+type API interface {
+ // GetOperationData 拉取运营数据
+ // App 管理员可以通过该接口拉取最近30天的运营数据,可拉取的字段见下文可拉取的运营字段。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/4193
+ GetOperationData(fields ...FieldType) (data []*OperationData, err error)
+
+ // GetHistoryData 下载最近消息记录
+ // App 管理员可以通过该接口获取 App 中最近7天中某天某小时的所有单发或群组消息记录的下载地址
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1650
+ GetHistoryData(chatType ChatType, msgTime time.Time) (files []*HistoryFile, err error)
+
+ // GetIPList 获取服务器IP地址
+ // 基于安全等考虑,您可能需要获知服务器的 IP 地址列表,以便进行相关限制。
+ // App 管理员可以通过该接口获得 SDK、第三方回调所使用到的服务器 IP 地址列表或 IP 网段信息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45438
+ GetIPList() (ips []string, err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// GetOperationData 拉取运营数据
+// App 管理员可以通过该接口拉取最近30天的运营数据,可拉取的字段见下文可拉取的运营字段。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/4193
+func (a *api) GetOperationData(fields ...FieldType) (data []*OperationData, err error) {
+ req := &getOperationDataReq{Fields: fields}
+ resp := &getOperationDataResp{}
+
+ if err = a.client.Post(serviceOperation, commandGetAppInfo, req, resp); err != nil {
+ return
+ }
+
+ data = resp.Data
+
+ return
+}
+
+// GetHistoryData 下载最近消息记录
+// App 管理员可以通过该接口获取 App 中最近7天中某天某小时的所有单发或群组消息记录的下载地址
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1650
+func (a *api) GetHistoryData(chatType ChatType, msgTime time.Time) (files []*HistoryFile, err error) {
+ req := &getHistoryDataReq{ChatType: chatType, MsgTime: msgTime.Format("2006010215")}
+ resp := &getHistoryDataResp{}
+
+ if err = a.client.Post(serviceOpenMessage, commandGetHistory, req, resp); err != nil {
+ return
+ }
+
+ files = resp.Files
+
+ return
+}
+
+// GetIPList 获取服务器IP地址
+// 基于安全等考虑,您可能需要获知服务器的 IP 地址列表,以便进行相关限制。
+// App 管理员可以通过该接口获得 SDK、第三方回调所使用到的服务器 IP 地址列表或 IP 网段信息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45438
+func (a *api) GetIPList() (ips []string, err error) {
+ req := &getIPListReq{}
+ resp := &getIPListResp{}
+
+ if err = a.client.Post(serviceConfig, commandGetIPList, req, resp); err != nil {
+ return
+ }
+
+ ips = resp.IPList
+
+ return
+}
diff --git a/operation/enum.go b/operation/enum.go
new file mode 100644
index 0000000..93ce474
--- /dev/null
+++ b/operation/enum.go
@@ -0,0 +1,51 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 10:28
+ * @Desc: 运营管理枚举参数
+ */
+
+package operation
+
+type (
+ // ChatType 聊天类型
+ ChatType string
+
+ // FieldType 运营数据字段类型
+ FieldType string
+)
+
+const (
+ ChatTypeC2C ChatType = "C2C" // 单聊消息
+ ChatTypeGroup ChatType = "Group" // 群聊消息
+
+ FieldTypeAppName FieldType = "AppName" // 应用名称
+ FieldTypeAppId FieldType = "AppId" // 应用 SDKAppID
+ FieldTypeCompany FieldType = "Company" // 所属客户名称
+ FieldTypeActiveUserNum FieldType = "ActiveUserNum" // 活跃用户数
+ FieldTypeRegisterUserNumOneDay FieldType = "RegistUserNumOneDay" // 新增注册人数
+ FieldTypeRegisterUserNumTotal FieldType = "RegistUserNumTotal" // 累计注册人数
+ FieldTypeLoginTimes FieldType = "LoginTimes" // 登录次数
+ FieldTypeLoginUserNum FieldType = "LoginUserNum" // 登录人数
+ FieldTypeUpMsgNum FieldType = "UpMsgNum" // 上行消息数
+ FieldTypeSendMsgUserNum FieldType = "SendMsgUserNum" // 发消息人数
+ FieldTypeAPNSMsgNum FieldType = "APNSMsgNum" // APNs 推送数
+ FieldTypeC2CUpMsgNum FieldType = "C2CUpMsgNum" // 上行消息数(C2C)
+ FieldTypeC2CSendMsgUserNum FieldType = "C2CSendMsgUserNum" // 发消息人数(C2C)
+ FieldTypeC2CAPNSMsgNum FieldType = "C2CAPNSMsgNum" // APNs 推送数(C2C)
+ FieldTypeMaxOnlineNum FieldType = "MaxOnlineNum" // 最高在线人数
+ FieldTypeChainIncrease FieldType = "ChainIncrease" // 关系链对数增加量
+ FieldTypeChainDecrease FieldType = "ChainDecrease" // 关系链对数删除量
+ FieldTypeGroupUpMsgNum FieldType = "GroupUpMsgNum" // 上行消息数(群)
+ FieldTypeGroupSendMsgUserNum FieldType = "GroupSendMsgUserNum" // 发消息人数(群)
+ FieldTypeGroupAPNSMsgNum FieldType = "GroupAPNSMsgNum" // APNs 推送数(群)
+ FieldTypeGroupSendMsgGroupNum FieldType = "GroupSendMsgGroupNum" // 发消息群组数
+ FieldTypeGroupJoinGroupTimes FieldType = "GroupJoinGroupTimes" // 入群总数
+ FieldTypeGroupQuitGroupTimes FieldType = "GroupQuitGroupTimes" // 退群总数
+ FieldTypeGroupNewGroupNum FieldType = "GroupNewGroupNum" // 新增群组数
+ FieldTypeGroupAllGroupNum FieldType = "GroupAllGroupNum" // 累计群组数
+ FieldTypeGroupDestroyGroupNum FieldType = "GroupDestroyGroupNum" // 解散群个数
+ FieldTypeCallBackReq FieldType = "CallBackReq" // 回调请求数
+ FieldTypeCallBackRsp FieldType = "CallBackRsp" // 回调应答数
+ FieldTypeDate FieldType = "Date" // 日期
+)
diff --git a/operation/types.go b/operation/types.go
new file mode 100644
index 0000000..20bf41f
--- /dev/null
+++ b/operation/types.go
@@ -0,0 +1,91 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 18:39
+ * @Desc: 运营管理数据类型
+ */
+
+package operation
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ // 拉取运营数据(请求)
+ getOperationDataReq struct {
+ Fields []FieldType `json:"RequestField,omitempty"` // 该字段用来指定需要拉取的运营数据,不填默认拉取所有字段。
+ }
+
+ // 拉取运营数据(响应)
+ getOperationDataResp struct {
+ types.BaseResp
+ Data []*OperationData `json:"Result"`
+ }
+
+ // OperationData 运营数据
+ OperationData struct {
+ AppId string `json:"AppId"` // 应用AppID
+ AppName string `json:"AppName"` // 应用名称
+ Company string `json:"Company"` // 所属客户名称
+ ActiveUserNum string `json:"ActiveUserNum"` // 活跃用户数
+ RegistUserNumOneDay string `json:"RegistUserNumOneDay"` // 新增注册人数
+ RegistUserNumTotal string `json:"RegistUserNumTotal"` // 累计注册人数
+ LoginTimes string `json:"LoginTimes"` // 登录次数
+ LoginUserNum string `json:"LoginUserNum"` // 登录人数
+ UpMsgNum string `json:"UpMsgNum"` // 上行消息数
+ DownMsgNum string `json:"DownMsgNum"` // 下行消息数
+ SendMsgUserNum string `json:"SendMsgUserNum"` // 发消息人数
+ APNSMsgNum string `json:"APNSMsgNum"` // APNs推送数
+ C2CUpMsgNum string `json:"C2CUpMsgNum"` // 上行消息数(C2C)
+ C2CSendMsgUserNum string `json:"C2CSendMsgUserNum"` // 发消息人数(C2C)
+ C2CAPNSMsgNum string `json:"C2CAPNSMsgNum"` // APNs推送数(C2C)
+ C2CDownMsgNum string `json:"C2CDownMsgNum"` // 下行消息数(C2C)
+ MaxOnlineNum string `json:"MaxOnlineNum"` // 最高在线人数
+ ChainDecrease string `json:"ChainDecrease"` // 关系链对数删除量
+ ChainIncrease string `json:"ChainIncrease"` // 关系链对数增加量
+ GroupUpMsgNum string `json:"GroupUpMsgNum"` // 上行消息数(群)
+ GroupDownMsgNum string `json:"GroupDownMsgNum"` // 下行消息数(群)
+ GroupSendMsgUserNum string `json:"GroupSendMsgUserNum"` // 发消息人数(群)
+ GroupAPNSMsgNum string `json:"GroupAPNSMsgNum"` // APNs推送数(群)
+ GroupSendMsgGroupNum string `json:"GroupSendMsgGroupNum"` // 发消息群组数
+ GroupJoinGroupTimes string `json:"GroupJoinGroupTimes"` // 入群总数
+ GroupQuitGroupTimes string `json:"GroupQuitGroupTimes"` // 退群总数
+ GroupNewGroupNum string `json:"GroupNewGroupNum"` // 新增群组数
+ GroupAllGroupNum string `json:"GroupAllGroupNum"` // 累计群组数
+ GroupDestroyGroupNum string `json:"GroupDestroyGroupNum"` // 解散群个数
+ CallBackReq string `json:"CallBackReq"` // 回调请求数
+ CallBackRsp string `json:"CallBackRsp"` // 回调应答数
+ Date string `json:"Date"` // 日期
+ }
+
+ // 获取历史数据(请求)
+ getHistoryDataReq struct {
+ ChatType ChatType `json:"ChatType"` // (必填)消息类型,C2C 表示单发消息 Group 表示群组消息
+ MsgTime string `json:"MsgTime"` // (必填)需要下载的消息记录的时间段,2015120121表示获取2015年12月1日21:00 - 21:59的消息的下载地址。该字段需精确到小时。每次请求只能获取某天某小时的所有单发或群组消息记录
+ }
+
+ // 获取历史数据(响应)
+ getHistoryDataResp struct {
+ types.BaseResp
+ Files []*HistoryFile `json:"File"` // 消息记录文件下载信息
+ }
+
+ // HistoryFile 历史数据文件
+ HistoryFile struct {
+ URL string `json:"URL"` // 消息记录文件下载地址
+ ExpireTime string `json:"ExpireTime"` // 下载地址过期时间,请在过期前进行下载,若地址失效,请通过该接口重新获取
+ FileSize int `json:"FileSize"` // GZip 压缩前的文件大小(单位 Byte)
+ FileMD5 string `json:"FileMD5"` // GZip 压缩前的文件 MD5
+ GzipSize int `json:"GzipSize"` // GZip 压缩后的文件大小(单位 Byte)
+ GzipMD5 string `json:"GzipMD5"` // GZip 压缩后的文件 MD5
+ }
+
+ // 获取服务器IP地址(请求)
+ getIPListReq struct {
+ }
+
+ // 获取服务器IP地址(响应)
+ getIPListResp struct {
+ types.BaseResp
+ IPList []string `json:"IPList"` // 服务器IP列表
+ }
+)
diff --git a/private/api.go b/private/api.go
new file mode 100644
index 0000000..069f33b
--- /dev/null
+++ b/private/api.go
@@ -0,0 +1,354 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 19:32
+ * @Desc: 单聊消息
+ */
+
+package private
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/conv"
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ service = "openim"
+ commandSendMessage = "sendmsg"
+ commandSendMessages = "batchsendmsg"
+ commandImportMessage = "importmsg"
+ commandFetchMessages = "admin_getroammsg"
+ commandRevokeMessage = "admin_msgwithdraw"
+ commandSetMessageRead = "admin_set_msg_read"
+ commandGetUnreadMessageNum = "get_c2c_unread_msg_num"
+)
+
+type API interface {
+ // SendMessage 单发单聊消息
+ // 管理员向帐号发消息,接收方看到消息发送者是管理员。
+ // 管理员指定某一帐号向其他帐号发消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+ // 该接口不会检查发送者和接收者的好友关系(包括黑名单),同时不会检查接收者是否被禁言。
+ // 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2282
+ SendMessage(message *Message) (ret *SendMessageRet, err error)
+
+ // SendMessages 批量发单聊消息
+ // 支持一次对最多500个用户进行单发消息。
+ // 与单发消息相比,该接口更适用于营销类消息、系统通知 tips 等时效性较强的消息。
+ // 管理员指定某一帐号向目标帐号批量发消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+ // 该接口不触发回调请求。
+ // 该接口不会检查发送者和接收者的好友关系(包括黑名单),同时不会检查接收者是否被禁言。
+ // 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1612
+ SendMessages(message *Message) (ret *SendMessagesRet, err error)
+
+ // ImportMessage 导入单聊消息
+ // 导入历史单聊消息到即时通信 IM。
+ // 平滑过渡期间,将原有即时通信实时单聊消息导入到即时通信 IM。
+ // 该接口不会触发回调。
+ // 该接口会根据 From_Account , To_Account ,MsgSeq , MsgRandom , MsgTimeStamp 字段的值对导入的消息进行去重。仅当这五个字段的值都对应相同时,才判定消息是重复的,消息是否重复与消息内容本身无关。
+ // 重复导入的消息不会覆盖之前已导入的消息(即消息内容以首次导入的为准)。
+ // 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/2568
+ ImportMessage(message *Message) (err error)
+
+ // FetchMessages 查询单聊消息
+ // 管理员按照时间范围查询某单聊会话的消息记录。
+ // 查询的单聊会话由请求中的 From_Account 和 To_Account 指定。查询结果包含会话双方互相发送的消息,具体每条消息的发送方和接收方由每条消息里的 From_Account 和 To_Account 指定。
+ // 一般情况下,请求中的 From_Account 和 To_Account 字段值互换,查询结果不变。但通过 单发单聊消息 或 批量发单聊消息 接口发送的消息,如果指定 SyncOtherMachine 值为2,则需要指定正确的 From_Account 和 To_Account 字段值才能查询到该消息。
+ // 例如,通过 单发单聊消息 接口指定帐号 A 给帐号 B 发一条消息,同时指定 SyncOtherMachine 值为2。则调用本接口时,From_Account 必须设置为帐号 B,To_Account 必须设置为帐号 A 才能查询到该消息。
+ // 查询结果包含被撤回的消息,由消息里的 MsgFlagBits 字段标识。
+ // 若想通过 REST API 撤回单聊消息 接口撤回某条消息,可先用本接口查询出该消息的 MsgKey,然后再调用撤回接口进行撤回。
+ // 可查询的消息记录的时间范围取决于漫游消息存储时长,默认是7天。支持在控制台修改消息漫游时长,延长消息漫游时长是增值服务。具体请参考 漫游消息存储。
+ // 若请求时间段内的消息总大小超过应答包体大小限制(目前为13K)时,则需要续拉。您可以通过应答中的 Complete 字段查看是否已拉取请求的全部消息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/42794
+ FetchMessages(arg *FetchMessagesArg) (ret *FetchMessagesRet, err error)
+
+ // PullMessages 续拉取单聊消息
+ // 本API是借助"查询单聊消息"API进行扩展实现
+ // 管理员按照时间范围查询某单聊会话的全部消息记录
+ // 查询的单聊会话由请求中的 From_Account 和 To_Account 指定。查询结果包含会话双方互相发送的消息,具体每条消息的发送方和接收方由每条消息里的 From_Account 和 To_Account 指定。
+ // 一般情况下,请求中的 From_Account 和 To_Account 字段值互换,查询结果不变。但通过 单发单聊消息 或 批量发单聊消息 接口发送的消息,如果指定 SyncOtherMachine 值为2,则需要指定正确的 From_Account 和 To_Account 字段值才能查询到该消息。
+ // 例如,通过 单发单聊消息 接口指定帐号 A 给帐号 B 发一条消息,同时指定 SyncOtherMachine 值为2。则调用本接口时,From_Account 必须设置为帐号 B,To_Account 必须设置为帐号 A 才能查询到该消息。
+ // 查询结果包含被撤回的消息,由消息里的 MsgFlagBits 字段标识。
+ // 若想通过 REST API 撤回单聊消息 接口撤回某条消息,可先用本接口查询出该消息的 MsgKey,然后再调用撤回接口进行撤回。
+ // 可查询的消息记录的时间范围取决于漫游消息存储时长,默认是7天。支持在控制台修改消息漫游时长,延长消息漫游时长是增值服务。具体请参考 漫游消息存储。
+ // 若请求时间段内的消息总大小超过应答包体大小限制(目前为13K)时,则需要续拉。您可以通过应答中的 Complete 字段查看是否已拉取请求的全部消息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/42794
+ PullMessages(arg *PullMessagesArg, fn func(ret *FetchMessagesRet)) (err error)
+
+ // RevokeMessage 撤回单聊消息
+ // 管理员撤回单聊消息。
+ // 该接口可以撤回所有单聊消息,包括客户端发出的单聊消息,由 REST API 单发 和 批量发 接口发出的单聊消息。
+ // 若需要撤回由客户端发出的单聊消息,您可以开通 发单聊消息之前回调 或 发单聊消息之后回调 ,通过该回调接口记录每条单聊消息的 MsgKey ,然后填在本接口的 MsgKey 字段进行撤回。您也可以通过 查询单聊消息 查询出待撤回的单聊消息的 MsgKey 后,填在本接口的 MsgKey 字段进行撤回。
+ // 若需要撤回由 REST API 单发 和 批量发 接口发出的单聊消息,需要记录这些接口回包里的 MsgKey 字段以进行撤回。
+ // 调用该接口撤回消息后,该条消息的离线、漫游存储,以及消息发送方和接收方的客户端的本地缓存都会被撤回。
+ // 该接口可撤回的单聊消息没有时间限制,即可以撤回任何时间的单聊消息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/38980
+ RevokeMessage(fromUserId, toUserId, msgKey string) (err error)
+
+ // SetMessageRead 设置单聊消息已读
+ // 设置用户的某个单聊会话的消息全部已读。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/50349
+ SetMessageRead(userId, peerUserId string) (err error)
+
+ // GetUnreadMessageNum 查询单聊未读消息计数
+ // App 后台可以通过该接口查询特定账号的单聊总未读数(包含所有的单聊会话)或者单个单聊会话的未读数。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/56043
+ GetUnreadMessageNum(userId string, peerUserIds ...string) (ret *GetUnreadMessageNumRet, err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// SendMessage 单发单聊消息
+// 管理员向帐号发消息,接收方看到消息发送者是管理员。
+// 管理员指定某一帐号向其他帐号发消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+// 该接口不会检查发送者和接收者的好友关系(包括黑名单),同时不会检查接收者是否被禁言。
+// 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2282
+func (a *api) SendMessage(message *Message) (ret *SendMessageRet, err error) {
+ if err = message.CheckError(); err != nil {
+ return
+ }
+
+ req := &sendMessageReq{}
+ req.FromUserId = message.GetSender()
+ req.ToUserId = message.GetLastReceiver()
+ req.MsgLifeTime = message.GetLifeTime()
+ req.MsgTimeStamp = message.GetTimestamp()
+ req.OfflinePushInfo = message.GetOfflinePushInfo()
+ req.CloudCustomData = conv.String(message.GetCustomData())
+ req.MsgSeq = message.GetSerialNo()
+ req.MsgBody = message.GetBody()
+ req.MsgRandom = message.GetRandom()
+ req.SendMsgControl = message.GetSendMsgControl()
+ req.ForbidCallbackControl = message.GetForbidCallbackControl()
+ req.SyncOtherMachine = message.GetSyncOtherMachine()
+
+ resp := &sendMessageResp{}
+
+ if err = a.client.Post(service, commandSendMessage, req, resp); err != nil {
+ return
+ }
+
+ ret = &SendMessageRet{
+ MsgKey: resp.MsgKey,
+ MsgTime: resp.MsgTime,
+ }
+
+ return
+}
+
+// SendMessages 批量发单聊消息
+// 支持一次对最多500个用户进行单发消息。
+// 与单发消息相比,该接口更适用于营销类消息、系统通知 tips 等时效性较强的消息。
+// 管理员指定某一帐号向目标帐号批量发消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+// 该接口不触发回调请求。
+// 该接口不会检查发送者和接收者的好友关系(包括黑名单),同时不会检查接收者是否被禁言。
+// 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1612
+func (a *api) SendMessages(message *Message) (ret *SendMessagesRet, err error) {
+ if err = message.CheckError(); err != nil {
+ return
+ }
+
+ req := &sendMessagesReq{}
+ req.FromUserId = message.GetSender()
+ req.ToUserIds = message.GetReceivers()
+ req.OfflinePushInfo = message.GetOfflinePushInfo()
+ req.CloudCustomData = conv.String(message.GetCustomData())
+ req.MsgSeq = message.GetSerialNo()
+ req.MsgBody = message.GetBody()
+ req.MsgRandom = message.GetRandom()
+ req.SendMsgControl = message.GetSendMsgControl()
+ req.SyncOtherMachine = message.GetSyncOtherMachine()
+
+ resp := &sendMessagesResp{}
+
+ if err = a.client.Post(service, commandSendMessages, req, resp); err != nil {
+ return
+ }
+
+ ret = &SendMessagesRet{
+ MsgKey: resp.MsgKey,
+ Errors: resp.Errors,
+ }
+
+ return
+}
+
+// ImportMessage 导入单聊消息
+// 导入历史单聊消息到即时通信 IM。
+// 平滑过渡期间,将原有即时通信实时单聊消息导入到即时通信 IM。
+// 该接口不会触发回调。
+// 该接口会根据 From_Account , To_Account ,MsgSeq , MsgRandom , MsgTimeStamp 字段的值对导入的消息进行去重。仅当这五个字段的值都对应相同时,才判定消息是重复的,消息是否重复与消息内容本身无关。
+// 重复导入的消息不会覆盖之前已导入的消息(即消息内容以首次导入的为准)。
+// 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/2568
+func (a *api) ImportMessage(message *Message) (err error) {
+ if err = message.CheckError(); err != nil {
+ return
+ }
+
+ req := &importMessageReq{}
+ req.FromUserId = message.GetSender()
+ req.ToUserId = message.GetLastReceiver()
+ req.MsgTimeStamp = message.GetTimestamp()
+ req.CloudCustomData = conv.String(message.GetCustomData())
+ req.MsgSeq = message.GetSerialNo()
+ req.MsgBody = message.GetBody()
+ req.MsgRandom = message.GetRandom()
+ req.SyncFromOldSystem = message.GetSyncOtherMachine()
+
+ if err = a.client.Post(service, commandImportMessage, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// FetchMessages 查询单聊消息
+// 管理员按照时间范围查询某单聊会话的消息记录。
+// 查询的单聊会话由请求中的 From_Account 和 To_Account 指定。查询结果包含会话双方互相发送的消息,具体每条消息的发送方和接收方由每条消息里的 From_Account 和 To_Account 指定。
+// 一般情况下,请求中的 From_Account 和 To_Account 字段值互换,查询结果不变。但通过 单发单聊消息 或 批量发单聊消息 接口发送的消息,如果指定 SyncOtherMachine 值为2,则需要指定正确的 From_Account 和 To_Account 字段值才能查询到该消息。
+// 例如,通过 单发单聊消息 接口指定帐号 A 给帐号 B 发一条消息,同时指定 SyncOtherMachine 值为2。则调用本接口时,From_Account 必须设置为帐号 B,To_Account 必须设置为帐号 A 才能查询到该消息。
+// 查询结果包含被撤回的消息,由消息里的 MsgFlagBits 字段标识。
+// 若想通过 REST API 撤回单聊消息 接口撤回某条消息,可先用本接口查询出该消息的 MsgKey,然后再调用撤回接口进行撤回。
+// 可查询的消息记录的时间范围取决于漫游消息存储时长,默认是7天。支持在控制台修改消息漫游时长,延长消息漫游时长是增值服务。具体请参考 漫游消息存储。
+// 若请求时间段内的消息总大小超过应答包体大小限制(目前为13K)时,则需要续拉。您可以通过应答中的 Complete 字段查看是否已拉取请求的全部消息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/42794
+func (a *api) FetchMessages(arg *FetchMessagesArg) (ret *FetchMessagesRet, err error) {
+ resp := &fetchMessagesResp{}
+
+ if err = a.client.Post(service, commandFetchMessages, arg, resp); err != nil {
+ return
+ }
+
+ ret = &FetchMessagesRet{
+ LastMsgKey: resp.LastMsgKey,
+ LastMsgTime: resp.LastMsgTime,
+ Count: resp.MsgCount,
+ List: resp.MsgList,
+ HasMore: resp.Complete != 1,
+ }
+
+ return
+}
+
+// PullMessages 续拉取单聊消息
+// 本API是借助"查询单聊消息"API进行扩展实现
+// 管理员按照时间范围查询某单聊会话的全部消息记录
+// 查询的单聊会话由请求中的 From_Account 和 To_Account 指定。查询结果包含会话双方互相发送的消息,具体每条消息的发送方和接收方由每条消息里的 From_Account 和 To_Account 指定。
+// 一般情况下,请求中的 From_Account 和 To_Account 字段值互换,查询结果不变。但通过 单发单聊消息 或 批量发单聊消息 接口发送的消息,如果指定 SyncOtherMachine 值为2,则需要指定正确的 From_Account 和 To_Account 字段值才能查询到该消息。
+// 例如,通过 单发单聊消息 接口指定帐号 A 给帐号 B 发一条消息,同时指定 SyncOtherMachine 值为2。则调用本接口时,From_Account 必须设置为帐号 B,To_Account 必须设置为帐号 A 才能查询到该消息。
+// 查询结果包含被撤回的消息,由消息里的 MsgFlagBits 字段标识。
+// 若想通过 REST API 撤回单聊消息 接口撤回某条消息,可先用本接口查询出该消息的 MsgKey,然后再调用撤回接口进行撤回。
+// 可查询的消息记录的时间范围取决于漫游消息存储时长,默认是7天。支持在控制台修改消息漫游时长,延长消息漫游时长是增值服务。具体请参考 漫游消息存储。
+// 若请求时间段内的消息总大小超过应答包体大小限制(目前为13K)时,则需要续拉。您可以通过应答中的 Complete 字段查看是否已拉取请求的全部消息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/42794
+func (a *api) PullMessages(arg *PullMessagesArg, fn func(ret *FetchMessagesRet)) (err error) {
+ var (
+ ret *FetchMessagesRet
+ req = &FetchMessagesArg{
+ FromUserId: arg.FromUserId,
+ ToUserId: arg.ToUserId,
+ MaxLimited: arg.MaxLimited,
+ MinTime: arg.MinTime,
+ MaxTime: arg.MaxTime,
+ }
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchMessages(req)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ req.LastMsgKey = ret.LastMsgKey
+ req.MaxTime = ret.LastMsgTime
+ }
+ }
+
+ return
+}
+
+// RevokeMessage 撤回单聊消息
+// 管理员撤回单聊消息。
+// 该接口可以撤回所有单聊消息,包括客户端发出的单聊消息,由 REST API 单发 和 批量发 接口发出的单聊消息。
+// 若需要撤回由客户端发出的单聊消息,您可以开通 发单聊消息之前回调 或 发单聊消息之后回调 ,通过该回调接口记录每条单聊消息的 MsgKey ,然后填在本接口的 MsgKey 字段进行撤回。您也可以通过 查询单聊消息 查询出待撤回的单聊消息的 MsgKey 后,填在本接口的 MsgKey 字段进行撤回。
+// 若需要撤回由 REST API 单发 和 批量发 接口发出的单聊消息,需要记录这些接口回包里的 MsgKey 字段以进行撤回。
+// 调用该接口撤回消息后,该条消息的离线、漫游存储,以及消息发送方和接收方的客户端的本地缓存都会被撤回。
+// 该接口可撤回的单聊消息没有时间限制,即可以撤回任何时间的单聊消息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/38980
+func (a *api) RevokeMessage(fromUserId, toUserId, msgKey string) (err error) {
+ req := &revokeMessageReq{FromUserId: fromUserId, ToUserId: toUserId, MsgKey: msgKey}
+
+ if err = a.client.Post(service, commandRevokeMessage, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// SetMessageRead 设置单聊消息已读
+// 设置用户的某个单聊会话的消息全部已读。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/50349
+func (a *api) SetMessageRead(userId, peerUserId string) (err error) {
+ req := &setMessageReadReq{UserId: userId, PeerUserId: peerUserId}
+
+ if err = a.client.Post(service, commandSetMessageRead, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// GetUnreadMessageNum 查询单聊未读消息计数
+// App 后台可以通过该接口查询特定账号的单聊总未读数(包含所有的单聊会话)或者单个单聊会话的未读数。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/56043
+func (a *api) GetUnreadMessageNum(userId string, peerUserIds ...string) (ret *GetUnreadMessageNumRet, err error) {
+ req := &getUnreadMessageNumReq{UserId: userId, PeerUserIds: peerUserIds}
+ resp := &getUnreadMessageNumResp{}
+
+ if err = a.client.Post(service, commandGetUnreadMessageNum, req, resp); err != nil {
+ return
+ }
+
+ ret = &GetUnreadMessageNumRet{
+ Total: resp.AllUnreadMsgNum,
+ Results: make(map[string]int, len(resp.PeerUnreadMsgNums)),
+ Errors: resp.PeerErrors,
+ }
+
+ for _, item := range resp.PeerUnreadMsgNums {
+ ret.Results[item.UserId] = item.UnreadMsgNum
+ }
+
+ return
+}
diff --git a/private/enum.go b/private/enum.go
new file mode 100644
index 0000000..6aff35e
--- /dev/null
+++ b/private/enum.go
@@ -0,0 +1,42 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 10:22
+ * @Desc: TODO
+ */
+
+package private
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/enum"
+)
+
+const (
+ // 同步至其他设备
+ SyncOtherMachineYes = enum.SyncOtherMachineYes // 把消息同步到From_Account在线终端和漫游上
+ SyncOtherMachineNo = enum.SyncOtherMachineNo // 消息不同步至From_Account
+
+ // 推送标识
+ PushFlagYes = enum.PushFlagYes // 正常推送
+ PushFlagNo = enum.PushFlagYes // 不离线推送
+
+ // 华为推送通知消息分类
+ HuaWeiImportanceLow = enum.HuaWeiImportanceLow // LOW类消息
+ HuaWeiImportanceNormal = enum.HuaWeiImportanceNormal // NORMAL类消息
+
+ // 华为推送为“打开应用内指定页面”的前提下透传参数行为
+ HuaweiIntentParamAction = enum.HuaweiIntentParamAction // 将透传内容Ext作为Action参数
+ HuaweiIntentParamIntent = enum.HuaweiIntentParamIntent // 将透传内容Ext作为Intent参数
+
+ // VIVO手机推送消息分类
+ VivoClassificationOperation = enum.VivoClassificationOperation // 运营类消息
+ VivoClassificationSystem = enum.VivoClassificationSystem // 系统类消息
+
+ // IOS徽章计数模式
+ BadgeModeNormal = enum.BadgeModeNormal // 本条消息需要计数
+ BadgeModeIgnore = enum.BadgeModeIgnore // 本条消息不需要计数
+
+ // IOS10的推送扩展开关
+ MutableContentNormal = enum.MutableContentNormal // 关闭iOS10的推送扩展
+ MutableContentEnable = enum.MutableContentEnable // 开启iOS10的推送扩展
+)
diff --git a/private/message.go b/private/message.go
new file mode 100644
index 0000000..d4cbf0a
--- /dev/null
+++ b/private/message.go
@@ -0,0 +1,183 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/8/31 18:04
+ * @Desc: 私聊消息实体
+ */
+
+package private
+
+import (
+ "errors"
+
+ "git.echol.cn/loser/tencent-im/internal/entity"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+var errNotSetMsgReceiver = errors.New("message receiver is not set")
+
+type Message struct {
+ entity.Message
+ receivers []string // 接收方UserId(可以为多个)
+ syncOtherMachine int // 同步到其他器
+ timestamp int64 // 消息时间戳,UNIX 时间戳(单位:秒)
+ seq int // 消息序列号
+ customData interface{} // 自定义数据
+ sendControls map[string]bool // 发送消息控制
+ callbackControls map[string]bool // 禁用回调
+}
+
+func NewMessage() *Message {
+ return &Message{}
+}
+
+// AddReceivers 添加接收方
+func (m *Message) AddReceivers(userId ...string) {
+ if m.receivers == nil {
+ m.receivers = make([]string, 0)
+ }
+ m.receivers = append(m.receivers, userId...)
+}
+
+// SetReceivers 设置接收方
+func (m *Message) SetReceivers(userId ...string) {
+ if m.receivers != nil {
+ m.receivers = m.receivers[0:0]
+ }
+ m.AddReceivers(userId...)
+}
+
+// GetReceivers 获取接收方
+func (m *Message) GetReceivers() []string {
+ return m.receivers
+}
+
+func (m *Message) GetLastReceiver() string {
+ return m.receivers[0]
+}
+
+// SetSyncOtherMachine 设置同步到其他机器
+func (m *Message) SetSyncOtherMachine(syncOtherMachine types.SyncOtherMachine) {
+ m.syncOtherMachine = int(syncOtherMachine)
+}
+
+// GetSyncOtherMachine 获取同步至其他设备
+func (m *Message) GetSyncOtherMachine() int {
+ return m.syncOtherMachine
+}
+
+// SetSerialNo 设置消息序列号
+func (m *Message) SetSerialNo(seq int) {
+ m.seq = seq
+}
+
+// GetSerialNo 获取消息序列号
+func (m *Message) GetSerialNo() int {
+ return m.seq
+}
+
+// SetTimestamp 设置消息的时间戳
+func (m *Message) SetTimestamp(timestamp int64) {
+ m.timestamp = timestamp
+}
+
+// GetTimestamp 获取消息的时间戳
+func (m *Message) GetTimestamp() int64 {
+ return m.timestamp
+}
+
+// SetCustomData 设置自定义数据
+func (m *Message) SetCustomData(data interface{}) {
+ m.customData = data
+}
+
+// GetCustomData 获取自定义数据
+func (m *Message) GetCustomData() interface{} {
+ return m.customData
+}
+
+// SetForbidBeforeSendMsgCallback 设置禁止发消息前回调
+func (m *Message) SetForbidBeforeSendMsgCallback() {
+ if m.callbackControls == nil {
+ m.callbackControls = make(map[string]bool, 0)
+ }
+ m.callbackControls["ForbidBeforeSendMsgCallback"] = true
+}
+
+// SetForbidAfterSendMsgCallback 设置禁止发消息后回调
+func (m *Message) SetForbidAfterSendMsgCallback() {
+ if m.callbackControls == nil {
+ m.callbackControls = make(map[string]bool, 0)
+ }
+ m.callbackControls["ForbidAfterSendMsgCallback"] = true
+}
+
+// GetForbidCallbackControl 获取消息回调禁止开关
+func (m *Message) GetForbidCallbackControl() (controls []string) {
+ if m.callbackControls != nil {
+ if n := len(m.callbackControls); n > 0 {
+ controls = make([]string, 0, n)
+ for k := range m.callbackControls {
+ controls = append(controls, k)
+ }
+ }
+ }
+
+ return
+}
+
+// SetNoUnread 设置该条消息不计入未读数
+func (m *Message) SetNoUnread() {
+ if m.sendControls == nil {
+ m.sendControls = make(map[string]bool, 0)
+ }
+ m.sendControls["NoUnread"] = true
+}
+
+// SetNoLastMsg 设置该条消息不更新会话列表
+func (m *Message) SetNoLastMsg() {
+ if m.sendControls == nil {
+ m.sendControls = make(map[string]bool, 0)
+ }
+ m.sendControls["NoLastMsg"] = true
+}
+
+// GetSendMsgControl 获取消息发送控制选项
+func (m *Message) GetSendMsgControl() (controls []string) {
+ if m.sendControls != nil {
+ if n := len(m.sendControls); n > 0 {
+ controls = make([]string, 0, n)
+ for k := range m.sendControls {
+ controls = append(controls, k)
+ }
+ }
+ }
+
+ return
+}
+
+// CheckError 检测错误
+func (m *Message) CheckError() (err error) {
+ if err = m.CheckLifeTimeArgError(); err != nil {
+ return
+ }
+
+ if err = m.CheckBodyArgError(); err != nil {
+ return
+ }
+
+ if err = m.checkReceiverArgError(); err != nil {
+ return
+ }
+
+ return
+}
+
+// checkReceiverArgError 检测接收方参数
+func (m *Message) checkReceiverArgError() error {
+ if m.receivers == nil || len(m.receivers) == 0 {
+ return errNotSetMsgReceiver
+ }
+
+ return nil
+}
diff --git a/private/types.go b/private/types.go
new file mode 100644
index 0000000..e7a6207
--- /dev/null
+++ b/private/types.go
@@ -0,0 +1,192 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 17:42
+ * @Desc: 私聊消息数据结构
+ */
+
+package private
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ // 发送消息(请求)
+ sendMessageReq struct {
+ FromUserId string `json:"From_Account,omitempty"` // (选填)消息发送方UserID(用于指定发送消息方帐号)
+ ToUserId string `json:"To_Account"` // (必填)消息接收方UserID
+ MsgLifeTime int `json:"MsgLifeTime,omitempty"` // (选填)消息离线保存时长(单位:秒),最长为7天(604800秒)
+ MsgSeq int `json:"MsgSeq,omitempty"` // (选填)消息序列号,后台会根据该字段去重及进行同秒内消息的排序,详细规则请看本接口的功能说明。若不填该字段,则由后台填入随机数。
+ MsgRandom uint32 `json:"MsgRandom"` // (必填)消息随机数,后台用于同一秒内的消息去重。请确保该字段填的是随机数
+ MsgTimeStamp int64 `json:"MsgTimeStamp,omitempty"` // (选填)消息时间戳,UNIX 时间戳(单位:秒)
+ MsgBody []*types.MsgBody `json:"MsgBody"` // (必填)消息内容,具体格式请参考 消息格式描述(注意,一条消息可包括多种消息元素,MsgBody 为 Array 类型)
+ SyncOtherMachine int `json:"SyncOtherMachine,omitempty"` // (选填)消息是否同步到在线终端和漫游上 1:把消息同步到 From_Account 在线终端和漫游上;2:消息不同步至 From_Account; 若不填写默认情况下会将消息存 From_Account 漫游
+ CloudCustomData string `json:"CloudCustomData,omitempty"` // (选填)消息回调禁止开关,只对本条消息有效,
+ SendMsgControl []string `json:"SendMsgControl,omitempty"` // (选填)消息发送控制选项,是一个 String 数组,只对本条消息有效。
+ ForbidCallbackControl []string `json:"ForbidCallbackControl,omitempty"` // (选填)消息回调禁止开关,只对本条消息有效
+ OfflinePushInfo *types.OfflinePushInfo `json:"OfflinePushInfo,omitempty"` // (选填)离线推送信息配置
+ }
+
+ // 发送消息(响应)
+ sendMessageResp struct {
+ types.ActionBaseResp
+ MsgTime int `json:"MsgTime"` // 消息时间戳,UNIX 时间戳
+ MsgKey string `json:"MsgKey"` // 消息唯一标识,用于撤回。长度不超过50个字符
+ }
+
+ // SendMessageRet 发送消息结果
+ SendMessageRet struct {
+ MsgKey string // 消息唯一标识,用于撤回。长度不超过50个字符
+ MsgTime int // 消息时间戳,UNIX 时间戳
+ }
+
+ // 批量发单聊消息(请求)
+ sendMessagesReq struct {
+ FromUserId string `json:"From_Account,omitempty"` // (选填)消息发送方UserID(用于指定发送消息方帐号)
+ ToUserIds []string `json:"To_Account"` // (必填)消息接收方UserID
+ MsgSeq int `json:"MsgSeq,omitempty"` // (选填)消息序列号,后台会根据该字段去重及进行同秒内消息的排序,详细规则请看本接口的功能说明。若不填该字段,则由后台填入随机数。
+ MsgRandom uint32 `json:"MsgRandom"` // (必填)消息随机数,后台用于同一秒内的消息去重。请确保该字段填的是随机数
+ MsgBody []*types.MsgBody `json:"MsgBody"` // (必填)消息内容,具体格式请参考 消息格式描述(注意,一条消息可包括多种消息元素,MsgBody 为 Array 类型)
+ SyncOtherMachine int `json:"SyncOtherMachine,omitempty"` // (选填)消息是否同步到在线终端和漫游上 1:把消息同步到 From_Account 在线终端和漫游上;2:消息不同步至 From_Account; 若不填写默认情况下会将消息存 From_Account 漫游
+ CloudCustomData string `json:"CloudCustomData,omitempty"` // (选填)消息回调禁止开关,只对本条消息有效,
+ SendMsgControl []string `json:"SendMsgControl,omitempty"` // (选填)消息发送控制选项,是一个 String 数组,只对本条消息有效。
+ OfflinePushInfo *types.OfflinePushInfo `json:"OfflinePushInfo,omitempty"` // (选填)离线推送信息配置
+ }
+
+ // 批量发单聊消息(响应)
+ sendMessagesResp struct {
+ types.ActionBaseResp
+ MsgKey string `json:"MsgKey"`
+ Errors []SendMessageError `json:"ErrorList"`
+ }
+
+ // SendMessageError 发送消息错误项
+ SendMessageError struct {
+ UserId string `json:"To_Account"`
+ ErrorCode int `json:"ErrorCode"`
+ }
+
+ // SendMessagesRet 发送消息结果
+ SendMessagesRet struct {
+ MsgKey string
+ Errors []SendMessageError
+ }
+
+ // 导入消息(请求)
+ importMessageReq struct {
+ FromUserId string `json:"From_Account,omitempty"` // (选填)消息发送方UserID(用于指定发送消息方帐号)
+ ToUserId string `json:"To_Account"` // (必填)消息接收方UserID
+ MsgSeq int `json:"MsgSeq,omitempty"` // (选填)消息序列号,后台会根据该字段去重及进行同秒内消息的排序,详细规则请看本接口的功能说明。若不填该字段,则由后台填入随机数。
+ MsgRandom uint32 `json:"MsgRandom"` // (必填)消息随机数,后台用于同一秒内的消息去重。请确保该字段填的是随机数
+ MsgTimeStamp int64 `json:"MsgTimeStamp,omitempty"` // (选填)消息时间戳,UNIX 时间戳(单位:秒)
+ MsgBody []*types.MsgBody `json:"MsgBody"` // (必填)消息内容,具体格式请参考 消息格式描述(注意,一条消息可包括多种消息元素,MsgBody 为 Array 类型)
+ SyncFromOldSystem int `json:"SyncFromOldSystem,omitempty"` // (选填)消息是否同步到在线终端和漫游上 1:把消息同步到 From_Account 在线终端和漫游上;2:消息不同步至 From_Account; 若不填写默认情况下会将消息存 From_Account 漫游
+ CloudCustomData string `json:"CloudCustomData,omitempty"` // (选填)消息回调禁止开关,只对本条消息有效,
+ }
+
+ // FetchMessagesArg 拉取消息参数
+ FetchMessagesArg struct {
+ FromUserId string `json:"From_Account"` // (必填)会话其中一方的 UserID,若已指定发送消息方帐号,则为消息发送方
+ ToUserId string `json:"To_Account"` // (必填)会话其中一方的 UserID
+ MaxLimited int `json:"MaxCnt"` // (必填)请求的消息条数
+ MinTime int64 `json:"MinTime"` // (必填)请求的消息时间范围的最小值
+ MaxTime int64 `json:"MaxTime"` // (必填)请求的消息时间范围的最大值
+ LastMsgKey string `json:"LastMsgKey,omitempty"` // (选填)上一次拉取到的最后一条消息的 MsgKey,续拉时需要填该字段,填写方法见上方
+ }
+
+ // 拉取消息参数(响应)
+ fetchMessagesResp struct {
+ types.ActionBaseResp
+ Complete int `json:"Complete"` // 是否全部拉取,0表示未全部拉取,需要续拉,1表示已全部拉取
+ LastMsgTime int64 `json:"LastMsgTime"` // 本次拉取到的消息里的最后一条消息的时间
+ LastMsgKey string `json:"LastMsgKey"` // 本次拉取到的消息里的最后一条消息的标识
+ MsgCount int `json:"MsgCnt"` // 本次拉取到的消息条数
+ MsgList []*MessageItem `json:"MsgList"` // 消息列表
+ }
+
+ // FetchMessagesRet 消息结果
+ FetchMessagesRet struct {
+ LastMsgTime int64 // 本次拉取到的消息里的最后一条消息的时间
+ LastMsgKey string // 本次拉取到的消息里的最后一条消息的标识
+ Count int // 本次拉取到的消息条数
+ HasMore bool // 是否还有更多数据
+ List []*MessageItem // 消息列表
+ }
+
+ // MessageItem 消息项
+ MessageItem struct {
+ FromUserId string `json:"From_Account"`
+ ToUserId string `json:"To_Account"`
+ MsgSeq int `json:"MsgSeq"`
+ MsgRandom int `json:"MsgRandom"`
+ MsgTimeStamp int64 `json:"MsgTimeStamp"`
+ MsgFlagBits int `json:"MsgFlagBits"`
+ MsgKey string `json:"MsgKey"`
+ MsgBody []*types.MsgBody `json:"MsgBody"`
+ CloudCustomData string `json:"CloudCustomData"`
+ }
+
+ // PullMessagesArg 持续拉取单聊消息参数
+ PullMessagesArg struct {
+ FromUserId string `json:"From_Account"` // (必填)会话其中一方的 UserID,若已指定发送消息方帐号,则为消息发送方
+ ToUserId string `json:"To_Account"` // (必填)会话其中一方的 UserID
+ MaxLimited int `json:"MaxCnt"` // (必填)请求的消息条数
+ MinTime int64 `json:"MinTime"` // (必填)请求的消息时间范围的最小值
+ MaxTime int64 `json:"MaxTime"` // (必填)请求的消息时间范围的最大值
+ }
+
+ // 撤销消息(请求)
+ revokeMessageReq struct {
+ FromUserId string `json:"From_Account"` // (必填)消息发送方UserID
+ ToUserId string `json:"To_Account"` // (必填)消息接收方UserID
+ MsgKey string `json:"MsgKey"` // (必填)待撤回消息的唯一标识。该字段由 REST API 接口 单发单聊消息 和 批量发单聊消息 返回
+ }
+
+ // 设置单聊消息已读(请求)
+ setMessageReadReq struct {
+ UserId string `json:"Report_Account"` // (必填)进行消息已读的用户UserId
+ PeerUserId string `json:"Peer_Account"` // (必填)进行消息已读的单聊会话的另一方用户UserId
+ }
+
+ // 查询单聊未读消息计数(请求)
+ getUnreadMessageNumReq struct {
+ UserId string `json:"To_Account"` // (必填)待查询的用户UserId
+ PeerUserIds []string `json:"Peer_Account,omitempty"` // (选填)待查询的单聊会话对端的用户UserId
+ }
+
+ // 查询单聊未读消息计数(响应)
+ getUnreadMessageNumResp struct {
+ types.ActionBaseResp
+ AllUnreadMsgNum int `json:"AllC2CUnreadMsgNum"` // 单聊消息总未读数
+ PeerUnreadMsgNums []unreadMessageNum `json:"C2CUnreadMsgNumList"` // 单聊消息未读对端列表
+ PeerErrors []*UnreadMessageError `json:"ErrorList"` // 查询错误列表
+ }
+
+ // 未读消息数
+ unreadMessageNum struct {
+ UserId string `json:"Peer_Account"` // 单聊会话对端UserId
+ UnreadMsgNum int `json:"C2CUnreadMsgNum"` // 该单聊会话的未读数
+ }
+
+ // 查询错误项
+ UnreadMessageError struct {
+ UserId string `json:"Peer_Account"` // 查询错误的目标UserId
+ ErrorCode int `json:"ErrorCode"` // 查询错误的错误码。若目标帐号的错误码为70107表示该帐号不存在
+ }
+
+ // GetUnreadMessageNumRet 未读消息结果
+ GetUnreadMessageNumRet struct {
+ Total int // 单聊消息总未读数
+ Results map[string]int // 未读消息数列表
+ Errors []*UnreadMessageError // 错误消息列表
+ }
+
+ ImageInfo = types.ImageInfo
+ MsgTextContent = types.MsgTextContent
+ MsgFaceContent = types.MsgFaceContent
+ MsgFileContent = types.MsgFileContent
+ MsgImageContent = types.MsgImageContent
+ MsgSoundContent = types.MsgSoundContent
+ MsgVideoContent = types.MsgVideoContent
+ MsgCustomContent = types.MsgCustomContent
+ MsgLocationContent = types.MsgLocationContent
+)
diff --git a/profile/api.go b/profile/api.go
new file mode 100644
index 0000000..c612515
--- /dev/null
+++ b/profile/api.go
@@ -0,0 +1,106 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 20:44
+ * @Desc: 资料管理
+ */
+
+package profile
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ service = "profile"
+ commandSetProfile = "portrait_set"
+ commandGetProfiles = "portrait_get"
+)
+
+type API interface {
+ // SetProfile 设置资料
+ // 支持 标配资料字段 和 自定义资料字段 的设置
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1640
+ SetProfile(profile *Profile) (err error)
+
+ // GetProfiles 拉取资料
+ // 支持拉取好友和非好友的资料字段。
+ // 支持拉取 标配资料字段 和 自定义资料字段。
+ // 建议每次拉取的用户数不超过100,避免因回包数据量太大导致回包失败。
+ // 请确保请求中的所有帐号都已导入即时通信 IM,如果请求中含有未导入即时通信 IM 的帐号,即时通信 IM 后台将会提示错误。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1639
+ GetProfiles(userIds []string, attrs []string) (profiles []*Profile, err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// SetProfile 设置资料
+// 支持 标配资料字段 和 自定义资料字段 的设置
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1640
+func (a *api) SetProfile(profile *Profile) (err error) {
+ if err = profile.CheckError(); err != nil {
+ return
+ }
+
+ userId := profile.GetUserId()
+
+ attrs := profile.GetAttrs()
+
+ if len(attrs) == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the attributes is not set")
+ return
+ }
+
+ req := &setProfileReq{UserId: userId, Attrs: make([]*types.TagPair, 0, len(attrs))}
+
+ for tag, value := range attrs {
+ req.Attrs = append(req.Attrs, &types.TagPair{
+ Tag: tag,
+ Value: value,
+ })
+ }
+
+ if err = a.client.Post(service, commandSetProfile, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// GetProfiles 拉取资料
+// 支持拉取好友和非好友的资料字段。
+// 支持拉取 标配资料字段 和 自定义资料字段。
+// 建议每次拉取的用户数不超过100,避免因回包数据量太大导致回包失败。
+// 请确保请求中的所有帐号都已导入即时通信 IM,如果请求中含有未导入即时通信 IM 的帐号,即时通信 IM 后台将会提示错误。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1639
+func (a *api) GetProfiles(userIds []string, attrs []string) (profiles []*Profile, err error) {
+ req := &getProfileReq{UserIds: userIds, TagList: attrs}
+ resp := &getProfileResp{}
+
+ if err = a.client.Post(service, commandGetProfiles, req, resp); err != nil {
+ return
+ }
+
+ for _, account := range resp.UserProfiles {
+ p := NewProfile(account.UserId)
+ p.SetError(account.ResultCode, account.ResultInfo)
+ for _, item := range account.Profile {
+ p.SetAttr(item.Tag, item.Value)
+ }
+ profiles = append(profiles, p)
+ }
+
+ return
+}
diff --git a/profile/enum.go b/profile/enum.go
new file mode 100644
index 0000000..f7489fb
--- /dev/null
+++ b/profile/enum.go
@@ -0,0 +1,54 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 14:03
+ * @Desc: TODO
+ */
+
+package profile
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+type (
+ // GenderType 性别类型
+ GenderType = types.GenderType
+
+ // AllowType 加好友验证方式
+ AllowType = types.AllowType
+
+ // AdminForbidType 管理员禁止加好友标识类型
+ AdminForbidType = types.AdminForbidType
+)
+
+const (
+ // 性别类型
+ GenderTypeUnknown = enum.GenderTypeUnknown // 没设置性别
+ GenderTypeFemale = enum.GenderTypeFemale // 女性
+ GenderTypeMale = enum.GenderTypeMale // 男性
+
+ // 加好友验证方式
+ AllowTypeNeedConfirm = enum.AllowTypeNeedConfirm // 需要经过自己确认对方才能添加自己为好友
+ AllowTypeAllowAny = enum.AllowTypeAllowAny // 允许任何人添加自己为好友
+ AllowTypeDenyAny = enum.AllowTypeDenyAny // 不允许任何人添加自己为好友
+
+ // 管理员禁止加好友标识类型
+ AdminForbidTypeNone = enum.AdminForbidTypeNone // 默认值,允许加好友
+ AdminForbidTypeSendOut = enum.AdminForbidTypeSendOut // 禁止该用户发起加好友请求
+
+ // 标准资料字段
+ StandardAttrNickname = enum.StandardAttrNickname // 昵称
+ StandardAttrGender = enum.StandardAttrGender // 性别
+ StandardAttrBirthday = enum.StandardAttrBirthday // 生日
+ StandardAttrLocation = enum.StandardAttrLocation // 所在地
+ StandardAttrSignature = enum.StandardAttrSignature // 个性签名
+ StandardAttrAllowType = enum.StandardAttrAllowType // 加好友验证方式
+ StandardAttrLanguage = enum.StandardAttrLanguage // 语言
+ StandardAttrAvatar = enum.StandardAttrAvatar // 头像URL
+ StandardAttrMsgSettings = enum.StandardAttrMsgSettings // 消息设置
+ StandardAttrAdminForbidType = enum.StandardAttrAdminForbidType // 管理员禁止加好友标识
+ StandardAttrLevel = enum.StandardAttrLevel // 等级
+ StandardAttrRole = enum.StandardAttrRole // 角色
+)
diff --git a/profile/profile.go b/profile/profile.go
new file mode 100644
index 0000000..7830556
--- /dev/null
+++ b/profile/profile.go
@@ -0,0 +1,39 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/28 11:23 上午
+ * @Desc: TODO
+ */
+
+package profile
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/entity"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+)
+
+type Profile struct {
+ entity.User
+}
+
+func NewProfile(userId ...string) *Profile {
+ p := &Profile{}
+ if len(userId) > 0 {
+ p.SetUserId(userId[0])
+ }
+ return p
+}
+
+// CheckError 检测错误
+func (p *Profile) CheckError() (err error) {
+ if userId := p.GetUserId(); userId == "" {
+ return core.NewError(enum.InvalidParamsCode, "the userid is not set")
+ }
+
+ if err = p.GetError(); err != nil {
+ return
+ }
+
+ return
+}
diff --git a/profile/types.go b/profile/types.go
new file mode 100644
index 0000000..41f99cd
--- /dev/null
+++ b/profile/types.go
@@ -0,0 +1,39 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 17:38
+ * @Desc: 资料管理结构体定义
+ */
+
+package profile
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ // 设置资料(请求)
+ setProfileReq struct {
+ UserId string `json:"From_Account"` // (必填)需要设置该 UserID 的资料
+ Attrs []*types.TagPair `json:"ProfileItem"` // (必填)待设置的用户的资料对象数组
+ }
+
+ // 获取资料(请求)
+ getProfileReq struct {
+ UserIds []string `json:"To_Account"` // (必填)需要拉取这些UserID的资料
+ TagList []string `json:"TagList"` // (必填)指定要拉取的资料字段的 Tag,支持的字段有
+ }
+
+ // 获取资料(响应)
+ getProfileResp struct {
+ types.ActionBaseResp
+ ErrorDisplay string `json:"ErrorDisplay"` // 详细的客户端展示信息
+ UserProfiles []UserProfile `json:"UserProfileItem"` // 用户资料结构化信息
+ }
+
+ // UserProfile 用户资料
+ UserProfile struct {
+ UserId string `json:"To_Account"` // 用户的UserID
+ Profile []types.TagPair `json:"ProfileItem"` // 用户的资料对象数组
+ ResultCode int `json:"ResultCode"` // 处理结果,0表示成功,非0表示失败
+ ResultInfo string `json:"ResultInfo"` // 错误描述信息,成功时该字段为空
+ }
+)
diff --git a/push/api.go b/push/api.go
new file mode 100644
index 0000000..6e3ac98
--- /dev/null
+++ b/push/api.go
@@ -0,0 +1,398 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 20:42
+ * @Desc: 全员推送
+ */
+
+package push
+
+import (
+ "fmt"
+ "strconv"
+
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ service = "all_member_push"
+ commandPushMessage = "im_push"
+ commandSetAttrNames = "im_set_attr_name"
+ commandGetAttrNames = "im_get_attr_name"
+ commandGetUserAttrs = "im_get_attr"
+ commandSetUserAttrs = "im_set_attr"
+ commandDeleteUserAttrs = "im_remove_attr"
+ commandGetUserTags = "im_get_tag"
+ commandAddUserTags = "im_add_tag"
+ commandDeleteUserTags = "im_remove_tag"
+ commandDeleteUserAllTags = "im_remove_all_tags"
+
+ batchSetAttrNamesLimit = 10 // 批量设置应用属性名限制
+ batchGetUserAttrsLimit = 100 // 批量获取用户属性限制
+ batchSetUserAttrsLimit = 100 // 批量设置用户属性限制
+ batchDeleteUserAttrsLimit = 100 // 批量删除用户属性限制
+ batchAddUserTagsLimit = 100 // 批量添加用户标签限制
+ batchGetUserTagsLimit = 100 // 批量获取用户标签限制
+ batchDeleteUserTagsLimit = 100 // 批量删除用户标签限制
+ batchDeleteUserAllTagsUserLimit = 100 // 批量删除用户所有标签的用户限制
+)
+
+type API interface {
+ // PushMessage 全员推送
+ // 支持全员推送。
+ // 支持按用户属性推送。
+ // 支持按用户标签推送。
+ // 管理员推送消息,接收方看到消息发送者是管理员。
+ // 管理员指定某一帐号向其他帐号推送消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+ // 支持消息离线存储,不支持漫游。
+ // 由于全员推送需要下发的帐号数量巨大,下发完全部帐号需要一定时间(根据帐号总数而定,一般在一分钟内)。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45934
+ PushMessage(message *Message) (taskId string, err error)
+
+ // SetAttrNames 设置应用属性名称
+ // 每个应用可以设置自定义的用户属性,最多可以有10个。通过本接口可以设置每个属性的名称,设置完成后,即可用于按用户属性推送等。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45935
+ SetAttrNames(attrNames map[int]string) (err error)
+
+ // GetAttrNames 获取应用属性名称
+ // 管理员获取应用属性名称。使用前请先 设置应用属性名称 。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45936
+ GetAttrNames() (attrNames map[int]string, err error)
+
+ // GetUserAttrs 获取用户属性
+ // 获取用户属性(必须以管理员帐号调用);每次最多只能获取100个用户的属性。使用前请先 设置应用属性名称 。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45937
+ GetUserAttrs(userIds ...string) (attrs map[string]map[string]interface{}, err error)
+
+ // SetUserAttrs 设置用户属性
+ // 管理员给用户设置属性。每次最多只能给100个用户设置属性。使用前请先 设置应用属性名称 。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45938
+ SetUserAttrs(userAttrs map[string]map[string]interface{}) (err error)
+
+ // DeleteUserAttrs 删除用户属性
+ // 管理员给用户删除属性。注意每次最多只能给100个用户删除属性。使用前请先 设置应用属性名称。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45939
+ DeleteUserAttrs(userAttrs map[string][]string) (err error)
+
+ // GetUserTags 获取用户标签
+ // 获取用户标签(必须以管理员帐号调用)。每次最多只能获取100个用户的标签。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45940
+ GetUserTags(userIds ...string) (tags map[string][]string, err error)
+
+ // AddUserTags 添加用户标签
+ // 管理员给用户添加标签。
+ // 每次请求最多只能给100个用户添加标签,请求体中单个用户添加标签数最多为10个。
+ // 单个用户可设置最大标签数为100个,若用户当前标签超过100,则添加新标签之前请先删除旧标签。
+ // 单个标签最大长度为50字节。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45941
+ AddUserTags(userTags map[string][]string) (err error)
+
+ // DeleteUserTags 删除用户标签
+ // 管理员给用户删除标签。注意每次最多只能给100个用户删除标签。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45942
+ DeleteUserTags(userTags map[string][]string) (err error)
+
+ // DeleteUserAllTags 删除用户所有标签
+ // 管理员给用户删除所有标签。注意每次最多只能给100个用户删除所有标签。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/45943
+ DeleteUserAllTags(userIds ...string) (err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// PushMessage 全员推送
+// 支持全员推送。
+// 支持按用户属性推送。
+// 支持按用户标签推送。
+// 管理员推送消息,接收方看到消息发送者是管理员。
+// 管理员指定某一帐号向其他帐号推送消息,接收方看到发送者不是管理员,而是管理员指定的帐号。
+// 支持消息离线存储,不支持漫游。
+// 由于全员推送需要下发的帐号数量巨大,下发完全部帐号需要一定时间(根据帐号总数而定,一般在一分钟内)。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45934
+func (a *api) PushMessage(message *Message) (taskId string, err error) {
+ if err = message.checkError(); err != nil {
+ return
+ }
+
+ req := &pushMessageReq{}
+ req.FromUserId = message.GetSender()
+ req.MsgLifeTime = message.GetLifeTime()
+ req.OfflinePushInfo = message.GetOfflinePushInfo()
+ req.MsgBody = message.GetBody()
+ req.MsgRandom = message.GetRandom()
+ req.Condition = message.GetCondition()
+
+ resp := &pushMessageResp{}
+
+ if err = a.client.Post(service, commandPushMessage, req, resp); err != nil {
+ return
+ }
+
+ taskId = resp.TaskId
+
+ return
+}
+
+// SetAttrNames 设置应用属性名称
+// 每个应用可以设置自定义的用户属性,最多可以有10个。通过本接口可以设置每个属性的名称,设置完成后,即可用于按用户属性推送等。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45935
+func (a *api) SetAttrNames(attrNames map[int]string) (err error) {
+ if c := len(attrNames); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the attribute names is not set")
+ return
+ } else if c > batchSetAttrNamesLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of attribute names to be set cannot exceed %d", batchSetAttrNamesLimit))
+ return
+ }
+
+ req := &setAttrNamesReq{AttrNames: make(map[string]string, len(attrNames))}
+
+ for i, attrName := range attrNames {
+ req.AttrNames[strconv.Itoa(i)] = attrName
+ }
+
+ if err = a.client.Post(service, commandSetAttrNames, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// GetAttrNames 获取应用属性名称
+// 管理员获取应用属性名称。使用前请先 设置应用属性名称 。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45936
+func (a *api) GetAttrNames() (attrNames map[int]string, err error) {
+ req := &getAttrNamesReq{}
+ resp := &getAttrNamesResp{}
+
+ if err = a.client.Post(service, commandGetAttrNames, req, resp); err != nil {
+ return
+ }
+
+ if len(resp.AttrNames) > 0 {
+ var i int
+ attrNames = make(map[int]string, len(resp.AttrNames))
+ for key, attrName := range resp.AttrNames {
+ i, _ = strconv.Atoi(key)
+ attrNames[i] = attrName
+ }
+ }
+
+ return
+}
+
+// GetUserAttrs 获取用户属性
+// 获取用户属性(必须以管理员帐号调用);每次最多只能获取100个用户的属性。使用前请先 设置应用属性名称 。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45937
+func (a *api) GetUserAttrs(userIds ...string) (attrs map[string]map[string]interface{}, err error) {
+ if c := len(userIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the accounts is not set")
+ return
+ } else if c > batchGetUserAttrsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of accounts being queried cannot exceed %d", batchGetUserAttrsLimit))
+ return
+ }
+
+ req := &getUserAttrsReq{UserIds: userIds}
+ resp := &getUserAttrsResp{}
+
+ if err = a.client.Post(service, commandGetUserAttrs, req, resp); err != nil {
+ return
+ }
+
+ attrs = make(map[string]map[string]interface{}, len(resp.Attrs))
+ for _, attr := range resp.Attrs {
+ attrs[attr.UserId] = attr.Attrs
+ }
+
+ return
+}
+
+// SetUserAttrs 设置用户属性
+// 管理员给用户设置属性。每次最多只能给100个用户设置属性。使用前请先 设置应用属性名称 。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45938
+func (a *api) SetUserAttrs(userAttrs map[string]map[string]interface{}) (err error) {
+ if c := len(userAttrs); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the attributes is not set")
+ return
+ } else if c > batchSetUserAttrsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of attributes to be set cannot exceed %d", batchSetUserAttrsLimit))
+ return
+ }
+
+ req := &setUserAttrsReq{Attrs: make([]*userAttrItem, 0, len(userAttrs))}
+ for userId, attrs := range userAttrs {
+ req.Attrs = append(req.Attrs, &userAttrItem{
+ UserId: userId,
+ Attrs: attrs,
+ })
+ }
+
+ if err = a.client.Post(service, commandSetUserAttrs, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// DeleteUserAttrs 删除用户属性
+// 管理员给用户删除属性。注意每次最多只能给100个用户删除属性。使用前请先 设置应用属性名称。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45939
+func (a *api) DeleteUserAttrs(userAttrs map[string][]string) (err error) {
+ if c := len(userAttrs); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the attributes is not set")
+ return
+ } else if c > batchDeleteUserAttrsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of attributes to be delete cannot exceed %d", batchDeleteUserAttrsLimit))
+ return
+ }
+
+ req := &deleteUserAttrsReq{Attrs: make([]deleteUserAttr, 0, len(userAttrs))}
+ for userId, attrs := range userAttrs {
+ req.Attrs = append(req.Attrs, deleteUserAttr{
+ UserId: userId,
+ Attrs: attrs,
+ })
+ }
+
+ if err = a.client.Post(service, commandDeleteUserAttrs, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// GetUserTags 获取用户标签
+// 获取用户标签(必须以管理员帐号调用)。每次最多只能获取100个用户的标签。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45940
+func (a *api) GetUserTags(userIds ...string) (tags map[string][]string, err error) {
+ if c := len(userIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the accounts is not set")
+ return
+ } else if c > batchGetUserTagsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of tags being queried cannot exceed %d", batchGetUserTagsLimit))
+ return
+ }
+
+ req := &getUserTagsReq{UserIds: userIds}
+ resp := &getUserTagsResp{}
+
+ if err = a.client.Post(service, commandGetUserTags, req, resp); err != nil {
+ return
+ }
+
+ if len(resp.Tags) > 0 {
+ tags = make(map[string][]string, len(resp.Tags))
+ for _, item := range resp.Tags {
+ tags[item.UserId] = item.Tags
+ }
+ }
+
+ return
+}
+
+// AddUserTags 添加用户标签
+// 管理员给用户添加标签。
+// 每次请求最多只能给100个用户添加标签,请求体中单个用户添加标签数最多为10个。
+// 单个用户可设置最大标签数为100个,若用户当前标签超过100,则添加新标签之前请先删除旧标签。
+// 单个标签最大长度为50字节。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45941
+func (a *api) AddUserTags(userTags map[string][]string) (err error) {
+ if c := len(userTags); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the tags of user is not set")
+ return
+ } else if c > batchAddUserTagsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of tags to be add cannot exceed %d", batchAddUserTagsLimit))
+ return
+ }
+
+ req := &addUserTagsReq{Tags: make([]*userTag, 0, len(userTags))}
+ for userId, tags := range userTags {
+ req.Tags = append(req.Tags, &userTag{
+ UserId: userId,
+ Tags: tags,
+ })
+ }
+
+ if err = a.client.Post(service, commandAddUserTags, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// DeleteUserTags 删除用户标签
+// 管理员给用户删除标签。注意每次最多只能给100个用户删除标签。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45942
+func (a *api) DeleteUserTags(userTags map[string][]string) (err error) {
+ if c := len(userTags); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the tags of user is not set")
+ return
+ } else if c > batchDeleteUserTagsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of tags to be delete cannot exceed %d", batchDeleteUserTagsLimit))
+ return
+ }
+
+ req := &deleteUserTagsReq{Tags: make([]*userTag, 0, len(userTags))}
+ for userId, tags := range userTags {
+ req.Tags = append(req.Tags, &userTag{
+ UserId: userId,
+ Tags: tags,
+ })
+ }
+
+ if err = a.client.Post(service, commandDeleteUserTags, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// DeleteUserAllTags 删除用户所有标签
+// 管理员给用户删除所有标签。注意每次最多只能给100个用户删除所有标签。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/45943
+func (a *api) DeleteUserAllTags(userIds ...string) (err error) {
+ if c := len(userIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the accounts is not set")
+ return
+ } else if c > batchDeleteUserAllTagsUserLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of accounts to be delete cannot exceed %d", batchDeleteUserAllTagsUserLimit))
+ return
+ }
+
+ req := &deleteUserAllTagsReq{UserIds: userIds}
+
+ if err = a.client.Post(service, commandDeleteUserAllTags, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
diff --git a/push/enum.go b/push/enum.go
new file mode 100644
index 0000000..ae47902
--- /dev/null
+++ b/push/enum.go
@@ -0,0 +1,38 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 10:16
+ * @Desc: TODO
+ */
+
+package push
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/enum"
+)
+
+const (
+ // 推送标识
+ PushFlagYes = enum.PushFlagYes // 正常推送
+ PushFlagNo = enum.PushFlagYes // 不离线推送
+
+ // 华为推送通知消息分类
+ HuaWeiImportanceLow = enum.HuaWeiImportanceLow // LOW类消息
+ HuaWeiImportanceNormal = enum.HuaWeiImportanceNormal // NORMAL类消息
+
+ // 华为推送为“打开应用内指定页面”的前提下透传参数行为
+ HuaweiIntentParamAction = enum.HuaweiIntentParamAction // 将透传内容Ext作为Action参数
+ HuaweiIntentParamIntent = enum.HuaweiIntentParamIntent // 将透传内容Ext作为Intent参数
+
+ // VIVO手机推送消息分类
+ VivoClassificationOperation = enum.VivoClassificationOperation // 运营类消息
+ VivoClassificationSystem = enum.VivoClassificationSystem // 系统类消息
+
+ // IOS徽章计数模式
+ BadgeModeNormal = enum.BadgeModeNormal // 本条消息需要计数
+ BadgeModeIgnore = enum.BadgeModeIgnore // 本条消息不需要计数
+
+ // IOS10的推送扩展开关
+ MutableContentNormal = enum.MutableContentNormal // 关闭iOS10的推送扩展
+ MutableContentEnable = enum.MutableContentEnable // 开启iOS10的推送扩展
+)
diff --git a/push/message.go b/push/message.go
new file mode 100644
index 0000000..5760c3c
--- /dev/null
+++ b/push/message.go
@@ -0,0 +1,152 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/3 18:15
+ * @Desc: 推送消息实体
+ */
+
+package push
+
+import (
+ "errors"
+
+ "git.echol.cn/loser/tencent-im/internal/entity"
+)
+
+var errInvalidPushCondition = errors.New("attrs and tags condition cannot be set at the same time")
+
+type Message struct {
+ entity.Message
+ condition *condition
+}
+
+func NewMessage() *Message {
+ return &Message{}
+}
+
+// SetConditionTagsOr 设置标签的或条件(设置会冲掉之前的标签或条件)
+func (m *Message) SetConditionTagsOr(tags ...string) {
+ if m.condition != nil && m.condition.TagsOr != nil {
+ m.condition.TagsOr = m.condition.TagsOr[0:0]
+ }
+
+ m.AddConditionTagsOr(tags...)
+}
+
+// AddConditionTagsOr 添加标签的或条件(添加会累加之前的条件或条件)
+func (m *Message) AddConditionTagsOr(tags ...string) {
+ if m.condition == nil {
+ m.condition = &condition{}
+ }
+ if m.condition.TagsOr == nil {
+ m.condition.TagsOr = make([]string, 0)
+ }
+ m.condition.TagsOr = append(m.condition.TagsOr, tags...)
+}
+
+// SetConditionTagsAnd 设置标签的与条件(设置会冲掉之前的标签与条件)
+func (m *Message) SetConditionTagsAnd(tags ...string) {
+ if m.condition != nil && m.condition.TagsAnd != nil {
+ m.condition.TagsAnd = m.condition.TagsAnd[0:0]
+ }
+
+ m.AddConditionTagsAnd(tags...)
+}
+
+// AddConditionTagsAnd 添加标签的与条件(添加会累加之前的标签与条件)
+func (m *Message) AddConditionTagsAnd(tags ...string) {
+ if m.condition == nil {
+ m.condition = &condition{}
+ }
+ if m.condition.TagsAnd == nil {
+ m.condition.TagsAnd = make([]string, 0)
+ }
+ m.condition.TagsAnd = append(m.condition.TagsAnd, tags...)
+}
+
+// SetConditionAttrsOr 设置属性的或条件(设置会冲掉之前的属性或条件)
+func (m *Message) SetConditionAttrsOr(attrs map[string]interface{}) {
+ if m.condition != nil && m.condition.AttrsOr != nil {
+ m.condition.AttrsOr = make(map[string]interface{})
+ }
+
+ m.AddConditionAttrsOr(attrs)
+}
+
+// AddConditionAttrsOr 添加属性的或条件(添加会累加之前的属性或条件)
+func (m *Message) AddConditionAttrsOr(attrs map[string]interface{}) {
+ if m.condition == nil {
+ m.condition = &condition{}
+ }
+ if m.condition.AttrsOr == nil {
+ m.condition.AttrsOr = make(map[string]interface{})
+ }
+ for k, v := range attrs {
+ m.condition.AttrsOr[k] = v
+ }
+}
+
+// SetConditionAttrsAnd 设置属性的与条件(设置会冲掉之前的属性与条件)
+func (m *Message) SetConditionAttrsAnd(attrs map[string]interface{}) {
+ if m.condition != nil && m.condition.AttrsAnd != nil {
+ m.condition.AttrsAnd = make(map[string]interface{})
+ }
+
+ m.AddConditionAttrsAnd(attrs)
+}
+
+// AddConditionAttrsAnd 添加属性的与条件(添加会累加之前的属性与条件)
+func (m *Message) AddConditionAttrsAnd(attrs map[string]interface{}) {
+ if m.condition == nil {
+ m.condition = &condition{}
+ }
+ if m.condition.AttrsAnd == nil {
+ m.condition.AttrsAnd = make(map[string]interface{})
+ }
+ for k, v := range attrs {
+ m.condition.AttrsAnd[k] = v
+ }
+}
+
+// GetCondition 获取推送条件
+func (m *Message) GetCondition() *condition {
+ return m.condition
+}
+
+// checkError 检测错误
+func (m *Message) checkError() (err error) {
+ if err = m.CheckLifeTimeArgError(); err != nil {
+ return
+ }
+
+ if err = m.CheckBodyArgError(); err != nil {
+ return
+ }
+
+ if err = m.checkConditionArgError(); err != nil {
+ return
+ }
+
+ return
+}
+
+// checkConditionArgError 检测条件参数错误
+func (m *Message) checkConditionArgError() error {
+ hasAttrs, hasTags := false, false
+
+ if m.condition != nil {
+ if m.condition.AttrsAnd != nil || m.condition.AttrsOr != nil {
+ hasAttrs = true
+ }
+
+ if m.condition.TagsAnd != nil || m.condition.TagsOr != nil {
+ hasTags = true
+ }
+ }
+
+ if hasAttrs && hasTags {
+ return errInvalidPushCondition
+ }
+
+ return nil
+}
diff --git a/push/types.go b/push/types.go
new file mode 100644
index 0000000..16dab12
--- /dev/null
+++ b/push/types.go
@@ -0,0 +1,126 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 17:39
+ * @Desc: Push Api Request And Response Type Definition.
+ */
+
+package push
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ // 推送条件
+ condition struct {
+ TagsAnd []string `json:"TagsAnd"` // (选填)标签条件的交集。标签是一个不超过50字节的字符串。注意属性推送和标签推送不可同时作为推送条件。TagsAnd 条件中的标签个数不能超过10个
+ TagsOr []string `json:"TagsOr"` // (选填)标签条件的并集。标签是一个不超过50字节的字符串。注意属性推送和标签推送不可同时作为推送条件。TagsOr 条件中的标签个数不能超过10个
+ AttrsAnd map[string]interface{} `json:"AttrsAnd"` // (选填)属性条件的交集。注意属性推送和标签推送不可同时作为推送条件
+ AttrsOr map[string]interface{} `json:"AttrsOr"` // (选填)属性条件的并集。注意属性推送和标签推送不可同时作为推送条件
+ }
+
+ // 推送(请求)
+ pushMessageReq struct {
+ FromUserId string `json:"From_Account,omitempty"` // (选填)消息推送方帐号
+ Condition *condition `json:"Condition,omitempty"` // (选填)推送条件
+ MsgRandom uint32 `json:"MsgRandom"` // (必填)消息随机数,由随机函数产生
+ MsgBody []*types.MsgBody `json:"MsgBody"` // (必填)消息内容
+ MsgLifeTime int `json:"MsgLifeTime,omitempty"` // (选填)消息离线存储时间,单位秒,最多保存7天(604800秒)。默认为0,表示不离线存储
+ OfflinePushInfo *types.OfflinePushInfo `json:"OfflinePushInfo,omitempty"` // (选填)离线推送信息配置
+ }
+
+ // 推送(响应)
+ pushMessageResp struct {
+ types.ActionBaseResp
+ TaskId string `json:"TaskId"` // 推送任务ID
+ }
+
+ // 设置应用属性名称(请求)
+ setAttrNamesReq struct {
+ AttrNames map[string]string `json:"AttrNames"` // (必填)属性名
+ }
+
+ // 获取应用属性名称(请求)
+ getAttrNamesReq struct {
+ }
+
+ // 获取应用属性名称(响应)
+ getAttrNamesResp struct {
+ types.ActionBaseResp
+ AttrNames map[string]string `json:"AttrNames"` // 属性名
+ }
+
+ // 获取用户属性(请求)
+ getUserAttrsReq struct {
+ UserIds []string `json:"To_Account"` // (必填)目标用户帐号列表
+ }
+
+ // 获取用户属性(响应)
+ getUserAttrsResp struct {
+ types.ActionBaseResp
+ Attrs []*userAttrItem `json:"Attrs"`
+ }
+
+ // 用户属性
+ userAttrItem struct {
+ UserId string `json:"To_Account"` // 用户UserId
+ Attrs map[string]interface{} `json:"Attrs"` // 用户属性
+ }
+
+ // 设置用户属性(请求)
+ setUserAttrsReq struct {
+ Attrs []*userAttrItem `json:"Attrs"` // (必填)用户属性
+ }
+
+ // 删除用户属性(请求)
+ deleteUserAttrsReq struct {
+ Attrs []deleteUserAttr `json:"Attrs"` // (必填)用户属性
+ }
+
+ // 删除的用户属性
+ deleteUserAttr struct {
+ UserId string `json:"To_Account"` // 用户UserId
+ Attrs []string `json:"Attrs"` // 用户属性
+ }
+
+ // 获取用户标签(请求)
+ getUserTagsReq struct {
+ UserIds []string `json:"To_Account"` // (必填)目标用户帐号列表
+ }
+
+ // 获取用户标签(响应)
+ getUserTagsResp struct {
+ types.ActionBaseResp
+ Tags []userTag `json:"Tags"` // 用户标签内容列表
+ }
+
+ // 用户标签
+ userTag struct {
+ UserId string `json:"To_Account"`
+ Tags []string `json:"Tags"`
+ }
+
+ // 添加用户标签(请求)
+ addUserTagsReq struct {
+ Tags []*userTag `json:"Tags"` // (必填)用户标签内容列表
+ }
+
+ // 删除用户标签(请求)
+ deleteUserTagsReq struct {
+ Tags []*userTag `json:"Tags"` // (必填)用户标签内容列表
+ }
+
+ // 删除用户所有标签(请求)
+ deleteUserAllTagsReq struct {
+ UserIds []string `json:"To_Account"`
+ }
+
+ ImageInfo = types.ImageInfo
+ MsgTextContent = types.MsgTextContent
+ MsgFaceContent = types.MsgFaceContent
+ MsgFileContent = types.MsgFileContent
+ MsgImageContent = types.MsgImageContent
+ MsgSoundContent = types.MsgSoundContent
+ MsgVideoContent = types.MsgVideoContent
+ MsgCustomContent = types.MsgCustomContent
+ MsgLocationContent = types.MsgLocationContent
+)
diff --git a/recentcontact/api.go b/recentcontact/api.go
new file mode 100644
index 0000000..146fb35
--- /dev/null
+++ b/recentcontact/api.go
@@ -0,0 +1,148 @@
+/**
+ * @Author: wanglin
+ * @Author: wanglin@vspn.com
+ * @Date: 2021/10/28 16:05
+ * @Desc: TODO
+ */
+
+package recentcontact
+
+import (
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ service = "recentcontact"
+ commandFetchSessions = "get_list"
+ commandDeleteSession = "delete"
+)
+
+type API interface {
+ // FetchSessions 拉取会话列表
+ // 支持分页拉取会话列表
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/62118
+ FetchSessions(arg *FetchSessionsArg) (ret *FetchSessionsRet, err error)
+
+ // PullSessions 续拉取会话列表
+ // 本API是借助"拉取会话列表"API进行扩展实现
+ // 支持分页拉取会话列表
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/62118
+ PullSessions(arg *PullSessionsArg, fn func(ret *FetchSessionsRet)) (err error)
+
+ // DeleteSession 删除单个会话
+ // 删除指定会话,支持同步清理漫游消息。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/62119
+ DeleteSession(fromUserId, toUserId string, SessionType SessionType, isClearRamble ...bool) (err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// FetchSessions 拉取会话列表
+// 支持分页拉取会话列表
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/62118
+func (a *api) FetchSessions(arg *FetchSessionsArg) (ret *FetchSessionsRet, err error) {
+ req := &fetchSessionsReq{
+ UserId: arg.UserId,
+ TimeStamp: arg.TimeStamp,
+ StartIndex: arg.StartIndex,
+ TopTimeStamp: arg.TopTimeStamp,
+ TopStartIndex: arg.TopStartIndex,
+ }
+
+ if arg.IsAllowTopSession {
+ req.AssistFlags += 1 << 0
+ }
+
+ if arg.IsReturnEmptySession {
+ req.AssistFlags += 1 << 1
+ }
+
+ if arg.IsAllowTopSessionPaging {
+ req.AssistFlags += 1 << 2
+ }
+
+ resp := &fetchSessionsResp{}
+
+ if err = a.client.Post(service, commandFetchSessions, req, resp); err != nil {
+ return
+ }
+
+ ret = &FetchSessionsRet{
+ TimeStamp: resp.TimeStamp,
+ StartIndex: resp.StartIndex,
+ TopTimeStamp: resp.TopTimeStamp,
+ TopStartIndex: resp.TopStartIndex,
+ List: resp.Sessions,
+ HasMore: resp.CompleteFlag == 0,
+ }
+
+ return
+}
+
+// PullSessions 续拉取会话列表
+// 本API是借助"拉取会话列表"API进行扩展实现
+// 支持分页拉取会话列表
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/62118
+func (a *api) PullSessions(arg *PullSessionsArg, fn func(ret *FetchSessionsRet)) (err error) {
+ var (
+ ret *FetchSessionsRet
+ req = &FetchSessionsArg{
+ UserId: arg.UserId,
+ IsAllowTopSession: arg.IsAllowTopSession,
+ IsReturnEmptySession: arg.IsReturnEmptySession,
+ IsAllowTopSessionPaging: arg.IsAllowTopSessionPaging,
+ }
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchSessions(req)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ req.TimeStamp = ret.TimeStamp
+ req.StartIndex = ret.StartIndex
+ req.TopTimeStamp = ret.TopTimeStamp
+ req.TopStartIndex = ret.TopStartIndex
+ }
+ }
+
+ return
+}
+
+// DeleteSession 删除单个会话
+// 删除指定会话,支持同步清理漫游消息。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/62119
+func (a *api) DeleteSession(fromUserId, toUserId string, SessionType SessionType, isClearRamble ...bool) (err error) {
+ req := &deleteSessionReq{
+ FromUserId: fromUserId,
+ ToUserId: toUserId,
+ Type: SessionType,
+ }
+
+ if len(isClearRamble) > 0 && isClearRamble[0] {
+ req.ClearRamble = 1
+ }
+
+ if err = a.client.Post(service, commandDeleteSession, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
diff --git a/recentcontact/enum.go b/recentcontact/enum.go
new file mode 100644
index 0000000..97ea4fe
--- /dev/null
+++ b/recentcontact/enum.go
@@ -0,0 +1,16 @@
+/**
+ * @Author: wanglin
+ * @Author: wanglin@vspn.com
+ * @Date: 2021/10/28 17:22
+ * @Desc: TODO
+ */
+
+package recentcontact
+
+// SessionType 会话类型
+type SessionType int
+
+const (
+ SessionTypeC2C SessionType = 1 // C2C 会话
+ SessionTypeG2C SessionType = 2 // G2C 会话
+)
diff --git a/recentcontact/types.go b/recentcontact/types.go
new file mode 100644
index 0000000..84d52aa
--- /dev/null
+++ b/recentcontact/types.go
@@ -0,0 +1,78 @@
+/**
+ * @Author: wanglin
+ * @Author: wanglin@vspn.com
+ * @Date: 2021/10/28 16:11
+ * @Desc: TODO
+ */
+
+package recentcontact
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+// FetchSessionsArg 拉取会话列表(参数)
+type FetchSessionsArg struct {
+ UserId string // (必填)请求拉取该用户的会话列表
+ TimeStamp int // (必填)普通会话的起始时间,第一页填 0
+ StartIndex int // (必填)普通会话的起始位置,第一页填 0
+ TopTimeStamp int // (必填)置顶会话的起始时间,第一页填 0
+ TopStartIndex int // (必填)置顶会话的起始位置,第一页填 0
+ IsAllowTopSession bool // (选填)是否支持置顶会话
+ IsReturnEmptySession bool // (选填)是否返回空会话
+ IsAllowTopSessionPaging bool // (选填)是否支持置顶会话分页
+}
+
+// PullSessionsArg 续拉取会话列表(参数)
+type PullSessionsArg struct {
+ UserId string // (必填)请求拉取该用户的会话列表
+ IsAllowTopSession bool // (选填)是否支持置顶会话
+ IsReturnEmptySession bool // (选填)是否返回空会话
+ IsAllowTopSessionPaging bool // (选填)是否支持置顶会话分页
+}
+
+// FetchSessionsRet 拉取会话列表(返回)
+type FetchSessionsRet struct {
+ TimeStamp int // 普通会话下一页拉取的起始时间,分页拉取时通过请求包的 TimeStamp 字段带给移动通信后台
+ StartIndex int // 普通会话下一页拉取的起始位置,分页拉取时通过请求包的 StartIndex 字段带给移动通信后台
+ TopTimeStamp int // 置顶会话下一页拉取的起始时间,分页拉取时通过请求包的 TopTimeStamp 字段带给移动通信后台
+ TopStartIndex int // 置顶会话下一页拉取的起始位置,分页拉取时通过请求包的 TopStartIndex 字段带给移动通信后台
+ HasMore bool // 是否拉完了数据
+ List []*SessionItem // 会话对象数组
+}
+
+// fetchSessionsReq 拉取会话列表(请求)
+type fetchSessionsReq struct {
+ UserId string `json:"From_Account"` // (必填)请求拉取该用户的会话列表
+ TimeStamp int `json:"TimeStamp"` // (必填)普通会话的起始时间,第一页填 0
+ StartIndex int `json:"StartIndex"` // (必填)普通会话的起始位置,第一页填 0
+ TopTimeStamp int `json:"TopTimeStamp"` // (必填)置顶会话的起始时间,第一页填 0
+ TopStartIndex int `json:"TopStartIndex"` // (必填)置顶会话的起始位置,第一页填 0
+ AssistFlags int `json:"AssistFlags"` // (必填)会话辅助标志位(bit 0 - 是否支持置顶会话;bit 1 - 是否返回空会话;bit 2 - 是否支持置顶会话分页)
+}
+
+// fetchSessionsResp 拉取会话列表(响应)
+type fetchSessionsResp struct {
+ types.ActionBaseResp
+ CompleteFlag int `json:"CompleteFlag"` // 结束标识:1 表示已返回全量会话,0 表示还有会话没拉完
+ TimeStamp int `json:"TimeStamp"` // 普通会话下一页拉取的起始时间,分页拉取时通过请求包的 TimeStamp 字段带给移动通信后台
+ StartIndex int `json:"StartIndex"` // 普通会话下一页拉取的起始位置,分页拉取时通过请求包的 StartIndex 字段带给移动通信后台
+ TopTimeStamp int `json:"TopTimeStamp"` // 置顶会话下一页拉取的起始时间,分页拉取时通过请求包的 TopTimeStamp 字段带给移动通信后台
+ TopStartIndex int `json:"TopStartIndex"` // 置顶会话下一页拉取的起始位置,分页拉取时通过请求包的 TopStartIndex 字段带给移动通信后台
+ Sessions []*SessionItem `json:"SessionItem"` // 会话对象数组
+}
+
+// SessionItem 会话对象
+type SessionItem struct {
+ Type SessionType `json:"Type"` // 会话类型:1 表示 C2C 会话;2 表示 G2C 会话
+ UserId string `json:"To_Account,omitempty"` // C2C 会话才会返回,返回会话方的 UserID
+ GroupId string `json:"GroupId,omitempty"` // G2C 会话才会返回,返回群 ID
+ MsgTime int `json:"MsgTime"` // 会话时间
+ TopFlag int `json:"TopFlag"` // 置顶标记:0 标识普通会话;1 标识置顶会话
+}
+
+// deleteSessionReq 删除单个会话(请求)
+type deleteSessionReq struct {
+ FromUserId string `json:"From_Account"` // (必填)请求删除该 UserID 的会话
+ Type SessionType `json:"type"` // (必填)会话类型:1 表示 C2C 会话;2 表示 G2C 会话
+ ToUserId string `json:"To_Account"` // (必填)待删除的会话的 UserID
+ ClearRamble int `json:"ClearRamble,omitempty"` // (选填)是否清理漫游消息:1 表示清理漫游消息;0 表示不清理漫游消息
+}
diff --git a/sns/api.go b/sns/api.go
new file mode 100644
index 0000000..2f31339
--- /dev/null
+++ b/sns/api.go
@@ -0,0 +1,901 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/27 20:45
+ * @Desc: 关系链管理
+ */
+
+package sns
+
+import (
+ "fmt"
+
+ "git.echol.cn/loser/tencent-im/internal/core"
+ "git.echol.cn/loser/tencent-im/internal/enum"
+ "git.echol.cn/loser/tencent-im/internal/types"
+)
+
+const (
+ service = "sns"
+ commandAddFriend = "friend_add"
+ commandImportFriend = "friend_import"
+ commandUpdateFriend = "friend_update"
+ commandDeleteFriend = "friend_delete"
+ commandDeleteAllFriend = "friend_delete_all"
+ commandCheckFriend = "friend_check"
+ commandGetFriend = "friend_get_list"
+ commandFetchFriend = "friend_get"
+ commandAddBlackList = "black_list_add"
+ commandDeleteBlackList = "black_list_delete"
+ commandGetBlackList = "black_list_get"
+ commandCheckBlackList = "black_list_check"
+ commandAddGroup = "group_add"
+ commandDeleteGroup = "group_delete"
+ commandGetGroup = "group_get"
+
+ batchCheckFriendsLimit = 100 // 批量校验好友限制
+ batchGetFriendsLimit = 100 // 批量获取好友限制
+ batchAddBlacklistLimit = 1000 // 批量添加黑名单限制
+ batchDeleteBlacklistLimit = 1000 // 批量删除黑名单限制
+ batchCheckBlacklistLimit = 1000 // 批量校验黑名单限制
+ batchAddGroupsLimit = 100 // 批量添加分组限制
+ batchJoinGroupsLimit = 1000 // 批量加入群组账号限制
+ batchDeleteGroupsLimit = 100 // 批量删除分组限制
+ batchGetGroupsLimit = 100 // 批量获取分组限制
+)
+
+type API interface {
+ // AddFriend 添加单个好友
+ // 本方法拓展于“添加多个好友(AddFriends)”方法。
+ // 添加好友,仅支持添加单个好友
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1643
+ AddFriend(userId string, isBothAdd, isForceAdd bool, friend *Friend) (err error)
+
+ // AddFriends 添加多个好友
+ // 添加好友,支持批量添加好友
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1643
+ AddFriends(userId string, isBothAdd, isForceAdd bool, friends ...*Friend) (results []*Result, err error)
+
+ // ImportFriend 导入单个好友
+ // 本方法拓展于“添加多个好友(ImportFriends)”方法。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/8301
+ ImportFriend(userId string, friend *Friend) (err error)
+
+ // ImportFriends 导入多个好友
+ // 支持批量导入单向好友。
+ // 往同一个用户导入好友时建议采用批量导入的方式,避免并发写导致的写冲突。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/8301
+ ImportFriends(userId string, friends ...*Friend) (results []*Result, err error)
+
+ // UpdateFriend 更新单个好友
+ // 本方法拓展于“更新多个好友(UpdateFriends)”方法。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/12525
+ UpdateFriend(userId string, friend *Friend) (err error)
+
+ // UpdateFriends 更新多个好友
+ // 支持批量更新同一用户的多个好友的关系链数据。
+ // 更新一个用户多个好友时,建议采用批量方式,避免并发写导致的写冲突。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/12525
+ UpdateFriends(userId string, friends ...*Friend) (results []*Result, err error)
+
+ // DeleteFriend 删除单个好友
+ // 本方法拓展于“删除多个好友(DeleteFriends)”方法。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1644
+ DeleteFriend(userId string, isBothDelete bool, deletedUserId string) (err error)
+
+ // DeleteFriends 删除多个好友
+ // 删除好友,支持单向删除好友和双向删除好友。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1644
+ DeleteFriends(userId string, isBothDelete bool, deletedUserIds ...string) (results []*Result, err error)
+
+ // DeleteAllFriends 删除所有好友
+ // 清除指定用户的标配好友数据和自定义好友数据。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1645
+ DeleteAllFriends(userId string, deleteType ...DeleteType) (err error)
+
+ // CheckFriend 校验单个好友
+ // 本方法拓展于“校验多个好友(CheckFriends)”方法。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1646
+ CheckFriend(userId string, checkType CheckType, checkedUserId string) (relation string, err error)
+
+ // CheckFriends 校验多个好友
+ // 支持批量校验好友关系。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1646
+ CheckFriends(userId string, checkType CheckType, checkedUserIds ...string) (results []*CheckResult, err error)
+
+ // GetFriend 拉取单个指定好友
+ // 本方法拓展于“拉取多个指定好友(GetFriends)”方法。
+ // 支持拉取指定好友的好友数据和资料数据。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/8609
+ GetFriend(userId string, tagList []string, friendUserId string) (friend *Friend, err error)
+
+ // GetFriends 拉取多个指定好友
+ // 支持拉取指定好友的好友数据和资料数据。
+ // 建议每次拉取的好友数不超过100,避免因数据量太大导致回包失败。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/8609
+ GetFriends(userId string, tagList []string, friendUserIds ...string) (friends []*Friend, err error)
+
+ // FetchFriends 拉取好友
+ // 分页拉取全量好友数据。
+ // 不支持资料数据的拉取。
+ // 不需要指定请求拉取的字段,默认返回全量的标配好友数据和自定义好友数据。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1647
+ FetchFriends(userId string, startIndex int, sequence ...int) (ret *FetchFriendsRet, err error)
+
+ // PullFriends 续拉取好友
+ // 本API是借助"拉取好友"API进行扩展实现
+ // 分页拉取全量好友数据。
+ // 不支持资料数据的拉取。
+ // 不需要指定请求拉取的字段,默认返回全量的标配好友数据和自定义好友数据。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/1647
+ PullFriends(userId string, fn func(ret *FetchFriendsRet)) (err error)
+
+ // AddBlacklist 添加黑名单
+ // 添加黑名单,支持批量添加黑名单。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/3718
+ AddBlacklist(userId string, blackedUserIds ...string) (results []*Result, err error)
+
+ // DeleteBlacklist 删除黑名单
+ // 删除指定黑名单。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/3719
+ DeleteBlacklist(userId string, deletedUserIds ...string) (results []*Result, err error)
+
+ // FetchBlacklist 拉取黑名单
+ // 支持分页拉取所有黑名单。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/3722
+ FetchBlacklist(userId string, maxLimited int, startIndexAndSequence ...int) (ret *FetchBlacklistRet, err error)
+
+ // PullBlacklist 拉取黑名单
+ // 本API是借助"拉取黑名单"API进行扩展实现
+ // 支持分页拉取所有黑名单。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/3722
+ PullBlacklist(userId string, maxLimited int, fn func(ret *FetchBlacklistRet)) (err error)
+
+ // CheckBlacklist 校验黑名单
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/3725
+ CheckBlacklist(userId string, checkType BlacklistCheckType, checkedUserIds ...string) (results []*CheckResult, err error)
+
+ // AddGroups 添加分组
+ // 添加分组,支持批量添加分组,并将指定好友加入到新增分组中。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/10107
+ AddGroups(userId string, groupNames []string, joinedUserIds ...[]string) (currentSequence int, results []*Result, err error)
+
+ // DeleteGroups 删除分组
+ // 删除指定分组。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/10108
+ DeleteGroups(userId string, groupNames ...string) (currentSequence int, err error)
+
+ // GetGroups 拉取分组
+ // 拉取分组,支持指定分组以及拉取分组下的好友列表。
+ // 点击查看详细文档:
+ // https://cloud.tencent.com/document/product/269/54763
+ GetGroups(userId string, lastSequence int, isGetFriends bool, groupNames ...string) (currentSequence int, results []*GroupResult, err error)
+}
+
+type api struct {
+ client core.Client
+}
+
+func NewAPI(client core.Client) API {
+ return &api{client: client}
+}
+
+// AddFriend 添加单个好友
+// 本方法拓展于“添加多个好友(AddFriends)”方法。
+// 添加好友,仅支持添加单个好友
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1643
+func (a *api) AddFriend(userId string, isBothAdd, isForceAdd bool, friend *Friend) (err error) {
+ var results []*Result
+
+ if results, err = a.AddFriends(userId, isBothAdd, isForceAdd, friend); err != nil {
+ return
+ }
+
+ for _, result := range results {
+ if result.UserId == friend.GetUserId() && result.ResultCode != enum.SuccessCode {
+ return core.NewError(result.ResultCode, result.ResultInfo)
+ }
+ }
+
+ return
+}
+
+// AddFriends 添加多个好友
+// 添加好友,支持批量添加好友
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1643
+func (a *api) AddFriends(userId string, isBothAdd, isForceAdd bool, friends ...*Friend) (results []*Result, err error) {
+ if len(friends) == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the friends is not set")
+ return
+ }
+
+ req := &addFriendsReq{UserId: userId, Friends: make([]*addFriendItem, 0, len(friends))}
+
+ for _, friend := range friends {
+ if err = friend.checkError(); err != nil {
+ return
+ }
+
+ item := new(addFriendItem)
+ item.UserId = friend.GetUserId()
+ item.Remark, _ = friend.GetRemark()
+ item.AddWording, _ = friend.GetAddWording()
+ item.AddSource, _ = friend.GetSrcAddSource()
+ if groups, exist := friend.GetGroup(); exist {
+ item.GroupName = groups[0]
+ }
+
+ req.Friends = append(req.Friends, item)
+ }
+
+ if isBothAdd {
+ req.AddType = AddTypeBoth
+ } else {
+ req.AddType = AddTypeSingle
+ }
+
+ if isForceAdd {
+ req.ForceAddFlags = ForceAddYes
+ } else {
+ req.ForceAddFlags = ForceAddNo
+ }
+
+ resp := &addFriendsResp{}
+
+ if err = a.client.Post(service, commandAddFriend, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// ImportFriend 导入单个好友
+// 本方法拓展于“添加多个好友(ImportFriends)”方法。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/8301
+func (a *api) ImportFriend(userId string, friend *Friend) (err error) {
+ var results []*Result
+
+ if results, err = a.ImportFriends(userId, friend); err != nil {
+ return
+ }
+
+ for _, result := range results {
+ if result.UserId == friend.GetUserId() && result.ResultCode != enum.SuccessCode {
+ return core.NewError(result.ResultCode, result.ResultInfo)
+ }
+ }
+
+ return
+}
+
+// ImportFriends 导入多个好友
+// 支持批量导入单向好友。
+// 往同一个用户导入好友时建议采用批量导入的方式,避免并发写导致的写冲突。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/8301
+func (a *api) ImportFriends(userId string, friends ...*Friend) (results []*Result, err error) {
+ if len(friends) == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the friends is not set")
+ return
+ }
+
+ req := &importFriendsReq{UserId: userId, Friends: make([]*importFriendItem, 0, len(friends))}
+
+ for _, friend := range friends {
+ if err = friend.checkError(); err != nil {
+ return
+ }
+
+ item := new(importFriendItem)
+ item.UserId = friend.GetUserId()
+ item.Remark, _ = friend.GetRemark()
+ item.AddWording, _ = friend.GetAddWording()
+ item.AddTime, _ = friend.GetAddTime()
+ item.RemarkTime, _ = friend.GetRemarkTime()
+ item.AddSource, _ = friend.GetSrcAddSource()
+ item.GroupName, _ = friend.GetGroup()
+
+ if customAttrs := friend.GetSNSCustomAttrs(); len(customAttrs) > 0 {
+ item.CustomData = make([]*types.TagPair, 0, len(customAttrs))
+ for k, v := range customAttrs {
+ item.CustomData = append(item.CustomData, &types.TagPair{
+ Tag: k,
+ Value: v,
+ })
+ }
+ }
+
+ req.Friends = append(req.Friends, item)
+ }
+
+ resp := &importFriendsResp{}
+
+ if err = a.client.Post(service, commandImportFriend, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// UpdateFriend 更新单个好友
+// 本方法拓展于“更新多个好友(UpdateFriends)”方法。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/12525
+func (a *api) UpdateFriend(userId string, friend *Friend) (err error) {
+ var results []*Result
+
+ if results, err = a.UpdateFriends(userId, friend); err != nil {
+ return
+ }
+
+ for _, result := range results {
+ if result.UserId == friend.GetUserId() && result.ResultCode != enum.SuccessCode {
+ return core.NewError(result.ResultCode, result.ResultInfo)
+ }
+ }
+
+ return
+}
+
+// UpdateFriends 更新多个好友
+// 支持批量更新同一用户的多个好友的关系链数据。
+// 更新一个用户多个好友时,建议采用批量方式,避免并发写导致的写冲突。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/12525
+func (a *api) UpdateFriends(userId string, friends ...*Friend) (results []*Result, err error) {
+ if len(friends) == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the friends is not set")
+ return
+ }
+
+ req := &updateFriendsReq{UserId: userId, Friends: make([]*updateFriendItem, 0, len(friends))}
+
+ for _, friend := range friends {
+ item := new(updateFriendItem)
+ item.UserId = friend.GetUserId()
+
+ for k, v := range friend.GetSNSAttrs() {
+ switch k {
+ case FriendAttrAddSource, FriendAttrAddTime, FriendAttrRemarkTime, FriendAttrAddWording:
+ default:
+ item.Attrs = append(item.Attrs, &types.TagPair{
+ Tag: k,
+ Value: v,
+ })
+ }
+ }
+
+ for k, v := range friend.GetSNSCustomAttrs() {
+ item.Attrs = append(item.Attrs, &types.TagPair{
+ Tag: k,
+ Value: v,
+ })
+ }
+
+ req.Friends = append(req.Friends, item)
+ }
+
+ resp := &updateFriendsResp{}
+
+ if err = a.client.Post(service, commandUpdateFriend, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// DeleteFriend 删除单个好友
+// 本方法拓展于“删除多个好友(DeleteFriends)”方法。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1644
+func (a *api) DeleteFriend(userId string, isBothDelete bool, deletedUserId string) (err error) {
+ var results []*Result
+
+ if results, err = a.DeleteFriends(userId, isBothDelete, deletedUserId); err != nil {
+ return
+ }
+
+ if results != nil && len(results) > 0 {
+ for _, result := range results {
+ if result.UserId == deletedUserId && result.ResultCode != enum.SuccessCode {
+ return core.NewError(result.ResultCode, result.ResultInfo)
+ }
+ }
+ }
+
+ return
+}
+
+// DeleteFriends 删除多个好友
+// 删除好友,支持单向删除好友和双向删除好友。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1644
+func (a *api) DeleteFriends(userId string, isBothDelete bool, deletedUserIds ...string) (results []*Result, err error) {
+ req := &deleteFriendsReq{UserId: userId, DeletedUserIds: deletedUserIds}
+ resp := &deleteFriendsResp{}
+
+ if isBothDelete {
+ req.DeleteType = DeleteTypeBoth
+ } else {
+ req.DeleteType = DeleteTypeSingle
+ }
+
+ if err = a.client.Post(service, commandDeleteFriend, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// DeleteAllFriends 删除所有好友
+// 清除指定用户的标配好友数据和自定义好友数据。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1645
+func (a *api) DeleteAllFriends(userId string, deleteType ...DeleteType) (err error) {
+ req := &deleteAllFriendsReq{UserId: userId}
+
+ if len(deleteType) > 0 {
+ req.DeleteType = deleteType[0]
+ } else {
+ req.DeleteType = DeleteTypeSingle
+ }
+
+ if err = a.client.Post(service, commandDeleteAllFriend, req, &types.ActionBaseResp{}); err != nil {
+ return
+ }
+
+ return
+}
+
+// CheckFriend 校验单个好友
+// 本方法拓展于“校验多个好友(CheckFriends)”方法。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1646
+func (a *api) CheckFriend(userId string, checkType CheckType, checkedUserId string) (relation string, err error) {
+ var results []*CheckResult
+
+ if results, err = a.CheckFriends(userId, checkType, checkedUserId); err != nil {
+ return
+ }
+
+ if results != nil && len(results) > 0 {
+ for _, result := range results {
+ if result.UserId == checkedUserId {
+ if result.ResultCode != enum.SuccessCode {
+ err = core.NewError(result.ResultCode, result.ResultInfo)
+ return
+ }
+
+ relation = result.Relation
+ return
+ }
+ }
+ }
+
+ return
+}
+
+// CheckFriends 校验多个好友
+// 支持批量校验好友关系。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1646
+func (a *api) CheckFriends(userId string, checkType CheckType, checkedUserIds ...string) (results []*CheckResult, err error) {
+ if c := len(checkedUserIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the accounts is not set")
+ return
+ } else if c > batchCheckFriendsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of checked accounts cannot exceed %d", batchCheckFriendsLimit))
+ return
+ }
+
+ req := &checkFriendsReq{UserId: userId, CheckedUserIds: checkedUserIds, CheckType: checkType}
+ resp := &checkFriendsResp{}
+
+ if err = a.client.Post(service, commandCheckFriend, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// GetFriend 拉取单个指定好友
+// 本方法拓展于“拉取多个指定好友(GetFriends)”方法。
+// 支持拉取指定好友的好友数据和资料数据。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/8609
+func (a *api) GetFriend(userId string, tagList []string, friendUserId string) (friend *Friend, err error) {
+ var friends []*Friend
+
+ if friends, err = a.GetFriends(userId, tagList, friendUserId); err != nil {
+ return
+ }
+
+ if len(friends) > 0 {
+ friend = friends[0]
+ }
+
+ return
+}
+
+// GetFriends 拉取多个指定好友
+// 支持拉取指定好友的好友数据和资料数据。
+// 建议每次拉取的好友数不超过100,避免因数据量太大导致回包失败。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/8609
+func (a *api) GetFriends(userId string, tagList []string, friendUserIds ...string) (friends []*Friend, err error) {
+ if c := len(friendUserIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the account of friends is not set")
+ return
+ } else if c > batchGetFriendsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of friend's account cannot exceed %d", batchGetFriendsLimit))
+ return
+ }
+
+ req := &getFriendsReq{UserId: userId, FriendUserIds: friendUserIds}
+ resp := &getFriendsResp{}
+
+ for _, tag := range tagList {
+ switch tag {
+ case FriendAttrRemarkTime:
+ default:
+ req.TagList = append(req.TagList, tag)
+ }
+ }
+
+ if err = a.client.Post(service, commandGetFriend, req, resp); err != nil {
+ return
+ }
+
+ friends = make([]*Friend, 0, len(resp.Friends))
+
+ for _, item := range resp.Friends {
+ friend := NewFriend(item.UserId)
+ friend.SetError(item.ResultCode, item.ResultInfo)
+ for _, v := range item.Profiles {
+ friend.SetAttr(v.Tag, v.Value)
+ }
+ friends = append(friends, friend)
+ }
+
+ return
+}
+
+// FetchFriends 拉取好友
+// 分页拉取全量好友数据。
+// 不支持资料数据的拉取。
+// 不需要指定请求拉取的字段,默认返回全量的标配好友数据和自定义好友数据。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1647
+func (a *api) FetchFriends(userId string, startIndex int, sequence ...int) (ret *FetchFriendsRet, err error) {
+ req := &fetchFriendsReq{UserId: userId, StartIndex: startIndex}
+ resp := &fetchFriendsResp{}
+
+ if len(sequence) > 0 {
+ req.StandardSequence = sequence[0]
+ }
+
+ if len(sequence) > 1 {
+ req.CustomSequence = sequence[1]
+ }
+
+ if err = a.client.Post(service, commandFetchFriend, req, resp); err != nil {
+ return
+ }
+
+ ret = &FetchFriendsRet{
+ StandardSequence: resp.StandardSequence,
+ CustomSequence: resp.CustomSequence,
+ StartIndex: resp.NextStartIndex,
+ Total: resp.FriendNum,
+ HasMore: resp.CompleteFlag == 0,
+ List: make([]*Friend, 0, len(resp.Friends)),
+ }
+
+ for _, item := range resp.Friends {
+ friend := NewFriend(item.UserId)
+ for _, v := range item.Values {
+ friend.SetAttr(v.Tag, v.Value)
+ }
+ ret.List = append(ret.List, friend)
+ }
+
+ return
+}
+
+// PullFriends 续拉取好友
+// 本API是借助"拉取好友"API进行扩展实现
+// 分页拉取全量好友数据。
+// 不支持资料数据的拉取。
+// 不需要指定请求拉取的字段,默认返回全量的标配好友数据和自定义好友数据。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/1647
+func (a *api) PullFriends(userId string, fn func(ret *FetchFriendsRet)) (err error) {
+ var (
+ ret *FetchFriendsRet
+ startIndex int
+ standardSequence int
+ customSequence int
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchFriends(userId, startIndex, standardSequence, customSequence)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ startIndex = ret.StartIndex
+ standardSequence = ret.StandardSequence
+ customSequence = ret.CustomSequence
+ }
+ }
+
+ return
+}
+
+// AddBlacklist 添加黑名单
+// 添加黑名单,支持批量添加黑名单。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/3718
+func (a *api) AddBlacklist(userId string, blackedUserIds ...string) (results []*Result, err error) {
+ if c := len(blackedUserIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the blacked accounts is not set")
+ return
+ } else if c > batchAddBlacklistLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of blacked accounts cannot exceed %d", batchAddBlacklistLimit))
+ return
+ }
+
+ req := &addBlacklistReq{UserId: userId, BlackedUserIds: blackedUserIds}
+ resp := &addBlacklistResp{}
+
+ if err = a.client.Post(service, commandAddBlackList, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// DeleteBlacklist 删除黑名单
+// 删除指定黑名单。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/3719
+func (a *api) DeleteBlacklist(userId string, deletedUserIds ...string) (results []*Result, err error) {
+ if c := len(deletedUserIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the deleted accounts is not set")
+ return
+ } else if c > batchDeleteBlacklistLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of deleted accounts cannot exceed %d", batchDeleteBlacklistLimit))
+ return
+ }
+
+ req := &deleteBlacklistReq{UserId: userId, DeletedUserIds: deletedUserIds}
+ resp := &deleteBlacklistResp{}
+
+ if err = a.client.Post(service, commandDeleteBlackList, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// FetchBlacklist 拉取黑名单
+// 支持分页拉取所有黑名单。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/3722
+func (a *api) FetchBlacklist(userId string, maxLimited int, startIndexAndSequence ...int) (ret *FetchBlacklistRet, err error) {
+ req := &fetchBlacklistReq{UserId: userId, MaxLimited: maxLimited}
+
+ if len(startIndexAndSequence) > 0 {
+ req.StartIndex = startIndexAndSequence[0]
+ }
+
+ if len(startIndexAndSequence) > 1 {
+ req.LastSequence = startIndexAndSequence[1]
+ }
+
+ resp := &fetchBlacklistResp{}
+
+ if err = a.client.Post(service, commandGetBlackList, req, resp); err != nil {
+ return
+ }
+
+ ret = &FetchBlacklistRet{
+ StartIndex: resp.StartIndex,
+ StandardSequence: resp.CurrentSequence,
+ List: resp.Blacklists,
+ HasMore: resp.StartIndex != 0,
+ }
+
+ return
+}
+
+// PullBlacklist 拉取黑名单
+// 本API是借助"拉取黑名单"API进行扩展实现
+// 支持分页拉取所有黑名单。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/3722
+func (a *api) PullBlacklist(userId string, maxLimited int, fn func(ret *FetchBlacklistRet)) (err error) {
+ var (
+ ret *FetchBlacklistRet
+ startIndex = 0
+ standardSequence = 0
+ )
+
+ for ret == nil || ret.HasMore {
+ ret, err = a.FetchBlacklist(userId, maxLimited, startIndex, standardSequence)
+ if err != nil {
+ return
+ }
+
+ fn(ret)
+
+ if ret.HasMore {
+ startIndex = ret.StartIndex
+ standardSequence = ret.StandardSequence
+ }
+ }
+
+ return
+}
+
+// CheckBlacklist 校验黑名单
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/3725
+func (a *api) CheckBlacklist(userId string, checkType BlacklistCheckType, checkedUserIds ...string) (results []*CheckResult, err error) {
+ if c := len(checkedUserIds); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the checked accounts is not set")
+ return
+ } else if c > batchCheckBlacklistLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of checked accounts cannot exceed %d", batchCheckBlacklistLimit))
+ return
+ }
+
+ req := &checkBlacklistReq{UserId: userId, CheckedUserIds: checkedUserIds, CheckType: checkType}
+ resp := &checkBlacklistResp{}
+
+ if err = a.client.Post(service, commandCheckBlackList, req, resp); err != nil {
+ return
+ }
+
+ results = resp.Results
+
+ return
+}
+
+// AddGroups 添加分组
+// 添加分组,支持批量添加分组,并将指定好友加入到新增分组中。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/10107
+func (a *api) AddGroups(userId string, groupNames []string, joinedUserIds ...[]string) (currentSequence int, results []*Result, err error) {
+ if c := len(groupNames); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the added groups is not set")
+ return
+ } else if c > batchAddGroupsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of added groups cannot exceed %d", batchAddGroupsLimit))
+ return
+ }
+
+ req := &addGroupsReq{UserId: userId, GroupNames: groupNames}
+
+ if len(joinedUserIds) > 0 {
+ if c := len(joinedUserIds[0]); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the added groups is not set")
+ return
+ } else if c > batchJoinGroupsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of accounts joining the group cannot exceed %d", batchJoinGroupsLimit))
+ return
+ }
+
+ req.JoinedUserIds = joinedUserIds[0]
+ }
+
+ resp := &addGroupsResp{}
+
+ if err = a.client.Post(service, commandAddGroup, req, resp); err != nil {
+ return
+ }
+
+ currentSequence = resp.CurrentSequence
+ results = resp.Results
+
+ return
+}
+
+// DeleteGroups 删除分组
+// 删除指定分组。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/10108
+func (a *api) DeleteGroups(userId string, groupNames ...string) (currentSequence int, err error) {
+ if c := len(groupNames); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the deleted groups is not set")
+ return
+ } else if c > batchDeleteGroupsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of deleted groups cannot exceed %d", batchDeleteGroupsLimit))
+ return
+ }
+
+ req := &deleteGroupsReq{UserId: userId, GroupNames: groupNames}
+ resp := &deleteGroupsResp{}
+
+ if err = a.client.Post(service, commandDeleteGroup, req, resp); err != nil {
+ return
+ }
+
+ currentSequence = resp.CurrentSequence
+
+ return
+}
+
+// GetGroups 拉取分组
+// 拉取分组,支持指定分组以及拉取分组下的好友列表。
+// 点击查看详细文档:
+// https://cloud.tencent.com/document/product/269/54763
+func (a *api) GetGroups(userId string, lastSequence int, isGetFriends bool, groupNames ...string) (currentSequence int, results []*GroupResult, err error) {
+ if c := len(groupNames); c == 0 {
+ err = core.NewError(enum.InvalidParamsCode, "the gotten groups is not set")
+ return
+ } else if c > batchGetGroupsLimit {
+ err = core.NewError(enum.InvalidParamsCode, fmt.Sprintf("the number of gotten groups cannot exceed %d", batchGetGroupsLimit))
+ return
+ }
+
+ req := &getGroupsReq{UserId: userId, LastSequence: lastSequence, GroupNames: groupNames}
+ resp := &getGroupsResp{}
+
+ if isGetFriends {
+ req.NeedFriend = NeedFriendYes
+ } else {
+ req.NeedFriend = NeedFriendNo
+ }
+
+ if err = a.client.Post(service, commandGetGroup, req, resp); err != nil {
+ return
+ }
+
+ currentSequence = resp.CurrentSequence
+ results = resp.Results
+
+ return
+}
diff --git a/sns/enum.go b/sns/enum.go
new file mode 100644
index 0000000..a3b729d
--- /dev/null
+++ b/sns/enum.go
@@ -0,0 +1,90 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/9/7 14:40
+ * @Desc: TODO
+ */
+
+package sns
+
+import "git.echol.cn/loser/tencent-im/internal/enum"
+
+type (
+ // AddType 添加类型
+ AddType string
+
+ // DeleteType 删除类型
+ DeleteType string
+
+ // CheckType 校验模式
+ CheckType string
+
+ // ForceAddType 强制添加类型
+ ForceAddType int
+
+ // BlacklistCheckType 黑名单校验模式
+ BlacklistCheckType string
+
+ // NeedFriendType 是否需要拉取分组下的User列表
+ NeedFriendType string
+)
+
+const (
+ // 标准资料字段
+ StandardAttrNickname = enum.StandardAttrNickname // 昵称
+ StandardAttrGender = enum.StandardAttrGender // 性别
+ StandardAttrBirthday = enum.StandardAttrBirthday // 生日
+ StandardAttrLocation = enum.StandardAttrLocation // 所在地
+ StandardAttrSignature = enum.StandardAttrSignature // 个性签名
+ StandardAttrAllowType = enum.StandardAttrAllowType // 加好友验证方式
+ StandardAttrLanguage = enum.StandardAttrLanguage // 语言
+ StandardAttrAvatar = enum.StandardAttrAvatar // 头像URL
+ StandardAttrMsgSettings = enum.StandardAttrMsgSettings // 消息设置
+ StandardAttrAdminForbidType = enum.StandardAttrAdminForbidType // 管理员禁止加好友标识
+ StandardAttrLevel = enum.StandardAttrLevel // 等级
+ StandardAttrRole = enum.StandardAttrRole // 角色
+
+ // 好友属性
+ FriendAttrAddSource = "Tag_SNS_IM_AddSource" // 添加源
+ FriendAttrRemark = "Tag_SNS_IM_Remark" // 备注
+ FriendAttrGroup = "Tag_SNS_IM_Group" // 分组
+ FriendAttrAddWording = "Tag_SNS_IM_AddWording" // 附言信息
+ FriendAttrAddTime = "Tag_SNS_IM_AddTime" // 添加时间
+ FriendAttrRemarkTime = "Tag_SNS_IM_RemarkTime" // 备注时间
+
+ // 添加类型
+ AddTypeSingle AddType = "Add_Type_Single" // 单向添加
+ AddTypeBoth AddType = "Add_Type_Both" // 双向添加
+
+ // 删除类型
+ DeleteTypeSingle DeleteType = "Delete_Type_Single" // 单向删除
+ DeleteTypeBoth DeleteType = "Delete_Type_Both" // 双向删除
+
+ // 校验模式
+ CheckTypeSingle CheckType = "CheckResult_Type_Single" // 单向校验好友关系
+ CheckTypeBoth CheckType = "CheckResult_Type_Both" // 双向校验好友关系
+
+ // 黑名单校验模式
+ BlacklistCheckTypeSingle BlacklistCheckType = "BlackCheckResult_Type_Single" // 单向校验黑名单关系
+ BlacklistCheckTypeBoth BlacklistCheckType = "BlackCheckResult_Type_Both" // 双向校验黑名单关系
+
+ // 强制加好友类型
+ ForceAddYes ForceAddType = 1 // 强制加好友
+ ForceAddNo ForceAddType = 0 // 常规加好友
+
+ // 是否需要拉取分组下的 User 列表
+ NeedFriendYes NeedFriendType = "Need_Friend_Type_Yes" // 需要拉取
+ NeedFriendNo NeedFriendType = "Need_Friend_Type_No" // 不需要拉取
+
+ // 好友关系结果
+ CheckResultTypeNoRelation = "CheckResult_Type_NoRelation" // From_Account 的好友表中没有 To_Account,但无法确定 To_Account 的好友表中是否有 From_Account
+ CheckResultTypeAWithB = "CheckResult_Type_AWithB" // From_Account 的好友表中有 To_Account,但无法确定 To_Account 的好友表中是否有 From_Account
+ CheckResultTypeBWithA = "CheckResult_Type_BWithA" // From_Account 的好友表中没有 To_Account,但 To_Account 的好友表中有 From_Account
+ CheckResultTypeBothWay = "CheckResult_Type_BothWay" // From_Account 的好友表中有 To_Account,To_Account 的好友表中也有 From_Account
+
+ // 黑名单关系结果
+ BlackCheckResultTypeNO = "BlackCheckResult_Type_NO" // From_Account 的黑名单中没有 To_Account,但无法确定 To_Account 的黑名单中是否有 From_Account
+ BlackCheckResultTypeAWithB = "BlackCheckResult_Type_AWithB" // From_Account 的黑名单中有 To_Account,但无法确定 To_Account 的黑名单中是否有 From_Account
+ BlackCheckResultTypeBWithA = "BlackCheckResult_Type_BWithA" // From_Account 的黑名单中没有 To_Account,但 To_Account 的黑名单中有 From_Account
+ BlackCheckResultTypeBothWay = "BlackCheckResult_Type_BothWay" // From_Account 的黑名单中有 To_Account,To_Account 的黑名单中也有 From_Account
+)
diff --git a/sns/friend.go b/sns/friend.go
new file mode 100644
index 0000000..6a2fc4d
--- /dev/null
+++ b/sns/friend.go
@@ -0,0 +1,206 @@
+/**
+ * @Author: Echo
+ * @Email:1711788888@qq.com
+ * @Date: 2021/8/30 2:55 下午
+ * @Desc: 好友关系
+ */
+
+package sns
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "git.echol.cn/loser/tencent-im/internal/entity"
+)
+
+var (
+ errNotSetAccount = errors.New("the friend's account is not set")
+ errNotSetAddSource = errors.New("the friend's add source is not set")
+)
+
+type Friend struct {
+ entity.User
+ customAttrs map[string]interface{}
+}
+
+func NewFriend(userId ...string) *Friend {
+ f := new(Friend)
+ f.customAttrs = make(map[string]interface{})
+
+ if len(userId) > 0 {
+ f.SetUserId(userId[0])
+ }
+
+ return f
+}
+
+// SetAddSource 设置添加来源
+func (f *Friend) SetAddSource(addSource string) {
+ f.SetAttr(FriendAttrAddSource, "AddSource_Type_"+addSource)
+}
+
+// GetAddSource 获取添加来源
+func (f *Friend) GetAddSource() (addSource string, exist bool) {
+ var v interface{}
+ if v, exist = f.GetAttr(FriendAttrAddSource); exist {
+ addSource = strings.TrimLeft(v.(string), "AddSource_Type_")
+ }
+
+ return
+}
+
+// GetSrcAddSource 获取添加来源(原始的)
+func (f *Friend) GetSrcAddSource() (addSource string, exist bool) {
+ var v interface{}
+ if v, exist = f.GetAttr(FriendAttrAddSource); exist {
+ addSource = v.(string)
+ }
+
+ return
+}
+
+// SetRemark 设置备注
+func (f *Friend) SetRemark(remark string) {
+ f.SetAttr(FriendAttrRemark, remark)
+}
+
+// GetRemark 获取备注
+func (f *Friend) GetRemark() (remark string, exist bool) {
+ var v interface{}
+ if v, exist = f.GetAttr(FriendAttrRemark); exist {
+ remark = v.(string)
+ }
+
+ return
+}
+
+// SetGroup 设置分组
+func (f *Friend) SetGroup(groupName ...string) {
+ f.SetAttr(FriendAttrGroup, groupName)
+}
+
+// GetGroup 获取分组
+func (f *Friend) GetGroup() (groups []string, exist bool) {
+ var v interface{}
+ if v, exist = f.GetAttr(FriendAttrGroup); exist && v != nil {
+ if vv, ok := v.([]interface{}); ok {
+ for _, group := range vv {
+ groups = append(groups, group.(string))
+ }
+ }
+ }
+
+ return
+}
+
+// SetAddWording 设置形成好友关系时的附言信息
+func (f *Friend) SetAddWording(addWording string) {
+ f.SetAttr(FriendAttrAddWording, addWording)
+}
+
+// GetAddWording 获取形成好友关系时的附言信息
+func (f *Friend) GetAddWording() (addWording string, exist bool) {
+ var v interface{}
+ if v, exist = f.GetAttr(FriendAttrAddWording); exist {
+ addWording = v.(string)
+ }
+
+ return
+}
+
+// SetAddTime 设置添加时间(忽略)
+func (f *Friend) SetAddTime(addTime int64) {
+ f.SetAttr(FriendAttrAddTime, addTime)
+}
+
+// GetAddTime 获取添加时间
+func (f *Friend) GetAddTime() (addTime int64, exist bool) {
+ var v interface{}
+ if v, exist = f.GetAttr(FriendAttrAddTime); exist {
+ addTime = v.(int64)
+ }
+
+ return
+}
+
+// SetRemarkTime 设置备注时间
+func (f *Friend) SetRemarkTime(remarkTime int64) {
+ f.SetAttr(FriendAttrRemarkTime, remarkTime)
+}
+
+// GetRemarkTime 获取备注时间
+func (f *Friend) GetRemarkTime() (remarkTime int64, exist bool) {
+ var v interface{}
+ if v, exist = f.GetAttr(FriendAttrRemarkTime); exist {
+ remarkTime = v.(int64)
+ }
+
+ return
+}
+
+// SetSNSCustomAttr 设置SNS自定义关系数据(自定义字段需要单独申请,请在 IM 控制台 >应用配置>功能配置申请自定义好友字段,申请提交后,自定义好友字段将在5分钟内生效)
+func (f *Friend) SetSNSCustomAttr(name string, value interface{}) {
+ if f.customAttrs == nil {
+ f.customAttrs = make(map[string]interface{})
+ }
+
+ f.customAttrs[fmt.Sprintf("%s_%s", "Tag_SNS_Custom", name)] = value
+}
+
+// GetSNSCustomAttr 设置SNS自定义关系数据 (自定义字段需要单独申请,请在 IM 控制台 >应用配置>功能配置申请自定义好友字段,申请提交后,自定义好友字段将在5分钟内生效)
+func (f *Friend) GetSNSCustomAttr(name string) (value interface{}, exist bool) {
+ if f.customAttrs == nil {
+ return
+ }
+
+ value, exist = f.customAttrs[fmt.Sprintf("%s_%s", "Tag_SNS_Custom", name)]
+ return
+}
+
+// GetSNSAttrs 获取SNS标准关系数据
+func (f *Friend) GetSNSAttrs() (attrs map[string]interface{}) {
+ attrs = make(map[string]interface{})
+
+ for k, v := range f.GetAttrs() {
+ switch k {
+ case FriendAttrAddSource, FriendAttrRemark, FriendAttrGroup, FriendAttrAddWording, FriendAttrAddTime, FriendAttrRemarkTime:
+ attrs[k] = v
+ }
+ }
+
+ return
+}
+
+// GetSNSCustomAttrs 获取SNS自定义关系数据(自定义字段需要单独申请,请在 IM 控制台 >应用配置>功能配置申请自定义好友字段,申请提交后,自定义好友字段将在5分钟内生效)
+func (f *Friend) GetSNSCustomAttrs() (attrs map[string]interface{}) {
+ attrs = make(map[string]interface{})
+
+ if f.customAttrs == nil {
+ return
+ }
+
+ for k, v := range f.customAttrs {
+ switch k {
+ case FriendAttrAddSource, FriendAttrRemark, FriendAttrGroup, FriendAttrAddWording, FriendAttrAddTime, FriendAttrRemarkTime:
+ default:
+ attrs[k] = v
+ }
+ }
+
+ return
+}
+
+// checkError 检测参数错误
+func (f *Friend) checkError() error {
+ if f.GetUserId() == "" {
+ return errNotSetAccount
+ }
+
+ if _, exist := f.GetSrcAddSource(); !exist {
+ return errNotSetAddSource
+ }
+
+ return nil
+}
diff --git a/sns/types.go b/sns/types.go
new file mode 100644
index 0000000..23d549f
--- /dev/null
+++ b/sns/types.go
@@ -0,0 +1,301 @@
+/**
+ * @Author: Echo
+ * @Author:1711788888@qq.com
+ * @Date: 2021/5/29 17:34
+ * @Desc: 关系链管理数据类型
+ */
+
+package sns
+
+import "git.echol.cn/loser/tencent-im/internal/types"
+
+type (
+ // 添加的好友信息
+ addFriendItem struct {
+ UserId string `json:"To_Account"` // (必填)好友的UserID
+ AddSource string `json:"AddSource"` // (必填)加好友来源
+ Remark string `json:"Remark,omitempty"` // (选填)加好友备注
+ GroupName string `json:"GroupName,omitempty"` // (选填)分组信息,添加好友时只允许设置一个分组,因此使用 String 类型即可
+ AddWording string `json:"AddWording,omitempty"` // (选填)好友关系时的附言信息
+ }
+
+ // 添加好友(请求)
+ addFriendsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要为该UserID添加好友
+ Friends []*addFriendItem `json:"AddFriendItem"` // (必填)好友结构体对象
+ AddType AddType `json:"AddType"` // (选填)加好友方式(默认双向加好友方式)Add_Type_Single:表示单向加好友;Add_Type_Both:表示双向加好友
+ ForceAddFlags ForceAddType `json:"ForceAddFlags"` // (选填)管理员强制加好友标记:1表示强制加好友,0表示常规加好友方式
+ }
+
+ // 添加好友(响应)
+ addFriendsResp struct {
+ types.ActionBaseResp
+ Results []*Result `json:"ResultItem"` // 批量加好友的结果对象数组
+ }
+
+ // Result 添加结果
+ Result struct {
+ UserId string `json:"To_Account"` // 请求添加的好友的UserID
+ ResultCode int `json:"ResultCode"` // 处理结果,0表示成功,非0表示失败
+ ResultInfo string `json:"ResultInfo"` // 错误描述信息
+ }
+
+ // 导入好友(请求)
+ importFriendsReq struct {
+ UserId string `json:"From_Account"` // 需要为该UserID添加好友
+ Friends []*importFriendItem `json:"AddFriendItem"` // 好友结构体对象
+ }
+
+ // 导入好友(响应)
+ importFriendsResp struct {
+ types.ActionBaseResp
+ Results []*Result `json:"ResultItem"` // 结果对象数组
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ }
+
+ // 导入好友
+ importFriendItem struct {
+ UserId string `json:"To_Account"` // (必填)好友的UserID
+ AddSource string `json:"AddSource"` // (必填)加好友来源
+ Remark string `json:"Remark,omitempty"` // (选填)加好友备注
+ GroupName []string `json:"GroupName,omitempty"` // (选填)分组信息
+ AddWording string `json:"AddWording,omitempty"` // (选填)好友关系时的附言信息
+ AddTime int64 `json:"AddTime,omitempty"` // (选填)形成好友关系的时间
+ RemarkTime int64 `json:"RemarkTime,omitempty"` // (选填)好友备注时间
+ CustomData []*types.TagPair `json:"CustomItem,omitempty"` // (选填)自定义好友数据
+ }
+
+ // 更新好友(请求)
+ updateFriendsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要更新该UserID的关系链数据
+ Friends []*updateFriendItem `json:"UpdateItem"` // (必填)需要更新的好友对象数组
+ }
+
+ // 更新好友(响应)
+ updateFriendsResp struct {
+ types.ActionBaseResp
+ Results []*Result `json:"ResultItem"` // 结果对象数组
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ }
+
+ // 更新好友
+ updateFriendItem struct {
+ UserId string `json:"To_Account"` // (必填)好友的UserID
+ Attrs []*types.TagPair `json:"SnsItem"` // (必填)需要更新的关系链数据对象数组
+ }
+
+ // 删除好友(请求)
+ deleteFriendsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要删除该UserID的好友
+ DeletedUserIds []string `json:"To_Account"` // (必填)待删除的好友的 UserID 列表,单次请求的 To_Account 数不得超过1000
+ DeleteType DeleteType `json:"DeleteType"` // (选填)删除模式
+ }
+
+ // 删除好友(响应)
+ deleteFriendsResp struct {
+ types.ActionBaseResp
+ ErrorDisplay string `json:"ErrorDisplay"` // 详细的客户端展示信息
+ Results []*Result `json:"ResultItem"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ }
+
+ // 删除所有好友(请求)
+ deleteAllFriendsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要删除该UserID的好友
+ DeleteType DeleteType `json:"DeleteType"` // (选填)删除类型
+ }
+
+ // 校验好友(请求)
+ checkFriendsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要校验该 UserID 的好友
+ CheckedUserIds []string `json:"To_Account"` // (必填)请求校验的好友的 UserID 列表,单次请求的 To_Account 数不得超过1000
+ CheckType CheckType `json:"CheckType"` // (必填)校验模式
+ }
+
+ // 校验好友(响应)
+ checkFriendsResp struct {
+ types.ActionBaseResp
+ Results []*CheckResult `json:"InfoItem"` // 结果对象数组
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ }
+
+ // CheckResult 校验结果
+ CheckResult struct {
+ UserId string `json:"To_Account"` // 请求校验的用户的 UserID
+ Relation string `json:"Relation"` // 校验成功时 To_Account 与 From_Account 之间的好友关系
+ ResultCode int `json:"ResultCode"` // 处理结果,0表示成功,非0表示失败
+ ResultInfo string `json:"ResultInfo"` // 描述信息,成功时该字段为空
+ }
+
+ // 拉取指定好友(请求)
+ getFriendsReq struct {
+ UserId string `json:"From_Account"` // (必填)指定要拉取好友数据的用户的 UserID
+ FriendUserIds []string `json:"To_Account"` // (必填)好友的 UserID 列表,建议每次请求的好友数不超过100,避免因数据量太大导致回包失败
+ TagList []string `json:"TagList"` // (必填)指定要拉取的资料字段及好友字段
+ }
+
+ // 拉取指定好友(响应)
+ getFriendsResp struct {
+ types.ActionBaseResp
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ Friends []friendData `json:"InfoItem"` // 好友对象数组
+ }
+
+ // 拉取好友(请求)
+ fetchFriendsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要拉取该 UserID 的黑名单
+ StartIndex int `json:"StartIndex"` // (必填)拉取的起始位置
+ StandardSequence int `json:"StandardSequence"` // (选填)上次拉好友数据时返回的 StandardSequence,如果 StandardSequence 字段的值与后台一致,后台不会返回标配好友数据
+ CustomSequence int `json:"CustomSequence"` // (选填)上次拉好友数据时返回的 CustomSequence,如果 CustomSequence 字段的值与后台一致,后台不会返回自定义好友数据
+ }
+
+ // 拉取好友(响应)
+ fetchFriendsResp struct {
+ types.ActionBaseResp
+ Friends []friendData `json:"UserDataItem"` // 好友对象数组
+ StandardSequence int `json:"StandardSequence"` // 标配好友数据的 Sequence,客户端可以保存该 Sequence,下次请求时通过请求的 StandardSequence 字段返回给后台
+ CustomSequence int `json:"CustomSequence"` // 自定义好友数据的 Sequence,客户端可以保存该 Sequence,下次请求时通过请求的 CustomSequence 字段返回给后台
+ FriendNum int `json:"FriendNum"` // 好友总数
+ CompleteFlag int `json:"CompleteFlag"` // 分页的结束标识,非0值表示已完成全量拉取
+ NextStartIndex int `json:"NextStartIndex"` // 分页接口下一页的起始位置
+ }
+
+ // 好友信息
+ friendData struct {
+ UserId string `json:"To_Account"` // 好友的 UserID
+ Values []types.TagPair `json:"ValueItem"` // 好友数据的数组
+ Profiles []types.TagPair `json:"SnsProfileItem"` // 好友数据的数组
+ ResultCode int `json:"ResultCode"` // 处理结果,0表示成功,非0表示失败
+ ResultInfo string `json:"ResultInfo"` // 错误描述信息
+ }
+
+ // FetchFriendsRet 好友列表
+ FetchFriendsRet struct {
+ StandardSequence int // 标准排序
+ CustomSequence int // 自定义排序
+ StartIndex int // 分页接口下一页的起始位置
+ Total int // 好友总数
+ HasMore bool // 是否还有更多数据
+ List []*Friend // 好友列表
+ }
+
+ // 添加黑名单(请求)
+ addBlacklistReq struct {
+ UserId string `json:"From_Account"` // (必填)请求为该 UserID 添加黑名单
+ BlackedUserIds []string `json:"To_Account"` // (必填)待添加为黑名单的用户 UserID 列表,单次请求的 To_Account 数不得超过1000
+ }
+
+ // 添加黑名单(响应)
+ addBlacklistResp struct {
+ types.ActionBaseResp
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ Results []*Result `json:"ResultItem"` // 批量添加黑名单的结果对象数组
+ }
+
+ // 删除黑名单(请求)
+ deleteBlacklistReq struct {
+ UserId string `json:"From_Account"` // (必填)需要删除该 UserID 的黑名单
+ DeletedUserIds []string `json:"To_Account"` // (必填)待删除的黑名单的 UserID 列表,单次请求的 To_Account 数不得超过1000
+ }
+
+ // 删除黑名单(响应)
+ deleteBlacklistResp struct {
+ types.ActionBaseResp
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ Results []*Result `json:"ResultItem"` // 批量添加黑名单的结果对象数组
+ }
+
+ // 拉取黑名单(请求)
+ fetchBlacklistReq struct {
+ UserId string `json:"From_Account"` // (必填)需要拉取该 UserID 的黑名单
+ StartIndex int `json:"StartIndex"` // (必填)拉取的起始位置
+ MaxLimited int `json:"MaxLimited"` // (必填)每页最多拉取的黑名单数
+ LastSequence int `json:"LastSequence"` // (必填)上一次拉黑名单时后台返回给客户端的 Seq,初次拉取时为0
+ }
+
+ // 拉取黑名单(响应)
+ fetchBlacklistResp struct {
+ types.ActionBaseResp
+ ErrorDisplay string `json:"ErrorDisplay"` // 详细的客户端展示信息
+ StartIndex int `json:"StartIndex"` // 下页拉取的起始位置,0表示已拉完
+ CurrentSequence int `json:"CurrentSequence"` // 黑名单最新的 Seq
+ Blacklists []*Blacklist `json:"BlackListItem"` // 黑名单对象数组
+ }
+
+ // FetchBlacklistRet 拉取黑名单结果
+ FetchBlacklistRet struct {
+ StandardSequence int // 标准排序
+ StartIndex int // 分页接口下一页的起始位置
+ HasMore bool // 是否还有数据
+ List []*Blacklist // 黑名单列表
+ }
+
+ // Blacklist 黑名单
+ Blacklist struct {
+ UserId string `json:"To_Account"` // 黑名单的 UserID
+ Time int `json:"AddBlackTimeStamp"` // 添加黑名单的时间
+ }
+
+ // 校验黑名单(请求)
+ checkBlacklistReq struct {
+ UserId string `json:"From_Account"` // (必填)需要校验该 UserID 的黑名单
+ CheckedUserIds []string `json:"To_Account"` // (必填)待校验的黑名单的 UserID 列表,单次请求的 To_Account 数不得超过1000
+ CheckType BlacklistCheckType `json:"CheckType"` // (必填)校验模式
+ }
+
+ // 校验黑名单(响应)
+ checkBlacklistResp struct {
+ types.ActionBaseResp
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ Results []*CheckResult `json:"BlackListCheckItem"` // 校验结果对象数组
+ }
+
+ // 添加分组(请求)
+ addGroupsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要为该 UserID 添加新分组
+ GroupNames []string `json:"GroupName"` // (必填)新增分组列表
+ JoinedUserIds []string `json:"To_Account"` // (必填)需要加入新增分组的好友的 UserID 列表
+ }
+
+ // 添加分组(响应)
+ addGroupsResp struct {
+ types.ActionBaseResp
+ ErrorDisplay string `json:"ErrorDisplay"` // 详细的客户端展示信息
+ FailUserIds []string `json:"Fail_Account"` // 返回处理失败的用户列表,仅当存在失败用户时才返回该字段
+ CurrentSequence int `json:"CurrentSequence"` // 返回最新的分组 Sequence
+ Results []*Result `json:"ResultItem"` // 好友加入新增分组的结果对象数组
+ }
+
+ // 删除分组(请求)
+ deleteGroupsReq struct {
+ UserId string `json:"From_Account"` // (必填)需要删除该 UserID 的分组
+ GroupNames []string `json:"GroupName"` // (必填)要删除的分组列表
+ }
+
+ // 删除分组(响应)
+ deleteGroupsResp struct {
+ types.ActionBaseResp
+ CurrentSequence int `json:"CurrentSequence"` // 返回最新的分组 Sequence
+ }
+
+ // 拉取分组(请求)
+ getGroupsReq struct {
+ UserId string `json:"From_Account"` // (必填)指定要拉取分组的用户的 UserID
+ LastSequence int `json:"LastSequence"` // (必填)上一次拉取分组时后台返回给客户端的 Seq,初次拉取时为0,只有 GroupName 为空时有效
+ NeedFriend NeedFriendType `json:"NeedFriend"` // (选填)是否需要拉取分组下的 User 列表
+ GroupNames []string `json:"GroupName"` // (选填)要拉取的分组名称
+ }
+
+ // 拉取分组(响应)
+ getGroupsResp struct {
+ types.ActionBaseResp
+ CurrentSequence int `json:"CurrentSequence"` // 返回最新的分组 Sequence
+ Results []*GroupResult `json:"ResultItem"` // 拉取分组的结果对象数组
+ }
+
+ // GroupResult 分组结果
+ GroupResult struct {
+ GroupName string `json:"GroupName"` // 分组名
+ FriendNumber int `json:"FriendNumber"` // 该分组下的好友数量
+ UserIds []string `json:"To_Account"` // 该分组下的好友的 UserID
+ }
+)