初始化项目

This commit is contained in:
loser 2022-09-27 11:31:23 +08:00
parent b4dc3c7305
commit 533ede4f66
54 changed files with 12011 additions and 25 deletions

27
.gitignore vendored
View File

@ -1,23 +1,4 @@
# ---> Go .idea/
# If you prefer the allow list template instead of the deny list, see community template: */.DS_Store
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore .vscode
# go.sum
# 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

1014
README.md

File diff suppressed because it is too large Load Diff

305
account/api.go Normal file
View File

@ -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
}

16
account/enum.go Normal file
View File

@ -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" // 已导入
)

117
account/types.go Normal file
View File

@ -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表示该帐号不存在
}
)

289
callback/callback.go Normal file
View File

@ -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)
}

324
callback/types.go Normal file
View File

@ -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"` // 请求添加好友的用户的 UserIDA添加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"` // 请求的发起者
}
)

63
example/main.go Normal file
View File

@ -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)
}
}

7
go.mod Normal file
View File

@ -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

1315
group/api.go Normal file

File diff suppressed because it is too large Load Diff

210
group/filter.go Normal file
View File

@ -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
}

383
group/group.go Normal file
View File

@ -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
}

160
group/member.go Normal file
View File

@ -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
}

221
group/message.go Normal file
View File

@ -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
}

508
group/types.go Normal file
View File

@ -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"` // 该群组的在线人数
}
)

212
im.go Normal file
View File

@ -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
}

1511
im_test.go Normal file

File diff suppressed because it is too large Load Diff

108
internal/conv/conv.go Normal file
View File

@ -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)
}
}
}

146
internal/core/client.go Normal file
View File

@ -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
}

38
internal/core/error.go Normal file
View File

@ -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
}

170
internal/entity/mesage.go Normal file
View File

@ -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
}

View File

@ -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)
}

330
internal/entity/user.go Normal file
View File

@ -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
}

16
internal/enum/code.go Normal file
View File

@ -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 // 无效响应(自定义)
)

95
internal/enum/enum.go Normal file
View File

@ -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的推送扩展
)

63
internal/random/random.go Normal file
View File

@ -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
}
}

30
internal/sign/base64.go Normal file
View File

@ -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)
}

178
internal/sign/sign.go Normal file
View File

@ -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
}

View File

@ -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
}

171
internal/types/types.go Normal file
View File

@ -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 = 1GIF = 2PNG = 3BMP = 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
)

83
mute/api.go Normal file
View File

@ -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
}

37
mute/types.go Normal file
View File

@ -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表示该帐号被禁言一小时
}
)

104
operation/api.go Normal file
View File

@ -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
}

51
operation/enum.go Normal file
View File

@ -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" // 日期
)

91
operation/types.go Normal file
View File

@ -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列表
}
)

354
private/api.go Normal file
View File

@ -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 必须设置为帐号 BTo_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 必须设置为帐号 BTo_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 必须设置为帐号 BTo_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 必须设置为帐号 BTo_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
}

42
private/enum.go Normal file
View File

@ -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的推送扩展
)

183
private/message.go Normal file
View File

@ -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
}

192
private/types.go Normal file
View File

@ -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
)

106
profile/api.go Normal file
View File

@ -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
}

54
profile/enum.go Normal file
View File

@ -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 // 角色
)

39
profile/profile.go Normal file
View File

@ -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
}

39
profile/types.go Normal file
View File

@ -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"` // 错误描述信息,成功时该字段为空
}
)

398
push/api.go Normal file
View File

@ -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
}

38
push/enum.go Normal file
View File

@ -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的推送扩展
)

152
push/message.go Normal file
View File

@ -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
}

126
push/types.go Normal file
View File

@ -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
)

148
recentcontact/api.go Normal file
View File

@ -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
}

16
recentcontact/enum.go Normal file
View File

@ -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 会话
)

78
recentcontact/types.go Normal file
View File

@ -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 表示不清理漫游消息
}

901
sns/api.go Normal file
View File

@ -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
}

90
sns/enum.go Normal file
View File

@ -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_AccountTo_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_AccountTo_Account 的黑名单中也有 From_Account
)

206
sns/friend.go Normal file
View File

@ -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
}

301
sns/types.go Normal file
View File

@ -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
}
)