diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cd9e43f..f7339b0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -20,10 +20,10 @@ jobs: matrix: go: [ '1.17', 'oldstable', 'stable' ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} @@ -31,9 +31,9 @@ jobs: run: go build -v ./... - name: Lint - uses: golangci/golangci-lint-action@v3.6.0 + uses: golangci/golangci-lint-action@v3.7.0 with: - version: v1.53 + version: v1.55 - name: Test run: go test -v ./... diff --git a/.gitignore b/.gitignore index aa47867..5a90fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ # library has no lockfile /Gopkg.lock +# perhaps people would all have their workwxctl binary in the repo root +/workwxctl + # Created by https://www.toptal.com/developers/gitignore/api/go,vim,linux,emacs,macos,visualstudiocode,intellij+all # Edit at https://www.toptal.com/developers/gitignore?templates=go,vim,linux,emacs,macos,visualstudiocode,intellij+all diff --git a/README.md b/README.md index 725ea1c..7ee7f56 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,15 @@ CI 会在 `go1.17` 和 Go 的当前稳定版本、上一个稳定版本上跑测 * [x] 通讯录管理 (**部分支持**,见下) * [x] 客户联系 (**大部分支持**,见下) +* [x] 微信客服 (**部分支持**,见下) * [ ] 应用管理 -* [x] 消息发送 (除修改群聊会话外全部支持) +* [x] 消息发送 (全部支持) * [x] 消息接收 * [x] 素材管理 (**支持上传**, 见下) * [x] OA (**大部分支持**,见下) * [x] 会话内容存档 (**大部分支持**,见下) * [x] 企业微信登录接口 (code2Session) +* [x] 获取访问用户身份 (code2UserInfo)
通讯录管理 API @@ -88,17 +90,17 @@ CI 会在 `go1.17` 和 Go 的当前稳定版本、上一个稳定版本上跑测 * [ ] 成员管理 - [ ] 创建成员 - [x] 读取成员 *NOTE: 成员对外信息暂未实现* - - [ ] 更新成员 + - [x] 更新成员 - [ ] 删除成员 - [ ] 批量删除成员 - [ ] 获取部门成员 - [x] 获取部门成员详情 - - [ ] userid与openid互换 + - [x] userid与openid互换 - [ ] 二次验证 - [ ] 邀请成员 - - [ ] 获取加入企业二维码 + - [x] 获取加入企业二维码 - [x] 手机号获取userid - - [ ] 邮箱获取userid + - [x] 邮箱获取userid - [ ] 获取成员ID列表 * [ ] 部门管理 - [x] 创建部门 @@ -134,11 +136,16 @@ CI 会在 `go1.17` 和 Go 的当前稳定版本、上一个稳定版本上跑测 * [x] 企业服务人员管理 - [x] 获取配置了客户联系功能的成员列表 - [x] 客户联系「联系我」管理 + - [x] 客户群「加入群聊」管理 * [x] 客户管理 - [x] 获取客户列表 - [x] 获取客户详情 - [x] 批量获取客户详情 - [x] 修改客户备注信息 +* [x] 客户群管理 + - [x] 获取客户群列表 + - [x] 获取客户群详情 + - [x] 客户群opengid转换 * [x] 在职继承 - [x] 分配在职成员的客户 - [x] 查询客户接替状态 @@ -172,6 +179,36 @@ CI 会在 `go1.17` 和 Go 的当前稳定版本、上一个稳定版本上跑测
+
+微信客服 API + +* [x] 客服账号管理 + - [x] 添加客服账号 + - [x] 删除客服账号 + - [x] 修改客服账号 + - [x] 获取客服账号列表 + - [x] 获取客服账号链接 +* [x] 接待人员管理 + - [x] 添加接待人员 + - [x] 删除接待人员 + - [x] 获取接待人员列表 +* [x] 会话分配与消息收发 + - [x] 分配客服会话 + - [x] 接收消息和事件 + - [x] 发送消息 + - [x] 发送欢迎语等事件响应消息 +* [ ] 「升级服务」配置 +* [ ] 其他基础信息获取 + - [ ] 获取客户基础信息 +* [ ] 统计管理 + - [ ] 获取「客户数据统计」企业汇总数据 + - [ ] 获取「客户数据统计」接待人员明细数据 +* [ ] 机器人管理 + - [ ] 知识库分组管理 + - [ ] 知识库问答管理 + +
+
身份验证 API @@ -198,7 +235,7 @@ CI 会在 `go1.17` 和 Go 的当前稳定版本、上一个稳定版本上跑测 * [x] 接收消息 * [x] 发送消息到群聊会话 - [x] 创建群聊会话 - - [ ] 修改群聊会话 + - [x] 修改群聊会话 - [x] 获取群聊会话 - [x] 应用推送消息 diff --git a/apis.md.go b/apis.md.go index 95ebe12..ecc457a 100644 --- a/apis.md.go +++ b/apis.md.go @@ -58,6 +58,20 @@ func (c *WorkwxApp) execJSCode2Session(req reqJSCode2Session) (respJSCode2Sessio return resp, nil } +// execAuthCode2UserInfo 获取访问用户身份 +func (c *WorkwxApp) execAuthCode2UserInfo(req reqAuthCode2UserInfo) (respAuthCode2UserInfo, error) { + var resp respAuthCode2UserInfo + err := c.executeQyapiGet("/cgi-bin/auth/getuserinfo", req, &resp, true) + if err != nil { + return respAuthCode2UserInfo{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respAuthCode2UserInfo{}, bizErr + } + + return resp, nil +} + // execUserGet 读取成员 func (c *WorkwxApp) execUserGet(req reqUserGet) (respUserGet, error) { var resp respUserGet @@ -72,6 +86,20 @@ func (c *WorkwxApp) execUserGet(req reqUserGet) (respUserGet, error) { return resp, nil } +// execUserUpdate 更新成员 +func (c *WorkwxApp) execUserUpdate(req reqUserUpdate) (respUserUpdate, error) { + var resp respUserUpdate + err := c.executeQyapiJSONPost("/cgi-bin/user/update", req, &resp, true) + if err != nil { + return respUserUpdate{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respUserUpdate{}, bizErr + } + + return resp, nil +} + // execUserList 获取部门成员详情 func (c *WorkwxApp) execUserList(req reqUserList) (respUserList, error) { var resp respUserList @@ -86,6 +114,48 @@ func (c *WorkwxApp) execUserList(req reqUserList) (respUserList, error) { return resp, nil } +// execConvertUserIDToOpenID userid转openid +func (c *WorkwxApp) execConvertUserIDToOpenID(req reqConvertUserIDToOpenID) (respConvertUserIDToOpenID, error) { + var resp respConvertUserIDToOpenID + err := c.executeQyapiJSONPost("/cgi-bin/user/convert_to_openid", req, &resp, true) + if err != nil { + return respConvertUserIDToOpenID{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respConvertUserIDToOpenID{}, bizErr + } + + return resp, nil +} + +// execConvertOpenIDToUserID openid转userid +func (c *WorkwxApp) execConvertOpenIDToUserID(req reqConvertOpenIDToUserID) (respConvertOpenIDToUserID, error) { + var resp respConvertOpenIDToUserID + err := c.executeQyapiJSONPost("/cgi-bin/user/convert_to_userid", req, &resp, true) + if err != nil { + return respConvertOpenIDToUserID{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respConvertOpenIDToUserID{}, bizErr + } + + return resp, nil +} + +// execUserJoinQrcode 获取加入企业二维码 +func (c *WorkwxApp) execUserJoinQrcode(req reqUserJoinQrcode) (respUserJoinQrcode, error) { + var resp respUserJoinQrcode + err := c.executeQyapiGet("/cgi-bin/corp/get_join_qrcode", req, &resp, true) + if err != nil { + return respUserJoinQrcode{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respUserJoinQrcode{}, bizErr + } + + return resp, nil +} + // execUserIDByMobile 手机号获取userid func (c *WorkwxApp) execUserIDByMobile(req reqUserIDByMobile) (respUserIDByMobile, error) { var resp respUserIDByMobile @@ -100,6 +170,20 @@ func (c *WorkwxApp) execUserIDByMobile(req reqUserIDByMobile) (respUserIDByMobil return resp, nil } +// execUserIDByEmail 邮箱获取userid +func (c *WorkwxApp) execUserIDByEmail(req reqUserIDByEmail) (respUserIDByEmail, error) { + var resp respUserIDByEmail + err := c.executeQyapiJSONPost("/cgi-bin/user/get_userid_by_email", req, &resp, true) + if err != nil { + return respUserIDByEmail{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respUserIDByEmail{}, bizErr + } + + return resp, nil +} + // execDeptCreate 创建部门 func (c *WorkwxApp) execDeptCreate(req reqDeptCreate) (respDeptCreate, error) { var resp respDeptCreate @@ -338,43 +422,29 @@ func (c *WorkwxApp) execTransferGroupChatExternalContact(req reqTransferGroupCha return resp, nil } -// execAppchatListGet 获取客户群列表 -func (c *WorkwxApp) execAppchatListGet(req reqAppchatList) (respAppchatList, error) { - var resp respAppchatList - err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/list", req, &resp, true) +// execAppchatCreate 创建群聊会话 +func (c *WorkwxApp) execAppchatCreate(req reqAppchatCreate) (respAppchatCreate, error) { + var resp respAppchatCreate + err := c.executeQyapiJSONPost("/cgi-bin/appchat/create", req, &resp, true) if err != nil { - return respAppchatList{}, err + return respAppchatCreate{}, err } if bizErr := resp.TryIntoErr(); bizErr != nil { - return respAppchatList{}, bizErr + return respAppchatCreate{}, bizErr } return resp, nil } -// execAppchatInfoGet 获取客户群详细 -func (c *WorkwxApp) execAppchatInfoGet(req reqAppchatInfo) (respAppchatInfo, error) { - var resp respAppchatInfo - err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/get", req, &resp, true) +// execAppchatUpdate 修改群聊会话 +func (c *WorkwxApp) execAppchatUpdate(req reqAppchatUpdate) (respAppchatUpdate, error) { + var resp respAppchatUpdate + err := c.executeQyapiJSONPost("/cgi-bin/appchat/update", req, &resp, true) if err != nil { - return respAppchatInfo{}, err + return respAppchatUpdate{}, err } if bizErr := resp.TryIntoErr(); bizErr != nil { - return respAppchatInfo{}, bizErr - } - - return resp, nil -} - -// execAppchatCreate 创建群聊会话 -func (c *WorkwxApp) execAppchatCreate(req reqAppchatCreate) (respAppchatCreate, error) { - var resp respAppchatCreate - err := c.executeQyapiJSONPost("/cgi-bin/appchat/create", req, &resp, true) - if err != nil { - return respAppchatCreate{}, err - } - if bizErr := resp.TryIntoErr(); bizErr != nil { - return respAppchatCreate{}, bizErr + return respAppchatUpdate{}, bizErr } return resp, nil @@ -506,6 +576,48 @@ func (c *WorkwxApp) execOAGetApprovalDetail(req reqOAGetApprovalDetail) (respOAG return resp, nil } +// execOAGetCorpVacationConf 获取企业假期管理配置 +func (c *WorkwxApp) execOAGetCorpVacationConf(req reqOAGetCorpVacationConf) (respOAGetCorpVacationConf, error) { + var resp respOAGetCorpVacationConf + err := c.executeQyapiGet("/cgi-bin/oa/vacation/getcorpconf", req, &resp, true) + if err != nil { + return respOAGetCorpVacationConf{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respOAGetCorpVacationConf{}, bizErr + } + + return resp, nil +} + +// execOAGetUserVacationQuota 获取成员假期余额 +func (c *WorkwxApp) execOAGetUserVacationQuota(req reqOAGetUserVacationQuota) (respOAGetUserVacationQuota, error) { + var resp respOAGetUserVacationQuota + err := c.executeQyapiJSONPost("/cgi-bin/oa/vacation/getuservacationquota", req, &resp, true) + if err != nil { + return respOAGetUserVacationQuota{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respOAGetUserVacationQuota{}, bizErr + } + + return resp, nil +} + +// execOASetOneUserVacationQuota 修改成员假期余额 +func (c *WorkwxApp) execOASetOneUserVacationQuota(req reqOASetOneUserVacationQuota) (respOASetOneUserVacationQuota, error) { + var resp respOASetOneUserVacationQuota + err := c.executeQyapiJSONPost("/cgi-bin/oa/vacation/setoneuserquota", req, &resp, true) + if err != nil { + return respOASetOneUserVacationQuota{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respOASetOneUserVacationQuota{}, bizErr + } + + return resp, nil +} + // execMsgAuditListPermitUser 获取会话内容存档开启成员列表 func (c *WorkwxApp) execMsgAuditListPermitUser(req reqMsgAuditListPermitUser) (respMsgAuditListPermitUser, error) { var resp respMsgAuditListPermitUser @@ -660,6 +772,104 @@ func (c *WorkwxApp) execCloseTempChatExternalContact(req reqCloseTempChatExterna return resp, nil } +// execAddGroupChatJoinWayExternalContact 配置客户群「加入群聊」方式 +func (c *WorkwxApp) execAddGroupChatJoinWayExternalContact(req reqAddGroupChatJoinWayExternalContact) (respAddGroupChatJoinWayExternalContact, error) { + var resp respAddGroupChatJoinWayExternalContact + err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/add_join_way", req, &resp, true) + if err != nil { + return respAddGroupChatJoinWayExternalContact{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respAddGroupChatJoinWayExternalContact{}, bizErr + } + + return resp, nil +} + +// execGetGroupChatJoinWayExternalContact 获取企业已配置的客户群「加入群聊」方式 +func (c *WorkwxApp) execGetGroupChatJoinWayExternalContact(req reqGetGroupChatJoinWayExternalContact) (respGetGroupChatJoinWayExternalContact, error) { + var resp respGetGroupChatJoinWayExternalContact + err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/get_join_way", req, &resp, true) + if err != nil { + return respGetGroupChatJoinWayExternalContact{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respGetGroupChatJoinWayExternalContact{}, bizErr + } + + return resp, nil +} + +// execUpdateGroupChatJoinWayExternalContact 更新企业已配置的客户群「加入群聊」方式 +func (c *WorkwxApp) execUpdateGroupChatJoinWayExternalContact(req reqUpdateGroupChatJoinWayExternalContact) (respUpdateGroupChatJoinWayExternalContact, error) { + var resp respUpdateGroupChatJoinWayExternalContact + err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/update_join_way", req, &resp, true) + if err != nil { + return respUpdateGroupChatJoinWayExternalContact{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respUpdateGroupChatJoinWayExternalContact{}, bizErr + } + + return resp, nil +} + +// execDelGroupChatJoinWayExternalContact 删除企业已配置的客户群「加入群聊」方式 +func (c *WorkwxApp) execDelGroupChatJoinWayExternalContact(req reqDelGroupChatJoinWayExternalContact) (respDelGroupChatJoinWayExternalContact, error) { + var resp respDelGroupChatJoinWayExternalContact + err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/del_join_way", req, &resp, true) + if err != nil { + return respDelGroupChatJoinWayExternalContact{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respDelGroupChatJoinWayExternalContact{}, bizErr + } + + return resp, nil +} + +// execGroupChatListGet 获取客户群列表 +func (c *WorkwxApp) execGroupChatListGet(req reqGroupChatList) (respGroupChatList, error) { + var resp respGroupChatList + err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/list", req, &resp, true) + if err != nil { + return respGroupChatList{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respGroupChatList{}, bizErr + } + + return resp, nil +} + +// execGroupChatInfoGet 获取客户群详细 +func (c *WorkwxApp) execGroupChatInfoGet(req reqGroupChatInfo) (respGroupChatInfo, error) { + var resp respGroupChatInfo + err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/groupchat/get", req, &resp, true) + if err != nil { + return respGroupChatInfo{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respGroupChatInfo{}, bizErr + } + + return resp, nil +} + +// execConvertOpenGIDToChatID 客户群opengid转换 +func (c *WorkwxApp) execConvertOpenGIDToChatID(req reqConvertOpenGIDToChatID) (respConvertOpenGIDToChatID, error) { + var resp respConvertOpenGIDToChatID + err := c.executeQyapiJSONPost("/cgi-bin/externalcontact/opengid_to_chatid", req, &resp, true) + if err != nil { + return respConvertOpenGIDToChatID{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respConvertOpenGIDToChatID{}, bizErr + } + + return resp, nil +} + // execTransferCustomer 在职继承 分配在职成员的客户 func (c *WorkwxApp) execTransferCustomer(req reqTransferCustomer) (respTransferCustomer, error) { var resp respTransferCustomer @@ -743,3 +953,185 @@ func (c *WorkwxApp) execSendWelcomeMsg(req reqSendWelcomeMsgExternalContact) (re return resp, nil } + +// execKfAccountCreate 添加客服账号 +func (c *WorkwxApp) execKfAccountCreate(req reqKfAccountCreate) (respKfAccountCreate, error) { + var resp respKfAccountCreate + err := c.executeQyapiJSONPost("/cgi-bin/kf/account/add", req, &resp, true) + if err != nil { + return respKfAccountCreate{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfAccountCreate{}, bizErr + } + + return resp, nil +} + +// execKfAccountUpdate 修改客服账号 +func (c *WorkwxApp) execKfAccountUpdate(req reqKfAccountUpdate) (respKfAccountUpdate, error) { + var resp respKfAccountUpdate + err := c.executeQyapiJSONPost("/cgi-bin/kf/account/update", req, &resp, true) + if err != nil { + return respKfAccountUpdate{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfAccountUpdate{}, bizErr + } + + return resp, nil +} + +// execKfAccountDelete 删除客服账号 +func (c *WorkwxApp) execKfAccountDelete(req reqKfAccountDelete) (respKfAccountDelete, error) { + var resp respKfAccountDelete + err := c.executeQyapiJSONPost("/cgi-bin/kf/account/del", req, &resp, true) + if err != nil { + return respKfAccountDelete{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfAccountDelete{}, bizErr + } + + return resp, nil +} + +// execKfAccountList 获取客服账号列表 +func (c *WorkwxApp) execKfAccountList(req reqKfAccountList) (respKfAccountList, error) { + var resp respKfAccountList + err := c.executeQyapiGet("/cgi-bin/kf/account/list", req, &resp, true) + if err != nil { + return respKfAccountList{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfAccountList{}, bizErr + } + + return resp, nil +} + +// execAddKfContact 获取客服账号链接 +func (c *WorkwxApp) execAddKfContact(req reqAddKfContact) (respAddKfContact, error) { + var resp respAddKfContact + err := c.executeQyapiJSONPost("/cgi-bin/kf/add_contact_way", req, &resp, true) + if err != nil { + return respAddKfContact{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respAddKfContact{}, bizErr + } + + return resp, nil +} + +// execKfServicerCreate 添加接待人员 +func (c *WorkwxApp) execKfServicerCreate(req reqKfServicerCreate) (respKfServicerCreate, error) { + var resp respKfServicerCreate + err := c.executeQyapiJSONPost("/cgi-bin/kf/servicer/add", req, &resp, true) + if err != nil { + return respKfServicerCreate{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfServicerCreate{}, bizErr + } + + return resp, nil +} + +// execKfServicerDelete 删除接待人员 +func (c *WorkwxApp) execKfServicerDelete(req reqKfServicerDelete) (respKfServicerDelete, error) { + var resp respKfServicerDelete + err := c.executeQyapiJSONPost("/cgi-bin/kf/servicer/del", req, &resp, true) + if err != nil { + return respKfServicerDelete{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfServicerDelete{}, bizErr + } + + return resp, nil +} + +// execKfServicerList 获取接待人员列表 +func (c *WorkwxApp) execKfServicerList(req reqKfServicerList) (respKfServicerList, error) { + var resp respKfServicerList + err := c.executeQyapiGet("/cgi-bin/kf/servicer/list", req, &resp, true) + if err != nil { + return respKfServicerList{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfServicerList{}, bizErr + } + + return resp, nil +} + +// execKfServiceStateGet 获取会话状态 +func (c *WorkwxApp) execKfServiceStateGet(req reqKfServiceStateGet) (respKfServiceStateGet, error) { + var resp respKfServiceStateGet + err := c.executeQyapiJSONPost("/cgi-bin/kf/service_state/get", req, &resp, true) + if err != nil { + return respKfServiceStateGet{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfServiceStateGet{}, bizErr + } + + return resp, nil +} + +// execKfServiceStateTrans 变更会话状态 +func (c *WorkwxApp) execKfServiceStateTrans(req reqKfServiceStateTrans) (respKfServiceStateTrans, error) { + var resp respKfServiceStateTrans + err := c.executeQyapiJSONPost("/cgi-bin/kf/service_state/trans", req, &resp, true) + if err != nil { + return respKfServiceStateTrans{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfServiceStateTrans{}, bizErr + } + + return resp, nil +} + +// execKfSyncMsg 读取消息 +func (c *WorkwxApp) execKfSyncMsg(req reqKfSyncMsg) (respKfSyncMsg, error) { + var resp respKfSyncMsg + err := c.executeQyapiJSONPost("/cgi-bin/kf/sync_msg", req, &resp, true) + if err != nil { + return respKfSyncMsg{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respKfSyncMsg{}, bizErr + } + + return resp, nil +} + +// execKfSend 发送消息 +func (c *WorkwxApp) execKfSend(req reqMessage) (respMessageSend, error) { + var resp respMessageSend + err := c.executeQyapiJSONPost("/cgi-bin/kf/send_msg", req, &resp, true) + if err != nil { + return respMessageSend{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respMessageSend{}, bizErr + } + + return resp, nil +} + +// execKfOnEventSend 发送欢迎语等事件响应消息 +func (c *WorkwxApp) execKfOnEventSend(req reqMessage) (respMessageSend, error) { + var resp respMessageSend + err := c.executeQyapiJSONPost("/cgi-bin/kf/send_msg_on_event", req, &resp, true) + if err != nil { + return respMessageSend{}, err + } + if bizErr := resp.TryIntoErr(); bizErr != nil { + return respMessageSend{}, bizErr + } + + return resp, nil +} diff --git a/appchat.go b/appchat.go index 10981a5..5e3473c 100644 --- a/appchat.go +++ b/appchat.go @@ -11,6 +11,19 @@ func (c *WorkwxApp) CreateAppchat(chatInfo *ChatInfo) (chatID string, err error) return resp.ChatID, nil } +// UpdateAppchat 修改群聊会话 +func (c *WorkwxApp) UpdateAppchat(chatInfo ChatInfo, addMemberUserIDs, delMemberUserIDs []string) (err error) { + _, err = c.execAppchatUpdate(reqAppchatUpdate{ + ChatInfo: chatInfo, + AddMemberUserIDs: addMemberUserIDs, + DelMemberUserIDs: delMemberUserIDs, + }) + if err != nil { + return err + } + return nil +} + // GetAppchat 获取群聊会话 func (c *WorkwxApp) GetAppchat(chatID string) (*ChatInfo, error) { resp, err := c.execAppchatGet(reqAppchatGet{ @@ -25,20 +38,20 @@ func (c *WorkwxApp) GetAppchat(chatID string) (*ChatInfo, error) { return obj, nil } -// GetAppChatList 获取客户群列表 +// GetAppChatList 获取客户群列表 企业微信接口调整 此API同GetGroupChatList 兼容处理 func (c *WorkwxApp) GetAppChatList(req ReqChatList) (*RespAppchatList, error) { - resp, err := c.execAppchatListGet(reqAppchatList{ + resp, err := c.execGroupChatListGet(reqGroupChatList{ ReqChatList: req, }) if err != nil { return nil, err } - return resp.RespAppchatList, nil + return resp.RespGroupChatList, nil } -// GetAppChatInfo 获取客户群详细信息 +// GetAppChatInfo 获取客户群详细信息 企业微信接口调整 此API同GetGroupChatInfo 兼容处理 func (c *WorkwxApp) GetAppChatInfo(chatID string) (*RespAppChatInfo, error) { - resp, err := c.execAppchatInfoGet(reqAppchatInfo{ + resp, err := c.execGroupChatInfoGet(reqGroupChatInfo{ ChatID: chatID, NeedName: ChatNeedName, }) diff --git a/chat_info.md.go b/chat_info.md.go index becbb0f..1eb749f 100644 --- a/chat_info.md.go +++ b/chat_info.md.go @@ -32,18 +32,18 @@ type ReqChatList struct { Limit int64 `json:"limit"` } -// RespGroupChatList 客户群列表数据 -type RespGroupChatList struct { +// GroupChatList 客户群列表数据 +type GroupChatList struct { // ChatID 客户群ID ChatID string `json:"chat_id"` // Status 客户群跟进状态 0 - 跟进人正常 1 - 跟进人离职 2 - 离职继承中 3 - 离职继承完成 Status int64 `json:"status"` } -// RespAppchatList 客户群列表结果 -type RespAppchatList struct { +// RespGroupChatList 客户群列表结果 +type RespGroupChatList struct { // GroupChatList 客户群列表 - GroupChatList []RespGroupChatList `json:"group_chat_list"` + GroupChatList []GroupChatList `json:"group_chat_list"` // NextCursor 分页游标 NextCursor string `json:"next_cursor"` } @@ -80,8 +80,8 @@ type ChatAdminList struct { UserID string `json:"userid"` } -// RespAppChatInfo 客户群详情 -type RespAppChatInfo struct { +// RespGroupChatInfo 客户群详情 +type RespGroupChatInfo struct { // ChatID 客户群ID ChatID string `json:"chat_id"` // Name 客户群名称 @@ -100,3 +100,9 @@ type RespAppChatInfo struct { // ChatNeedName 是否需要返回群成员的名字 0-不返回;1-返回。默认不返回 const ChatNeedName int64 = 1 + +// RespAppchatList 企业微信接口调整 兼容处理 +type RespAppchatList = RespGroupChatList + +// RespAppChatInfo 企业微信接口调整 兼容处理 +type RespAppChatInfo = RespGroupChatInfo diff --git a/cmd/workwxctl/commands/cmd_appchat_create.go b/cmd/workwxctl/commands/cmd_appchat_create.go new file mode 100644 index 0000000..0f3cdb1 --- /dev/null +++ b/cmd/workwxctl/commands/cmd_appchat_create.go @@ -0,0 +1,36 @@ +package commands + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/xen0n/go-workwx" +) + +func cmdAppchatCreate(c *cli.Context) error { + cfg := mustGetConfig(c) + chatID := c.String(flagChatID) + name := c.String(flagName) + ownerID := c.String(flagOwner) + userIDs := c.StringSlice(flagUser) + + app := cfg.MakeWorkwxApp() + // TODO: failed requests currently panics + req := workwx.ChatInfo{ + ChatID: chatID, + Name: name, + OwnerUserID: ownerID, + MemberUserIDs: userIDs, + } + fmt.Printf("about to create appchat %+v\n", req) + + newChatID, err := app.CreateAppchat(&req) + if err != nil { + fmt.Printf("failed to create appchat: error = %+v\n", err) + } else { + fmt.Printf("created appchat: chatid = \"%s\"\n", newChatID) + } + + return err +} diff --git a/cmd/workwxctl/commands/cmd_send_message.go b/cmd/workwxctl/commands/cmd_send_message.go index 3d55cae..7188852 100644 --- a/cmd/workwxctl/commands/cmd_send_message.go +++ b/cmd/workwxctl/commands/cmd_send_message.go @@ -26,7 +26,7 @@ func cmdSendMessage(c *cli.Context) error { url := c.String(flagURL) picURL := c.String(flagPicURL) buttonText := c.String(flagButtonText) - sourceContentURL := c.String(flagSourceContentURL) + // sourceContentURL := c.String(flagSourceContentURL) digest := c.String(flagDigest) app := cfg.MakeWorkwxApp() @@ -73,21 +73,29 @@ func cmdSendMessage(c *cli.Context) error { case "news": err = app.SendNewsMessage( &recipient, - title, - description, - url, - picURL, + []workwx.Article{ + workwx.Article{ + Title: title, + Description: description, + URL: url, + PicURL: picURL, + AppID: "", + PagePath: "", + }, + }, isSafe, ) case "mpnews": err = app.SendMPNewsMessage( &recipient, - title, - thumbMediaID, - author, - sourceContentURL, - content, - digest, + []workwx.MPArticle{workwx.MPArticle{ + Title: title, + ThumbMediaID: thumbMediaID, + Author: author, + ContentSourceURL: content, + Content: content, + Digest: digest, + }}, isSafe, ) default: diff --git a/cmd/workwxctl/commands/decl.go b/cmd/workwxctl/commands/decl.go index 2c90f69..14fd9ed 100644 --- a/cmd/workwxctl/commands/decl.go +++ b/cmd/workwxctl/commands/decl.go @@ -59,6 +59,30 @@ func InitApp() *cli.App { Usage: "获取部门列表", Action: cmdDeptList, }, + { + Name: "appchat-create", + Usage: "创建群聊会话", + Action: cmdAppchatCreate, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: flagChatID, + Usage: "欲建立的群聊 chatid,不能与已有的群重复,最长32个字符。只允许字符0-9及字母a-zA-Z。如果不填,系统会随机生成群id", + }, + &cli.StringFlag{ + Name: flagName, + Usage: "群聊名。最多50个utf8字符,超过将截断", + }, + &cli.StringFlag{ + Name: flagOwner, + Usage: "群主的 ID。如果不指定,系统会随机从群成员列表中选一人作为群主", + }, + &cli.StringSliceFlag{ + Name: flagUser, + Aliases: []string{flagToUserShort}, + Usage: "群成员 ID,可重复指定。至少2人,至多2000人", + }, + }, + }, { Name: "appchat-get", Usage: "获取群聊会话", diff --git a/cmd/workwxctl/commands/flags.go b/cmd/workwxctl/commands/flags.go index 18af99c..611a6e4 100644 --- a/cmd/workwxctl/commands/flags.go +++ b/cmd/workwxctl/commands/flags.go @@ -18,6 +18,11 @@ const ( flagQyapiHostOverride = "qyapi-host-override" flagTLSKeyLogFile = "tls-key-logfile" + flagName = "name" + flagOwner = "owner" + flagUser = "user" + flagChatID = "chatid" + flagMessageType = "message-type" flagSafe = "safe" flagToUser = "to-user" diff --git a/docs/apis.md b/docs/apis.md index 8f84941..35b32e1 100644 --- a/docs/apis.md +++ b/docs/apis.md @@ -8,6 +8,7 @@ Name|Request Type|Response Type|Access Token|URL|Doc `execGetJSAPITicket`|`reqJSAPITicket`|`respJSAPITicket`|+|`GET /cgi-bin/get_jsapi_ticket`|[获取企业的jsapi_ticket](https://open.work.weixin.qq.com/api/doc/90000/90136/90506) `execGetJSAPITicketAgentConfig`|`reqJSAPITicketAgentConfig`|`respJSAPITicket`|+|`GET /cgi-bin/ticket/get`|[获取应用的jsapi_ticket](https://open.work.weixin.qq.com/api/doc/90000/90136/90506) `execJSCode2Session`|`reqJSCode2Session`|`respJSCode2Session`|+|`GET /cgi-bin/miniprogram/jscode2session`|[临时登录凭证校验code2Session](https://open.work.weixin.qq.com/api/doc/90000/90136/91507) +`execAuthCode2UserInfo`|`reqAuthCode2UserInfo`|`respAuthCode2UserInfo`|+|`GET /cgi-bin/auth/getuserinfo`|[获取访问用户身份](https://developer.work.weixin.qq.com/document/path/91023) # 成员管理 @@ -17,15 +18,18 @@ Name|Request Type|Response Type|Access Token|URL|Doc :---|------------|-------------|------------|:--|:-- `execUserCreate`|TODO|TODO|+|`POST /cgi-bin/user/create`|[创建成员](https://work.weixin.qq.com/api/doc#90000/90135/90195) `execUserGet`|`reqUserGet`|`respUserGet`|+|`GET /cgi-bin/user/get`|[读取成员](https://work.weixin.qq.com/api/doc#90000/90135/90196) -`execUserUpdate`|TODO|TODO|+|`POST /cgi/bin/user/update`|[更新成员](https://work.weixin.qq.com/api/doc#90000/90135/90197) -`execUserDelete`|TODO|TODO|+|`GET /cgi/bin/user/delete`|[删除成员](https://work.weixin.qq.com/api/doc#90000/90135/90198) -`execUserBatchDelete`|TODO|TODO|+|`POST /cgi/bin/user/batchdelete`|[批量删除成员](https://work.weixin.qq.com/api/doc#90000/90135/90199) +`execUserUpdate`|`reqUserUpdate`|`respUserUpdate`|+|`POST /cgi-bin/user/update`|[更新成员](https://work.weixin.qq.com/api/doc#90000/90135/90197) +`execUserDelete`|TODO|TODO|+|`GET /cgi-bin/user/delete`|[删除成员](https://work.weixin.qq.com/api/doc#90000/90135/90198) +`execUserBatchDelete`|TODO|TODO|+|`POST /cgi-bin/user/batchdelete`|[批量删除成员](https://work.weixin.qq.com/api/doc#90000/90135/90199) `execUserSimpleList`|TODO|TODO|+|`GET /cgi-bin/user/simplelist`|[获取部门成员](https://work.weixin.qq.com/api/doc#90000/90135/90200) `execUserList`|`reqUserList`|`respUserList`|+|`GET /cgi-bin/user/list`|[获取部门成员详情](https://work.weixin.qq.com/api/doc#90000/90135/90201) -`execUserConvertToOpenID`|TODO|TODO|+|`POST /cgi-bin/user/convert_to_openid`|[userid与openid互换](https://work.weixin.qq.com/api/doc#90000/90135/90202) +`execConvertUserIDToOpenID`|`reqConvertUserIDToOpenID`|`respConvertUserIDToOpenID`|+|`POST /cgi-bin/user/convert_to_openid`|[userid转openid](https://work.weixin.qq.com/api/doc#90000/90135/90202) +`execConvertOpenIDToUserID`|`reqConvertOpenIDToUserID`|`respConvertOpenIDToUserID`|+|`POST /cgi-bin/user/convert_to_userid`|[openid转userid](https://work.weixin.qq.com/api/doc#90000/90135/90202) `execUserAuthSucc`|TODO|TODO|+|`GET /cgi-bin/user/authsucc`|[二次验证](https://work.weixin.qq.com/api/doc#90000/90135/90203) `execUserBatchInvite`|TODO|TODO|+|`POST /cgi-bin/batch/invite`|[邀请成员](https://work.weixin.qq.com/api/doc#90000/90135/90975) +`execUserJoinQrcode`|`reqUserJoinQrcode`|`respUserJoinQrcode`|+|`GET /cgi-bin/corp/get_join_qrcode`|[获取加入企业二维码](https://developer.work.weixin.qq.com/document/path/91714) `execUserIDByMobile`|`reqUserIDByMobile`|`respUserIDByMobile`|+|`POST /cgi-bin/user/getuserid`|[手机号获取userid](https://work.weixin.qq.com/api/doc/90001/90143/91693) +`execUserIDByEmail`|`reqUserIDByEmail`|`respUserIDByEmail`|+|`POST /cgi-bin/user/get_userid_by_email`|[邮箱获取userid](https://developer.work.weixin.qq.com/document/path/95895) # 部门管理 @@ -35,7 +39,7 @@ Name|Request Type|Response Type|Access Token|URL|Doc :---|------------|-------------|------------|:--|:-- `execDeptCreate`|`reqDeptCreate`|`respDeptCreate`|+|`POST /cgi-bin/department/create`|[创建部门](https://work.weixin.qq.com/api/doc#90000/90135/90205) `execDeptUpdate`|TODO|TODO|+|`POST /cgi-bin/department/update`|[更新部门](https://work.weixin.qq.com/api/doc#90000/90135/90206) -`execDeptDelete`|TODO|TODO|+|`GET /cgi/bin/department/delete`|[删除部门](https://work.weixin.qq.com/api/doc#90000/90135/90207) +`execDeptDelete`|TODO|TODO|+|`GET /cgi-bin/department/delete`|[删除部门](https://work.weixin.qq.com/api/doc#90000/90135/90207) `execDeptList`|`reqDeptList`|`respDeptList`|+|`GET /cgi-bin/department/list`|[获取部门列表](https://work.weixin.qq.com/api/doc#90000/90135/90208) `execDeptSimpleList`|`reqDeptSimpleList`| `respDeptSimpleList` |+|`GET /cgi-bin/department/simplelist`|[获取子部门ID列表](https://developer.work.weixin.qq.com/document/path/95350) @@ -47,11 +51,11 @@ Name|Request Type|Response Type|Access Token|URL|Doc :---|------------|-------------|------------|:--|:-- `execTagCreate`|TODO|TODO|+|`POST /cgi-bin/tag/create`|[创建标签](https://work.weixin.qq.com/api/doc#90000/90135/90210) `execTagUpdate`|TODO|TODO|+|`POST /cgi-bin/tag/update`|[更新标签名字](https://work.weixin.qq.com/api/doc#90000/90135/90211) -`execTagDelete`|TODO|TODO|+|`GET /cgi/bin/tag/delete`|[删除标签](https://work.weixin.qq.com/api/doc#90000/90135/90212) -`execTagListUsers`|TODO|TODO|+|`GET /cgi/bin/tag/get`|[获取标签成员](https://work.weixin.qq.com/api/doc#90000/90135/90213) -`execTagAddUsers`|TODO|TODO|+|`POST /cgi/bin/tag/addtagusers`|[增加标签成员](https://work.weixin.qq.com/api/doc#90000/90135/90214) -`execTagDeleteUsers`|TODO|TODO|+|`POST /cgi/bin/tag/deltagusers`|[删除标签成员](https://work.weixin.qq.com/api/doc#90000/90135/90215) -`execTagList`|TODO|TODO|+|`GET /cgi/bin/tag/list`|[获取标签列表](https://work.weixin.qq.com/api/doc#90000/90135/90216) +`execTagDelete`|TODO|TODO|+|`GET /cgi-bin/tag/delete`|[删除标签](https://work.weixin.qq.com/api/doc#90000/90135/90212) +`execTagListUsers`|TODO|TODO|+|`GET /cgi-bin/tag/get`|[获取标签成员](https://work.weixin.qq.com/api/doc#90000/90135/90213) +`execTagAddUsers`|TODO|TODO|+|`POST /cgi-bin/tag/addtagusers`|[增加标签成员](https://work.weixin.qq.com/api/doc#90000/90135/90214) +`execTagDeleteUsers`|TODO|TODO|+|`POST /cgi-bin/tag/deltagusers`|[删除标签成员](https://work.weixin.qq.com/api/doc#90000/90135/90215) +`execTagList`|TODO|TODO|+|`GET /cgi-bin/tag/list`|[获取标签列表](https://work.weixin.qq.com/api/doc#90000/90135/90216) # 异步批量接口 @@ -128,10 +132,8 @@ Name|Request Type|Response Type|Access Token|URL|Doc Name|Request Type|Response Type|Access Token|URL|Doc :---|------------|-------------|------------|:--|:-- -`execAppchatListGet`|`reqAppchatList`|`respAppchatList`|+|`POST /cgi-bin/externalcontact/groupchat/list`|[获取客户群列表](https://developer.work.weixin.qq.com/document/path/92120) -`execAppchatInfoGet`|`reqAppchatInfo`|`respAppchatInfo`|+|`POST /cgi-bin/externalcontact/groupchat/get`|[获取客户群详细](https://developer.work.weixin.qq.com/document/path/92122) `execAppchatCreate`|`reqAppchatCreate`|`respAppchatCreate`|+|`POST /cgi-bin/appchat/create`|[创建群聊会话](https://work.weixin.qq.com/api/doc#90000/90135/90245) -`execAppchatUpdate`|TODO|TODO|+|`POST /cgi-bin/appchat/update`|[修改群聊会话](https://work.weixin.qq.com/api/doc#90000/90135/90246) +`execAppchatUpdate`|`reqAppchatUpdate`|`respAppchatUpdate`|+|`POST /cgi-bin/appchat/update`|[修改群聊会话](https://work.weixin.qq.com/api/doc#90000/90135/90246) `execAppchatGet`|`reqAppchatGet`|`respAppchatGet`|+|`GET /cgi-bin/appchat/get`|[获取群聊会话](https://work.weixin.qq.com/api/doc#90000/90135/90247) `execMessageSend`|`reqMessage`|`respMessageSend`|+|`POST /cgi-bin/message/send`|[发送应用消息](https://work.weixin.qq.com/api/doc#90000/90135/90236) `execAppchatSend`|`reqMessage`|`respMessageSend`|+|`POST /cgi-bin/appchat/send`|[应用推送消息](https://work.weixin.qq.com/api/doc#90000/90135/90248) @@ -157,6 +159,9 @@ Name|Request Type|Response Type|Access Token|URL|Doc `execOAApplyEvent`|`reqOAApplyEvent`|`respOAApplyEvent`|+|`POST /cgi-bin/oa/applyevent`|[提交审批申请](https://work.weixin.qq.com/api/doc/90000/90135/91853) `execOAGetApprovalInfo`|`reqOAGetApprovalInfo`|`respOAGetApprovalInfo`|+|`POST /cgi-bin/oa/getapprovalinfo`|[批量获取审批单号](https://work.weixin.qq.com/api/doc/90000/90135/91816) `execOAGetApprovalDetail`|`reqOAGetApprovalDetail`|`respOAGetApprovalDetail`|+|`POST /cgi-bin/oa/getapprovaldetail`|[获取审批申请详情](https://work.weixin.qq.com/api/doc/90000/90135/91983) +`execOAGetCorpVacationConf`| `reqOAGetCorpVacationConf` | `respOAGetCorpVacationConf` |+| `GET /cgi-bin/oa/vacation/getcorpconf` |[获取企业假期管理配置](https://developer.work.weixin.qq.com/document/path/93375) +`execOAGetUserVacationQuota`| `reqOAGetUserVacationQuota` | `respOAGetUserVacationQuota` |+| `POST /cgi-bin/oa/vacation/getuservacationquota` |[获取成员假期余额](https://developer.work.weixin.qq.com/document/path/93376) +`execOASetOneUserVacationQuota`| `reqOASetOneUserVacationQuota` | `respOASetOneUserVacationQuota` |+| `POST /cgi-bin/oa/vacation/setoneuserquota` |[修改成员假期余额](https://developer.work.weixin.qq.com/document/path/93377) # 企业支付 @@ -183,7 +188,7 @@ Name|Request Type|Response Type|Access Token|URL|Doc `execMsgAuditCheckRoomAgree`|`reqMsgAuditCheckRoomAgree`|`respMsgAuditCheckRoomAgree`|+|`POST /cgi-bin/msgaudit/check_room_agree`|[获取会话同意情况(群聊)](https://work.weixin.qq.com/api/doc/90000/90135/91782) `execMsgAuditGetGroupChat`|`reqMsgAuditGetGroupChat`|`respMsgAuditGetGroupChat`|+|`POST /cgi-bin/msgaudit/groupchat/get`|[获取会话内容存档内部群信息](https://work.weixin.qq.com/api/doc/90000/90135/92951) -# 企业服务人员管理 - 联系我 +# 企业服务人员管理 - 联系我与客户入群方式 ## API calls @@ -196,6 +201,20 @@ Name|Request Type|Response Type|Access Token|URL|Doc `execUpdateContactWayExternalContact`|`reqUpdateContactWayExternalContact`|`respUpdateContactWayExternalContact`|+|`POST /cgi-bin/externalcontact/update_contact_way`|[更新企业已配置的「联系我」成员配置](https://developer.work.weixin.qq.com/document/path/92572#%E6%9B%B4%E6%96%B0%E4%BC%81%E4%B8%9A%E5%B7%B2%E9%85%8D%E7%BD%AE%E7%9A%84%E3%80%8C%E8%81%94%E7%B3%BB%E6%88%91%E3%80%8D%E6%96%B9%E5%BC%8F) `execDelContactWayExternalContact`|`reqDelContactWayExternalContact`|`respDelContactWayExternalContact`|+|`POST /cgi-bin/externalcontact/del_contact_way`|[删除企业已配置的「联系我」方式](https://developer.work.weixin.qq.com/document/path/92572#%E5%88%A0%E9%99%A4%E4%BC%81%E4%B8%9A%E5%B7%B2%E9%85%8D%E7%BD%AE%E7%9A%84%E3%80%8C%E8%81%94%E7%B3%BB%E6%88%91%E3%80%8D%E6%96%B9%E5%BC%8F) `execCloseTempChatExternalContact`|`reqCloseTempChatExternalContact`|`respCloseTempChatExternalContact`|+|`POST /cgi-bin/externalcontact/close_temp_chat`|[结束临时会话](https://developer.work.weixin.qq.com/document/path/92572#%E7%BB%93%E6%9D%9F%E4%B8%B4%E6%97%B6%E4%BC%9A%E8%AF%9D) +`execAddGroupChatJoinWayExternalContact`|`reqAddGroupChatJoinWayExternalContact`|`respAddGroupChatJoinWayExternalContact`|+|`POST /cgi-bin/externalcontact/groupchat/add_join_way`|[配置客户群「加入群聊」方式](https://developer.work.weixin.qq.com/document/path/92229#%E9%85%8D%E7%BD%AE%E5%AE%A2%E6%88%B7%E7%BE%A4%E8%BF%9B%E7%BE%A4%E6%96%B9%E5%BC%8F) +`execGetGroupChatJoinWayExternalContact`|`reqGetGroupChatJoinWayExternalContact`|`respGetGroupChatJoinWayExternalContact`|+|`POST /cgi-bin/externalcontact/groupchat/get_join_way`|[获取企业已配置的客户群「加入群聊」方式](https://developer.work.weixin.qq.com/document/path/92229#%E8%8E%B7%E5%8F%96%E5%AE%A2%E6%88%B7%E7%BE%A4%E8%BF%9B%E7%BE%A4%E6%96%B9%E5%BC%8F%E9%85%8D%E7%BD%AE) +`execUpdateGroupChatJoinWayExternalContact`|`reqUpdateGroupChatJoinWayExternalContact`|`respUpdateGroupChatJoinWayExternalContact`|+|`POST /cgi-bin/externalcontact/groupchat/update_join_way`|[更新企业已配置的客户群「加入群聊」方式](https://developer.work.weixin.qq.com/document/path/92229#%E8%8E%B7%E5%8F%96%E5%AE%A2%E6%88%B7%E7%BE%A4%E8%BF%9B%E7%BE%A4%E6%96%B9%E5%BC%8F%E9%85%8D%E7%BD%AE) +`execDelGroupChatJoinWayExternalContact`|`reqDelGroupChatJoinWayExternalContact`|`respDelGroupChatJoinWayExternalContact`|+|`POST /cgi-bin/externalcontact/groupchat/del_join_way`|[删除企业已配置的客户群「加入群聊」方式](https://developer.work.weixin.qq.com/document/path/92229#%E5%88%A0%E9%99%A4%E5%AE%A2%E6%88%B7%E7%BE%A4%E8%BF%9B%E7%BE%A4%E6%96%B9%E5%BC%8F%E9%85%8D%E7%BD%AE) + +# 客户联系 - 客户群管理 + +## API calls + +Name|Request Type|Response Type|Access Token|URL|Doc +:---|------------|-------------|------------|:--|:-- +`execGroupChatListGet`|`reqGroupChatList`|`respGroupChatList`|+|`POST /cgi-bin/externalcontact/groupchat/list`|[获取客户群列表](https://developer.work.weixin.qq.com/document/path/92120) +`execGroupChatInfoGet`|`reqGroupChatInfo`|`respGroupChatInfo`|+|`POST /cgi-bin/externalcontact/groupchat/get`|[获取客户群详细](https://developer.work.weixin.qq.com/document/path/92122) +`execConvertOpenGIDToChatID`|`reqConvertOpenGIDToChatID`|`respConvertOpenGIDToChatID`|+|`POST /cgi-bin/externalcontact/opengid_to_chatid`|[客户群opengid转换](https://developer.work.weixin.qq.com/document/path/94822) # 在职继承 @@ -215,10 +234,45 @@ Name|Request Type|Response Type|Access Token|URL|Doc `execTransferResignedCustomer`|`reqTransferCustomer`|`respTransferCustomer`|+|`POST /cgi-bin/externalcontact/resigned/transfer_customer`|[离职继承 分配离职成员的客户](https://developer.work.weixin.qq.com/document/path/94081) `execGetTransferResignedCustomerResult`|`reqGetTransferCustomerResult`|`respGetTransferCustomerResult`|+|`POST /cgi-bin/externalcontact/resigned/transfer_result`|[离职继承 查询客户接替状态](https://developer.work.weixin.qq.com/document/path/94082) -# 客户联系-消息推送 +# 客户联系 - 消息推送 ## API calls Name|Request Type|Response Type|Access Token|URL|Doc :---|------------|-------------|------------|:--|:-- `execAddMsgTemplate`|`reqAddMsgTemplateExternalContact`|`respAddMsgTemplateExternalContact`|+|`POST /cgi-bin/externalcontact/add_msg_template`|[创建企业群发](https://developer.work.weixin.qq.com/document/path/92135) `execSendWelcomeMsg`|`reqSendWelcomeMsgExternalContact`|`respSendWelcomeMsgExternalContact`|+|`POST /cgi-bin/externalcontact/send_welcome_msg`|[发送新客户欢迎语](https://developer.work.weixin.qq.com/document/path/92137) + + +# 微信客服 - 客服账号管理 + +## API calls + +Name|Request Type|Response Type|Access Token|URL|Doc +:---|------------|-------------|------------|:--|:-- +`execKfAccountCreate`|`reqKfAccountCreate`|`respKfAccountCreate`|+|`POST /cgi-bin/kf/account/add`|[添加客服账号](https://developer.work.weixin.qq.com/document/path/94662) +`execKfAccountUpdate`|`reqKfAccountUpdate`|`respKfAccountUpdate`|+|`POST /cgi-bin/kf/account/update`|[修改客服账号](https://developer.work.weixin.qq.com/document/path/94664) +`execKfAccountDelete`|`reqKfAccountDelete`|`respKfAccountDelete`|+|`POST /cgi-bin/kf/account/del`|[删除客服账号](https://developer.work.weixin.qq.com/document/path/94663) +`execKfAccountList`|`reqKfAccountList`|`respKfAccountList`|+|`GET /cgi-bin/kf/account/list`|[获取客服账号列表](https://developer.work.weixin.qq.com/document/path/94661) +`execAddKfContact`|`reqAddKfContact`|`respAddKfContact`|+|`POST /cgi-bin/kf/add_contact_way`|[获取客服账号链接](https://developer.work.weixin.qq.com/document/path/94665) + +# 微信客服 - 接待人员管理 + +## API calls + +Name|Request Type|Response Type|Access Token|URL|Doc +:---|------------|-------------|------------|:--|:-- +`execKfServicerCreate`|`reqKfServicerCreate`|`respKfServicerCreate`|+|`POST /cgi-bin/kf/servicer/add`|[添加接待人员](https://developer.work.weixin.qq.com/document/path/94646) +`execKfServicerDelete`|`reqKfServicerDelete`|`respKfServicerDelete`|+|`POST /cgi-bin/kf/servicer/del`|[删除接待人员](https://developer.work.weixin.qq.com/document/path/94647) +`execKfServicerList`|`reqKfServicerList`|`respKfServicerList`|+|`GET /cgi-bin/kf/servicer/list`|[获取接待人员列表](https://developer.work.weixin.qq.com/document/path/94645) + +# 微信客服 - 会话分配与消息收发 + +## API calls + +Name|Request Type|Response Type|Access Token|URL|Doc +:---|------------|-------------|------------|:--|:-- +`execKfServiceStateGet`|`reqKfServiceStateGet`|`respKfServiceStateGet`|+|`POST /cgi-bin/kf/service_state/get`|[获取会话状态](https://developer.work.weixin.qq.com/document/path/94669) +`execKfServiceStateTrans`|`reqKfServiceStateTrans`|`respKfServiceStateTrans`|+|`POST /cgi-bin/kf/service_state/trans`|[变更会话状态](https://developer.work.weixin.qq.com/document/path/94669) +`execKfSyncMsg`|`reqKfSyncMsg`|`respKfSyncMsg`|+|`POST /cgi-bin/kf/sync_msg`|[读取消息](https://developer.work.weixin.qq.com/document/path/94670) +`execKfSend`|`reqMessage`|`respMessageSend`|+|`POST /cgi-bin/kf/send_msg`|[发送消息](https://developer.work.weixin.qq.com/document/path/94677) +`execKfOnEventSend`|`reqMessage`|`respMessageSend`|+|`POST /cgi-bin/kf/send_msg_on_event`|[发送欢迎语等事件响应消息](https://developer.work.weixin.qq.com/document/path/95122) diff --git a/docs/chat_info.md b/docs/chat_info.md index 4eac4ff..288a253 100644 --- a/docs/chat_info.md +++ b/docs/chat_info.md @@ -26,18 +26,18 @@ Name|JSON|Type|Doc `Cursor`|`cursor`|`string`| 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用不填 `Limit`|`limit`|`int64`| 分页,预期请求的数据量,取值范围 1 ~ 1000 -### `RespGroupChatList` 客户群列表数据 +### `GroupChatList` 客户群列表数据 Name|JSON|Type|Doc :---|:---|:---|:-- `ChatID`|`chat_id`|`string`| 客户群ID `Status`|`status`|`int64`| 客户群跟进状态 0 - 跟进人正常 1 - 跟进人离职 2 - 离职继承中 3 - 离职继承完成 -### `RespAppchatList` 客户群列表结果 +### `RespGroupChatList` 客户群列表结果 Name|JSON|Type|Doc :---|:---|:---|:-- -`GroupChatList`|`group_chat_list`|`[]RespGroupChatList`| 客户群列表 +`GroupChatList`|`group_chat_list`|`[]GroupChatList`| 客户群列表 `NextCursor`|`next_cursor`|`string`| 分页游标 ### `ChatMemberList` 客户群成员列表 @@ -65,7 +65,7 @@ Name|JSON|Type|Doc :---|:---|:---|:-- `UserID`|`userid`|`string`| 管理员ID -### `RespAppChatInfo` 客户群详情 +### `RespGroupChatInfo` 客户群详情 Name|JSON|Type|Doc :---|:---|:---|:-- `ChatID`|`chat_id`|`string`| 客户群ID @@ -79,4 +79,9 @@ Name|JSON|Type|Doc ```go // ChatNeedName 是否需要返回群成员的名字 0-不返回;1-返回。默认不返回 const ChatNeedName int64 = 1 + +// RespAppchatList 企业微信接口调整 兼容处理 +type RespAppchatList = RespGroupChatList +// RespAppChatInfo 企业微信接口调整 兼容处理 +type RespAppChatInfo = RespGroupChatInfo ``` diff --git a/docs/external_contact.md b/docs/external_contact.md index 25f34fa..e1fd638 100644 --- a/docs/external_contact.md +++ b/docs/external_contact.md @@ -252,6 +252,18 @@ Name|JSON|Type|Doc `UnionID`|`unionid`|`string`| 可进行临时会话的客户UnionID,该参数仅在is_temp为true时有效,如不指定则不进行限制 `Conclusions`|`conclusions`|`Conclusions`| 结束语,会话结束时自动发送给客户,可参考“结束语定义”,仅在is_temp为true时有效, +### `ExternalGroupChatJoinWay` 配置客户群「加入群聊」方式 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Scene`|`scene`|`int`| 场景,1 - 群的小程序插件,2 - 群的二维码插件 +`Remark`|`remark`|`string`| 联系方式的备注信息,用于助记,超过30个字符将被截断 +`AutoCreateRoom`|`auto_create_room`|`int`| 当群满了后,是否自动新建群。0-否;1-是。 默认为1 +`RoomBaseName`|`room_base_name`|`string`| 自动建群的群名前缀,当auto_create_room为1时有效。最长40个utf8字符 +`RoomBaseID`|`room_base_id`|`int`| 自动建群的群起始序号,当auto_create_room为1时有效 +`ChatIDs`|`chat_id_list`|`[]string`| 使用该配置的客户群ID列表,支持5个。 +`State`|`state`|`string`| 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符 + ### `Conclusions` 结束语,会话结束时自动发送给客户 Name|JSON|Type|Doc diff --git a/docs/kf.md b/docs/kf.md new file mode 100644 index 0000000..9018fd0 --- /dev/null +++ b/docs/kf.md @@ -0,0 +1,122 @@ +# 客服 + +## Models + +### `KfAccount` 客服账号 + + Name | JSON | Type | Doc +:------------------|:-----------------------------|:---------|:--------------------------------------------------------- + `OpenKfID` | `open_kfid` | `string` | 客服账号ID + `Name` | `name` | `string` | 客服名称 + `Avatar` | `avatar` | `string` | 客服头像URL + `ManagePrivilege` | `manage_privilege,omitempty` | `bool` | 当前调用接口的应用身份,是否有该客服账号的管理权限(编辑客服账号信息、分配会话和收发消息)。组件应用不返回此字段 + +### `KfServicer` 客服接待人员 + + Name | JSON | Type | Doc +:---------------|:--------------------------|:---------|:-------------------------------------------- + `UserID` | `userid,omitempty` | `string` | 接待人员的userid。第三方应用获取到的为密文userid,即open_userid + `Status` | `status` | `int` | 接待人员的接待状态。0:接待中,1:停止接待。 + `StopType` | `stop_type` | `int` | 接待人员的接待状态为「停止接待」的子类型。0:停止接待,1:暂时挂起 + `DepartmentID` | `department_id,omitempty` | `int64` | 接待人员部门的id + +### `KfServicerResult` 接待人员数据 + + Name | JSON | Type | Doc +:---------------|:--------------------------|:---------|:------------ + `UserID` | `userid,omitempty` | `string` | 接待人员的userid + `DepartmentID` | `department_id,omitempty` | `int64` | 接待人员部门的id + `ErrCode` | `errcode` | `int64` | 该条记录的结果 + `ErrMsg` | `errmsg` | `string` | 结果信息 + +### `KfMsg` 客服消息数据 + + Name | JSON | Type | Doc +:---------------|:----------------------------|:--------------|:------------ +`MsgID` | `msgid,omitempty` | `string` | 消息ID +`OpenKfID`| `open_kfid,omitempty` | `string` |客服账号ID(msgtype为event,该字段不返回) +`ExternalUserID`| `external_userid,omitempty` | `string` |客客户UserID(msgtype为event,该字段不返回) +`SendTime` | `send_time,omitempty` | `int64` | 消息发送时间 +`Origin` | `origin,omitempty` | `int` | 消息来源。3-微信客户发送的消息 4-系统推送的事件消息 5-接待人员在企业微信客户端发送的消息 +`ServicerUserID`| `servicer_userid,omitempty` | `string` |从企业微信给客户发消息的接待人员userid(即仅origin为5才返回;msgtype为event,该字段不返回) +`MsgType` | `msgtype` | `MessageType` | 消息类型 +`Text` | `text,omitempty` | `Text` | 文本消息 +`Image` | `image,omitempty` | `Image` | 图片消息 +`Link` | `link,omitempty` | `Link` | 链接消息 +`MiniProgram` | `mini_program,omitempty` | `MiniProgram` | 小程序消息 +`Event` | `event,omitempty` | `KfEvent` | 事件类型 + +### `KfEvent` 客服会话事件 + +Name|JSON|Type|Doc +:---|:--|:---|:-- +`EventType`|`event_type`|`KfEventType`|事件类型 +`OpenKfID`|`open_kfid`|`string`|客服账号ID +`ExternalUserID`|`external_userid,omitempty`|`string`|客户UserID,注意不是企业成员的帐号 +`ServicerUserID`|`servicer_userid,omitempty`|`string`|接待人员userid +`Scene`|`scene,omitempty`|`string`|用户进入会话事件特有。进入会话的场景值,获取客服账号链接开发者自定义的场景值 +`SceneParam`|`scene_param,omitempty`|`string`|用户进入会话事件特有。进入会话的自定义参数,获取客服账号链接返回的url,开发者按规范拼接的scene_param参数 +`WelcomeCode`|`welcome_code,omitempty`|`string`|用户进入会话事件特有。如果满足发送欢迎语条件(条件为:用户在过去48小时里未收过欢迎语,且未向客服发过消息),会返回该字段。可用该welcome_code调用发送事件响应消息接口给客户发送欢迎语。 +`WechatChannels`|`wechat_channels,omitempty`|`KfWechatChannels`|用户进入会话事件特有。进入会话的视频号信息,从视频号进入会话才有值 +`FailMsgID`|`fail_msgid,omitempty`|`string`|消息发送失败事件特有。发送失败的消息msgid +`FailType`|`fail_type,omitempty`|`int`|消息发送失败事件特有。失败类型。0-未知原因 1-客服账号已删除 2-应用已关闭 4-会话已过期,超过48小时 5-会话已关闭 6-超过5条限制 8-主体未验证 10-用户拒收 11-企业未有成员登录企业微信App(排查方法:企业至少一个成员通过手机号验证/微信授权登录企业微信App即可)12-发送的消息为客服组件禁发的消息类型 +`Status`|`status,omitempty`|`int`|接待人员接待状态变更事件特有。状态类型。1-接待中 2-停止接待 +`StopType`|`stop_type,omitempty`|`int`|接待人员接待状态变更事件特有。接待人员的状态为「停止接待」的子类型。0:停止接待,1:暂时挂起 +`ChangeType`|`change_type,omitempty`|`KfServiceState`|会话状态变更事件特有。变更类型,均为接待人员在企业微信客户端操作触发。1-从接待池接入会话 2-转接会话 3-结束会话 4-重新接入已结束/已转接会话 +`OldServicerUserID`|`old_servicer_userid,omitempty`|`string`|会话状态变更事件特有。老的接待人员userid。仅change_type为2、3和4有值 +`NewServicerUserid`|`new_servicer_userid,omitempty`|`string`|会话状态变更事件特有。新的接待人员userid。仅change_type为1、2和4有值 +`MsgCode`|`msg_code,omitempty`|`string`|会话状态变更事件特有。用于发送事件响应消息的code,仅change_type为1和3时,会返回该字段。可用该msg_code调用发送事件响应消息接口给客户发送回复语或结束语。 +`RecallMsgID`|`recall_msgid,omitempty`|`string`|撤回消息事件特有。 撤回的消息msgid +`RejectSwitch`|`reject_switch,omitempty`|`int`|拒收客户消息变更事件特有。 拒收客户消息,1表示接待人员拒收了客户消息,0表示接待人员取消拒收客户消息 + +### `KfWechatChannels` 进入会话的视频号信息,从视频号进入会话才有值 + + Name | JSON | Type | Doc +:---------------|:--------------------------|:---------|:------------ + `NickName` | `nickname,omitempty` | `string` | 视频号名称,视频号场景值为1、2、3时返回此项 + `ShopNickName` | `shop_nickname,omitempty` | `string` | 视频号小店名称,视频号场景值为4、5时返回此项 + `Scene` | `scene` | `int64` | 视频号场景值。1:视频号主页,2:视频号直播间商品列表页,3:视频号商品橱窗页,4:视频号小店商品详情页,5:视频号小店订单页 + +```go +// KfEventType 事件类型 +type KfEventType string + +const ( + // KfEventTypeEnterSession 用户进入会话事件 + KfEventTypeEnterSession KfEventType = "enter_session" + // KfEventTypeMsgSendFail 消息发送失败事件 + KfEventTypeMsgSendFail KfEventType = "msg_send_fail" + // KfEventTypeServicerStatusChange 接待人员接待状态变更事件 + KfEventTypeServicerStatusChange KfEventType = "servicer_status_change" + // KfEventTypeSessionStatusChange 会话状态变更事件 + KfEventTypeSessionStatusChange KfEventType = "session_status_change" + // KfEventTypeUserRecallMsg 用户撤回消息事件 + KfEventTypeUserRecallMsg KfEventType = "user_recall_msg" + // KfEventTypeServicerRecallMsg 接待人员撤回消息事件 + KfEventTypeServicerRecallMsg KfEventType = "servicer_recall_msg" + // KfEventTypeRejectCustomerMsgSwitchChange 拒收客户消息变更事件 + KfEventTypeRejectCustomerMsgSwitchChange KfEventType = "reject_customer_msg_switch_change" +) + +// KfServiceState 客服会话状态 +// +// 0 未处理 新会话接入 +// 1 由智能助手接待 +// 2 待接入池排队中 +// 3 由人工接待 +// 4 已结束/未开始 +type KfServiceState int + +const ( + // KfServiceStateUntreated 未处理 新会话接入 + KfServiceStateUntreated KfServiceState = iota + // KfServiceStateRobotReception 由智能助手接待 + KfServiceStateRobotReception + // KfServiceStateInQueue 待接入池排队中 + KfServiceStateInQueue + // KfServiceStateManualReception 由人工接待 + KfServiceStateManualReception + // KfServiceStateFinished 已结束/未开始 + KfServiceStateFinished +) +``` diff --git a/docs/oa.md b/docs/oa.md index 7fd1965..beff8ba 100644 --- a/docs/oa.md +++ b/docs/oa.md @@ -61,13 +61,17 @@ Name|JSON|Type|Doc `Selector`|`selector`|`OAContentSelector`| 单选/多选控件(control参数为Selector) `Members`|`members`|`[]OAContentMember`| 成员控件(control参数为Contact,且value参数为members) `Departments`|`departments`|`[]OAContentDepartment`| 部门控件(control参数为Contact,且value参数为departments) +`Tips`|`new_tips`|`OATemplateControlConfigTips`| 说明文字控件(control参数为Tips) `Files`|`files`|`[]OAContentFile`| 附件控件(control参数为File,且value参数为files) `Table`|`children`|`[]OAContentTableList`| 明细控件(control参数为Table) `Vacation`|`vacation`|`OAContentVacation`| 假勤组件-请假组件(control参数为Vacation) +`Attendance`|`attendance`|`OAContentVacationAttendance`| 假勤组件-出差/外出/加班组件(control参数为Attendance) +`PunchCorrection`|`punch_correction`|`OAContentPunchCorrection`| 假勤组件-出差/外出/加班组件(control参数为Attendance) `Location`|`location`|`OAContentLocation`| 位置控件(control参数为Location,且value参数为location) `RelatedApproval`|`related_approval`|`[]OAContentRelatedApproval`| 关联审批单控件(control参数为RelatedApproval,且value参数为related_approval) `Formula`|`formula`|`OAContentFormula`| 公式控件(control参数为Formula,且value参数为formula) `DateRange`|`date_range`|`OAContentDateRange`| 时长组件(control参数为DateRange,且value参数为date_range) +`BankAccount`|`bank_account`|`OAContentBankAccount`| 收款账户控件(control参数为BankAccount) ### `OAContentDate` 日期/日期+时间内容 @@ -88,6 +92,7 @@ Name|JSON|Type|Doc Name|JSON|Type|Doc :---|:---|:---|:-- `Key`|`key`|`string`| 选项key,可通过“获取审批模板详情”接口获得 +`Value`|`value`|`[]OAText`| 选项值,若配置了多语言则会包含中英文的选项值 ### `OAContentMember` 所选成员内容,即申请人在此控件选择的成员,多选模式下可以有多个 @@ -128,6 +133,7 @@ Name|JSON|Type|Doc :---|:---|:---|:-- `DateRange`|`date_range`|`OAContentVacationAttendanceDateRange`| 假勤组件时间选择范围 `Type`|`type`|`uint8`| 假勤组件类型:1-请假;3-出差;4-外出;5-加班 +`SliceInfo`|`slice_info`|`OAContentVacationAttendanceSliceInfo`| 时长支持按天分片信息, 2020/10/01之前的历史表单不支持时长分片 ### `OAContentVacationAttendanceDateRange` 假勤组件时间选择范围 @@ -136,6 +142,30 @@ Name|JSON|Type|Doc `Type`|`type`|`string`| 时间展示类型:day-日期;hour-日期+时间 ``|``|`OAContentDateRange`| 时长范围 +### `OAContentVacationAttendanceSliceInfo` 假勤组件时长支持按天分片信息, 2020/10/01之前的历史表单不支持时长分片 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Duration`|`duration`|`uint64`| 总时长,单位是秒 +`State`|`state`|`uint8`| 时长计算来源类型: 1--系统自动计算;2--用户修改 +`DayItems`|`day_items`|`[]OAContentVacationAttendanceSliceInfoDayItem`| 时长计算来源类型: 1--系统自动计算;2--用户修改 + +### `OAContentVacationAttendanceSliceInfoDayItem` 假勤组件时长支持按天分片信息,每一天的分片时长信息 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Daytime`|`daytime`|`uint64`| 日期的00:00:00时间戳,Unix时间 +`Duration`|`duration`|`uint64`| 分隔当前日期的时长秒数 + +### `OAContentPunchCorrection` 补卡组件 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`State`|`state`|`string`| 异常状态说明 +`Time`|`time`|`uint64`| 补卡时间,Unix时间戳 +`Version`|`version`|`uint8`| 版本标识,为1的时候为新版补卡,daymonthyear有值 +`Daymonthyear`|`daymonthyear`|`uint64`| 补卡日期0点Unix时间戳 + ### `OAContentLocation` 位置控件 Name|JSON|Type|Doc @@ -162,10 +192,42 @@ Name|JSON|Type|Doc Name|JSON|Type|Doc :---|:---|:---|:-- +`Type`|`type`|`string`| 时间展示类型:halfday-日期;hour-日期+时间 `NewBegin`|`new_begin`|`int`| 开始时间,unix时间戳 `NewEnd`|`new_end`|`int`| 结束时间,unix时间戳 `NewDuration`|`new_duration`|`int`| 时长范围,单位秒 +`PerdayDuration`|`perday_duration`|`int`| 每天的工作时长 +`TimezoneInfo`|`timezone_info`|`*OAContentDateRangeTimezoneInfo`|时区信息,只有在非UTC+8的情况下会返回 + +### `OAContentDateRangeTimezoneInfo` 时区信息 +Name|JSON|Type|Doc +:---|:---|:---|:-- +`ZoneOffset`|`zone_offset`|`string`|时区偏移量 +`ZoneDesc`|`zone_desc`|`string`|时区描述 + +### `OAContentBankAccount` 时长组件 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`AccountType`|`account_type`|`uint8`| 账户类型 :1:对公账户,2:个人账户 +`AccountName`|`account_name`|`string`| 账户名 +`AccountNumber`|`account_number`|`string`| 账号 +`Remark`|`remark`|`string`| 备注 +`Bank`|`bank`|`OAContentBankAccountBank`| 银行信息 + +### `OAContentBankAccountBank` 银行信息 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`BankAlias`|`bank_alias`|`string`|银行名称 +`BankAliasCode`|`bank_alias_code`|`string`|银行代码 +`Province`|`province`|`string`|省份 +`ProvinceCode`|`province_code`|`uint8`|省份代码 +`City`|`city`|`string`|城市 +`CityCode`|`city_code`|`uint8`|城市代码 +`BankBranchName`|`bank_branch_name`|`string`|银行支行 +`BankBranchId`|`bank_branch_id`|`string`|银行支行联行号 ### `OATemplateDetail` 审批模板详情 @@ -207,7 +269,9 @@ Name|JSON|Type|Doc `Selector`|`selector`|`OATemplateControlConfigSelector`| Selector控件(单选/多选控件) `Contact`|`contact`|`OATemplateControlConfigContact`| Contact控件(成员/部门控件) `Table`|`table`|`OATemplateControlConfigTable`| Table(明细控件) -`Attendance`|`attendance`|`OATemplateControlConfigAttendance`| Attendance控件(假勤控件) +`Attendance`|`attendance`|`OATemplateControlConfigAttendance`| Attendance控件(假勤控件)【出差】【加班】【外出】模板特有的控件 +`Vacation`|`vacation_list`|`OATemplateControlConfigVacation`| Vacation控件(假勤控件)【请假】模板特有控件, 请假类型强关联审批应用中的假期管理。 +`Tips`|`tips`|`OATemplateControlConfigTips`| Tips控件(说明文字控件) ### `OATemplateControlConfigDate` 类型标志,日期/日期+时间控件的config中会包含此参数 @@ -268,6 +332,52 @@ Name|JSON|Type|Doc `ID`|`id`|`int`| 假期类型标识id `Name`|`name`|`[]OAText`| 假期类型名称,默认zh_CN中文名称 +### `OATemplateControlConfigTips` 类型标志,说明文字控件的config中会包含此参数 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`TipsContent`|`tips_content`|`[]OATemplateControlConfigTipsContent`| 说明文字数组,元素为不同语言的富文本说明文字 + +### `OATemplateControlConfigTipsContent` 类型标志,说明文字控件的config中会包含此参数 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Text`|`text`|`OATemplateControlConfigTipsContentText`| 某个语言的富文本说明文字数组,元素为不同文本类型的说明文字分段 +`Lang`|`lang`|`string`| 语言类型 + +### `OATemplateControlConfigTipsContentText` 类型标志,说明文字控件的config中会包含此参数 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`SubText`|`sub_text`|`[]OATemplateControlConfigTipsContentSubText`| 说明文字分段 + +### `OATemplateControlConfigTipsContentSubText` 类型标志,说明文字控件的config中会包含此参数 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Type`|`type`|`uint8`| 文本类型 1:纯文本 2:链接,每个说明文字中只支持包含一个链接 +`Content`|`content`|`OATemplateControlConfigTipsContentSubTextContent`| 内容 + +### `OATemplateControlConfigTipsContentSubTextContent` 类型标志,说明文字控件的config中会包含此参数 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Text`|`plain_text`|`*OATemplateControlConfigTipsContentSubTextContentPlain`| 纯文本类型的内容 +`Lang`|`link`|`*OATemplateControlConfigTipsContentSubTextContentLink`| 链接类型的内容 + +### `OATemplateControlConfigTipsContentSubTextContentPlain` 类型标志,说明文字控件的config中会包含此参数 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Content`|`content`|`string`| 纯文本文字 + +### `OATemplateControlConfigTipsContentSubTextContentLink` 类型标志,说明文字控件的config中会包含此参数 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`Title`|`title`|`string`| 链接标题 +`URL`|`url`|`string`| 链接url + ```go // OAControl 控件类型 type OAControl string @@ -412,3 +522,72 @@ const OAApprovalInfoFilterKeyDepartment OAApprovalInfoFilterKey = "department" // OAApprovalInfoFilterKeySpStatus 审批状态 const OAApprovalInfoFilterKeySpStatus OAApprovalInfoFilterKey = "sp_status" ``` + +### `CorpVacationConf` 企业假期管理配置 + +Name|JSON|Type|Doc +:---|:---|:---|:-- +`ID`|`id`|`uint32`| 假期id +`Name`|`name`|`string`| 假期名称 +`TimeAttr`|`time_attr`|`uint32`| 假期时间刻度:0-按天请假;1-按小时请假 +`DurationType`|`duration_type`|`uint32`| 时长计算类型:0-自然日;1-工作日 +`QuotaAttr`|`quota_attr`|`CorpVacationConfQuotaAttr`| 假期发放相关配置 +`PerdayDuration`|`perday_duration`|`uint32`| 单位换算值,即1天对应的秒数,可将此值除以3600得到一天对应的小时。 +`IsNewovertime`|`is_newovertime`|`*uint32`| 是否关联加班调休,0-不关联,1-关联,关联后改假期类型变为调休假 +`EnterCompTimeLimit`|`enter_comp_time_limit`|`*uint32`| 入职时间大于n个月可用该假期,单位为月 +`ExpireRule`|`expire_rule`|`*CorpVacationConfExpireRule`| 假期过期规则 + +### `CorpVacationConfQuotaAttr` 企业假期管理配置-假期发放相关配置 + +Name|JSON|Type|Doc +:---|:---|:-------------|:-- +`Type`|`type`|`uint32`| 假期发放类型:0-不限额;1-自动按年发放;2-手动发放;3-自动按月发放 +`AutoresetTime`|`autoreset_time`|`uint32`| 自动发放时间戳,若假期发放为自动发放,此参数代表自动发放日期。注:返回时间戳的年份是无意义的,请只使用返回时间的月和日;若at_entry_date为true,该字段则无效,假期发放时间为员工入职时间 +`AutoresetDuration`|`autoreset_duration`|`uint32`| 自动发放时长,单位为秒。注:只有自动按年发放和自动按月发放时有效,若选择了按照工龄和司龄发放,该字段无效,发放时长请使用区间中的quota +`QuotaRuleType`|`quota_rule_type`|`*uint32`| 额度计算类型,自动按年发放时有效,0-固定额度;1-按工龄计算;2-按司龄计算 +`QuotaRules`|`quota_rules`|`*CorpVacationConfQuotaRules`| 额度计算规则,自动按年发放时有效 +`AtEntryDate`|`at_entry_date`|`*bool`| 是否按照入职日期发放假期,只有在自动按年发放类型有效,选择后发放假期的时间会成为员工入职的日期 +`AutoResetMonthDay`|`auto_reset_month_day`|`*uint32`| 自动按月发放的发放时间,只有自动按月发放类型有效 + +### `CorpVacationConfQuotaRules` 企业假期管理配置-额度计算规则 + +Name|JSON|Type|Doc +:---|:---|:-------------|:-- +`List`|`list`|`[]CorpVacationConfQuotaRule`| 额度计算规则区间,只有在选择了按照工龄计算或者按照司龄计算时有效 + +### `CorpVacationConfQuotaRule` 企业假期管理配置-额度计算规则区间 + +Name|JSON|Type|Doc +:---|:---|:-------------|:-- +`Quota`|`quota`|`uint32`| 区间发放时长,单位为s +`Begin`|`begin`|`uint32`| 区间开始点,单位为年 +`End`|`end`|`uint32`| 区间结束点,无穷大则为0,单位为年 +`BasedOnActualWorkTime`|`based_on_actual_work_time`|`bool`| 是否根据实际入职时间计算假期,选择后会根据员工在今年的实际工作时间发放假期 + +### `CorpVacationConfExpireRule` 企业假期管理配置-假期过期规则 + +Name|JSON|Type|Doc +:---|:---|:-------------|:-- +`Type`|`type`|`uint32`| 过期规则类型,1-按固定时间过期,2-从发放日按年过期,3-从发放日按月过期,4-不过期 +`Duration`|`duration`|`uint64`| 有效期,按年过期为年,按月过期为月,只有在以上两种情况时有效 +`Date`|`date`|`CorpVacationConfDate`| 失效日期,只有按固定时间过期时有效 +`ExternDurationEnable`|`extern_duration_enable`|`bool`| 是否允许延长有效期 +`ExternDuration`|`extern_duration`|`CorpVacationConfDate`| 延长有效期的具体时间,只有在extern_duration_enable为true时有效 + +### `CorpVacationConfDate` 企业假期管理配置-失效日期 + +Name|JSON|Type|Doc +:---|:---|:-------------|:-- +`Month`|`month`|`uint32`| 月份 +`Day`|`day`|`uint32`| 日 + +### `UserVacationQuota` 假期列表 + +Name|JSON|Type|Doc +:---|:---|:-------------|:-- +`ID`|`id`|`uint32`| 假期id +`AssignDuration`|`assignduration`|`uint32`| 发放时长,单位为秒 +`UsedDuration`|`usedduration`|`uint32`| 使用时长,单位为秒 +`LeftDuration`|`leftduration`|`uint32`| 剩余时长,单位为秒 +`VacationName`|`vacationname`|`string`| 假期名称 +`RealAssignDuration`|`real_assignduration`|`uint32`| 假期的实际发放时长,通常在设置了按照实际工作时间发放假期后进行计算 diff --git a/docs/rx_msg.md b/docs/rx_msg.md index c4d72e9..bb48b59 100644 --- a/docs/rx_msg.md +++ b/docs/rx_msg.md @@ -55,6 +55,9 @@ const EventTypeSysApprovalChange EventType = "sys_approval_change" // EventTypeChangeContact 通讯录回调通知 const EventTypeChangeContact EventType = "change_contact" +// EventTypeKfMsgOrEvent 客服回调通知 +const EventTypeKfMsgOrEvent EventType = "kf_msg_or_event" + // ChangeType 变更类型 type ChangeType string @@ -297,6 +300,13 @@ Name|XML|Type|Doc :---|:--|:---|:-- `EventKey`|`EventKey`|`string`|事件key +### `rxEventKfMsgOrEvent` 接受的事件消息,客服接收消息和事件 + +Name|XML|Type|Doc +:---|:--|:---|:-- +`OpenKfID`|`OpenKfId`|`string`|有新消息的客服账号。可通过sync_msg接口指定open_kfid获取此客服账号的消息 +`Token`|`Token`|`string`|调用拉取消息接口时,需要传此token,用于校验请求的合法性 + ### `rxEventUnknown` 接受的事件消息,未定义的事件类型 Name|XML|Type|Doc diff --git a/dummy_for_generate.go b/dummy_for_generate.go index b9e047b..c66be56 100644 --- a/dummy_for_generate.go +++ b/dummy_for_generate.go @@ -4,6 +4,7 @@ package workwx //go:generate go run --tags sdkcodegen ./internal/sdkcodegen ./docs/chat_info.md ./chat_info.md.go //go:generate go run --tags sdkcodegen ./internal/sdkcodegen ./docs/dept_info.md ./dept_info.md.go //go:generate go run --tags sdkcodegen ./internal/sdkcodegen ./docs/external_contact.md ./external_contact.md.go +//go:generate go run --tags sdkcodegen ./internal/sdkcodegen ./docs/kf.md ./kf.md.go //go:generate go run --tags sdkcodegen ./internal/sdkcodegen ./docs/user_info.md ./user_info.md.go //go:generate go run --tags sdkcodegen ./internal/sdkcodegen ./docs/oa.md ./oa.md.go //go:generate go run --tags sdkcodegen ./internal/sdkcodegen ./docs/rx_msg.md ./rx_msg.md.go diff --git a/errcodes/mod.go b/errcodes/mod.go index 144fdc6..85dfb50 100644 --- a/errcodes/mod.go +++ b/errcodes/mod.go @@ -5,7 +5,7 @@ package errcodes // ErrCode 错误码类型 // // 全局错误码文档: https://developer.work.weixin.qq.com/document/path/90313 -// 文档爬取时间: 2023-07-06 15:39:31 +0800 +// 文档爬取时间: 2024-04-08 19:03:10 +0800 // // NOTE: 关于错误码的名字为何如此无聊: // @@ -325,11 +325,11 @@ const ErrCode40066 ErrCode = 40066 // 排查方法: 标签/标签组ID未指定,或者指定的标签/标签组ID不存在 const ErrCode40068 ErrCode = 40068 -// ErrCode40070 指定的标签范围结点全部无效 +// ErrCode40070 指定的标签范围节点全部无效 // // 排查方法: [查看帮助] // -// 指定的标签范围结点全部无效。确认: +// 指定的标签范围节点全部无效。确认: // 1)指定的参数格式是否正确。比如,"userlist":[ "user1"],而不是指定为 "userlist" : "user1"。 // 2)指定的成员或者部门,是否存在于通讯录中。 // @@ -596,6 +596,21 @@ const ErrCode40142 ErrCode = 40142 // 排查方法: - const ErrCode40143 ErrCode = 40143 +// ErrCode40165 已经升级了客户群ID,无法再次升级 +// +// 排查方法: - +const ErrCode40165 ErrCode = 40165 + +// ErrCode40166 还未升级客户群ID,无法调用接口 +// +// 排查方法: - +const ErrCode40166 ErrCode = 40166 + +// ErrCode40167 指定的升级时间不合法 +// +// 排查方法: - +const ErrCode40167 ErrCode = 40167 + // ErrCode40201 当前操作包含敏感信息,被反垃圾拦截 // // 排查方法: - @@ -606,6 +621,51 @@ const ErrCode40201 ErrCode = 40201 // 排查方法: 微盘接口请检查userid已废弃 const ErrCode40203 ErrCode = 40203 +// ErrCode40204 微信反垃圾 +// +// 排查方法: - +const ErrCode40204 ErrCode = 40204 + +// ErrCode40205 成员微信票据过期 +// +// 排查方法: 请确保成员最近一年内或离职前一年内登录过企业微信并进行微信授权 +const ErrCode40205 ErrCode = 40205 + +// ErrCode40206 请求body字节数超过限制 +// +// 排查方法: - +const ErrCode40206 ErrCode = 40206 + +// ErrCode40207 不合法的tfa_code +// +// 排查方法: - +const ErrCode40207 ErrCode = 40207 + +// ErrCode40208 验证的用户不在二次验证生效范围内 +// +// 排查方法: - +const ErrCode40208 ErrCode = 40208 + +// ErrCode40209 oauth跳转域名与二次验证的域名不匹配 +// +// 排查方法: - +const ErrCode40209 ErrCode = 40209 + +// ErrCode40210 未配置二次验证url +// +// 排查方法: - +const ErrCode40210 ErrCode = 40210 + +// ErrCode40211 不合法的RSA公钥 +// +// 排查方法: - +const ErrCode40211 ErrCode = 40211 + +// ErrCode40212 公钥版本号不能低于旧公钥的版本号 +// +// 排查方法: - +const ErrCode40212 ErrCode = 40212 + // ErrCode41001 缺少access_token参数 // // 排查方法: [查看帮助] @@ -1026,6 +1086,16 @@ const ErrCode41095 ErrCode = 41095 // 排查方法: - const ErrCode41096 ErrCode = 41096 +// ErrCode41098 组件关联应用创建的获客链接授权给了其他组件 +// +// 排查方法: - +const ErrCode41098 ErrCode = 41098 + +// ErrCode41099 不是服务商代支付模式 +// +// 排查方法: - +const ErrCode41099 ErrCode = 41099 + // ErrCode41102 缺少菜单名 // // 排查方法: - @@ -1321,16 +1391,17 @@ const ErrCode45009 ErrCode = 45009 // 排查方法: 设置应用若带有name参数,则不允许为空,且不超过32个字符 const ErrCode45022 ErrCode = 45022 -// ErrCode45024 帐号数量超过上限 +// ErrCode45024 账号数量超过上限 // // 排查方法: [查看帮助] // // 企业人数已达到上限,添加成员失败。 -// 1)管理员可前往管理后台进行[认证],成员可联系管理员认证,认证后可继续添加更多成员。 -// 2)可删除通讯录中的无用帐号,删除后可添加更多成员。 +// 1)若当前企业未认证,管理员可前往管理后台进行[认证],成员可联系管理员认证,认证后可继续添加更多成员。 +// 2)若当前企业已认证或已验证,管理员可前往管理后台申请[扩容],成员可联系管理员扩容,扩容后可继续添加更多成员。 // // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A45024 -// [认证]: https://work.weixin.qq.com/wework_admin/frame#/authCenter +// [认证]: https://work.weixin.qq.com/wework_admin/frame#/authCenter/?from=api +// [扩容]: https://work.weixin.qq.com/wework_admin/frame#/profile/addressBookExpansion/index?from=api const ErrCode45024 ErrCode = 45024 // ErrCode45026 触发删除用户数的保护 @@ -1447,7 +1518,7 @@ const ErrCode48006 ErrCode = 48006 // 排查方法: [查看帮助] // // API接口无权限调用,由于客服账号未授权给该应用。请确认: -// 1) 微信客服相关接口,要求「微信客服->管理帐号、分配会话和收发消息」权限,且授权了对应的客服账号。可在企业管理后台“微信客服”进行权限管理。 +// 1) 微信客服相关接口,要求「微信客服->管理账号、分配会话和收发消息」权限,且授权了对应的客服账号。可在企业管理后台“微信客服”进行权限管理。 // // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A48007 const ErrCode48007 ErrCode = 48007 @@ -1515,6 +1586,11 @@ const ErrCode50002 ErrCode = 50002 // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A50003 const ErrCode50003 ErrCode = 50003 +// ErrCode50006 系统应用已禁用 +// +// 排查方法: - +const ErrCode50006 ErrCode = 50006 + // ErrCode50100 分页查询的游标无效 // // 排查方法: - @@ -1610,6 +1686,16 @@ const ErrCode60021 ErrCode = 60021 // 排查方法: 第三方应用类型,不允许通过接口修改该应用的主页 URL const ErrCode60028 ErrCode = 60028 +// ErrCode60030 已超出应用可见范围 +// +// 排查方法: 调整应用可见范围覆盖操作的规则组、会议室等 +const ErrCode60030 ErrCode = 60030 + +// ErrCode60031 当前应用已禁止调用API +// +// 排查方法: - +const ErrCode60031 ErrCode = 60031 + // ErrCode60102 UserID已存在 // // 排查方法: - @@ -1913,6 +1999,11 @@ const ErrCode60236 ErrCode = 60236 // 排查方法: 检查payment_id是否误用或者拼写错误 const ErrCode60237 ErrCode = 60237 +// ErrCode60238 传入的partyid有误 +// +// 排查方法: 检查partyid +const ErrCode60238 ErrCode = 60238 + // ErrCode60239 收款人未实名 // // 排查方法: - @@ -2048,6 +2139,11 @@ const ErrCode65023 ErrCode = 65023 // 排查方法: 检查学生的班主任、任课教师或家长所在班级群群主是否在应用可见范围内 const ErrCode65024 ErrCode = 65024 +// ErrCode400237 location的长度超过最大限制 +// +// 排查方法: - +const ErrCode400237 ErrCode = 400237 + // ErrCode660001 无效的商户号 // // 排查方法: 请检查商户号是否正确 @@ -2209,7 +2305,7 @@ const ErrCode670016 ErrCode = 670016 // 排查方法: - const ErrCode670017 ErrCode = 670017 -// ErrCode670018 该联系人帐号异常,无法邀请其加入上下游 +// ErrCode670018 该联系人账号异常,无法邀请其加入上下游 // // 排查方法: - const ErrCode670018 ErrCode = 670018 @@ -2239,7 +2335,7 @@ const ErrCode670022 ErrCode = 670022 // 排查方法: - const ErrCode670023 ErrCode = 670023 -// ErrCode670024 帐号异常,不能进行导入 +// ErrCode670024 账号异常,不能进行导入 // // 排查方法: - const ErrCode670024 ErrCode = 670024 @@ -2339,7 +2435,7 @@ const ErrCode701112 ErrCode = 701112 // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A80001 const ErrCode80001 ErrCode = 80001 -// ErrCode81001 部门下的结点数超过限制(3W) +// ErrCode81001 部门下的节点数超过限制(3W) // // 排查方法: - const ErrCode81001 ErrCode = 81001 @@ -2462,6 +2558,16 @@ const ErrCode82003 ErrCode = 82003 // 排查方法: 消息内容中可能存在使客户端crash的内容 const ErrCode82004 ErrCode = 82004 +// ErrCode82101 指定的更新对象为空 +// +// 排查方法: +const ErrCode82101 ErrCode = 82101 + +// ErrCode82102 指定的更新对象不在多人消息内 +// +// 排查方法: +const ErrCode82102 ErrCode = 82102 + // ErrCode84005 第三方应用不存在 // // 排查方法: 检查access_token和应用id是否正确 @@ -2476,14 +2582,14 @@ const ErrCode84005 ErrCode = 84005 // 2)若需再次获取用户详情,需要用户重新点击链接后,根据新的code获取新的user_ticket // // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A84014 -// [根据code获取成员信息]: https://open.work.weixin.qq.com/api/doc#10028/%E6%A0%B9%E6%8D%AEcode%E8%8E%B7%E5%8F%96%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF +// [根据code获取成员信息]: https://open.work.weixin.qq.com/api/doc#15122/%E8%8E%B7%E5%8F%96%E8%AE%BF%E9%97%AE%E7%94%A8%E6%88%B7%E8%BA%AB%E4%BB%BD const ErrCode84014 ErrCode = 84014 // ErrCode84015 成员票据无效 // -// 排查方法: 确认user_ticket参数来源是否正确。参考接口:[根据code获取成员信息] +// 排查方法: 确认user_ticket参数来源是否正确。参考接口:[获取访问用户身份] // -// [根据code获取成员信息]: https://developer.work.weixin.qq.com/document/path/90313#10028/%E6%A0%B9%E6%8D%AEcode%E8%8E%B7%E5%8F%96%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF +// [获取访问用户身份]: https://developer.work.weixin.qq.com/document/path/90313#15122/%E8%8E%B7%E5%8F%96%E8%AE%BF%E9%97%AE%E7%94%A8%E6%88%B7%E8%BA%AB%E4%BB%BD const ErrCode84015 ErrCode = 84015 // ErrCode84019 缺少templateid参数 @@ -2753,7 +2859,7 @@ const ErrCode84095 ErrCode = 84095 // 排查方法: - const ErrCode84096 ErrCode = 84096 -// ErrCode84097 当前企业有未处理订单 +// ErrCode84097 当前企业有等待生效的订单,无法下新单 // // 排查方法: - const ErrCode84097 ErrCode = 84097 @@ -3108,6 +3214,11 @@ const ErrCode84178 ErrCode = 84178 // 排查方法: - const ErrCode84180 ErrCode = 84180 +// ErrCode84186 当前版本的剩余时长超过一年,不支持新购 +// +// 排查方法: - +const ErrCode84186 ErrCode = 84186 + // ErrCode84188 如果付费版本未发布,就不能创建预下单 // // 排查方法: - @@ -3293,6 +3404,11 @@ const ErrCode86217 ErrCode = 86217 // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A86220 const ErrCode86220 ErrCode = 86220 +// ErrCode86222 应用多人消息成员必须要包含至少一个下游企业员工 +// +// 排查方法: - +const ErrCode86222 ErrCode = 86222 + // ErrCode86224 不是受限群,不允许使用该接口 // // 排查方法: - @@ -3399,6 +3515,11 @@ const ErrCode90207 ErrCode = 90207 // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A90208 const ErrCode90208 ErrCode = 90208 +// ErrCode90210 需要用户进行成员授权 +// +// 排查方法: - +const ErrCode90210 ErrCode = 90210 + // ErrCode90211 登录时传入的suiteid不合法 // // 排查方法: - @@ -3744,6 +3865,41 @@ const ErrCode90415 ErrCode = 90415 // 排查方法: - const ErrCode90416 ErrCode = 90416 +// ErrCode90418 指定的提现人员未实名认证 +// +// 排查方法: - +const ErrCode90418 ErrCode = 90418 + +// ErrCode90419 上传图片出现错误 +// +// 排查方法: 请重新上传图片并使用新获得的mediaid,仍失败时请尝试使用新的申请单号重新提交 +const ErrCode90419 ErrCode = 90419 + +// ErrCode90420 指定的提现人员的实名认证信息与超级管理员不符 +// +// 排查方法: - +const ErrCode90420 ErrCode = 90420 + +// ErrCode90421 所选择的经营范围与主体类型不符 +// +// 排查方法: - +const ErrCode90421 ErrCode = 90421 + +// ErrCode90431 一个应用一个企业一天内最多只能创建3个免支付订单 +// +// 排查方法: - +const ErrCode90431 ErrCode = 90431 + +// ErrCode90432 充值账户未开通 +// +// 排查方法: - +const ErrCode90432 ErrCode = 90432 + +// ErrCode90433 账户余额不足 +// +// 排查方法: - +const ErrCode90433 ErrCode = 90433 + // ErrCode90456 必须指定组织者 // // 排查方法: - @@ -4059,6 +4215,31 @@ const ErrCode90700 ErrCode = 90700 // 排查方法: - const ErrCode90704 ErrCode = 90704 +// ErrCode90705 日程的创建者已经离职,不可删除 +// +// 排查方法: - +const ErrCode90705 ErrCode = 90705 + +// ErrCode90706 无法将非周期日程的起始时间修改成早于当前时间 +// +// 排查方法: - +const ErrCode90706 ErrCode = 90706 + +// ErrCode90707 日程关联的会议正在进行中,无法修改 +// +// 排查方法: - +const ErrCode90707 ErrCode = 90707 + +// ErrCode90708 日程创建者没有默认日历,需要传日历参数 +// +// 排查方法: - +const ErrCode90708 ErrCode = 90708 + +// ErrCode90710 日程关联的会议已经结束,无法修改 +// +// 排查方法: - +const ErrCode90710 ErrCode = 90710 + // ErrCode91040 获取ticket的类型无效 // // 排查方法: [查看帮助] @@ -4123,6 +4304,11 @@ const ErrCode93000 ErrCode = 93000 // 排查方法: - const ErrCode93004 ErrCode = 93004 +// ErrCode93006 不合法的群ID +// +// 排查方法: - +const ErrCode93006 ErrCode = 93006 + // ErrCode93008 不在群里 // // 排查方法: - @@ -4185,20 +4371,20 @@ const ErrCode95002 ErrCode = 95002 // ErrCode95003 发送客服消息可接待客户咨询数限制 // -// 排查方法: 企业未验证或未绑定视频号 +// 排查方法: 企业未验证 const ErrCode95003 ErrCode = 95003 // ErrCode95004 open_kfid不存在 // -// 排查方法: 跨企业使用,或对已删除的帐号操作 +// 排查方法: 跨企业使用,或对已删除的账号操作 const ErrCode95004 ErrCode = 95004 -// ErrCode95005 客服帐号数超过限制 +// ErrCode95005 客服账号数超过限制 // // 排查方法: 客服账号数不能多于最大的数量限制,且至少有一个 const ErrCode95005 ErrCode = 95005 -// ErrCode95006 不合法的客服帐号名 +// ErrCode95006 不合法的客服账号名 // // 排查方法: - const ErrCode95006 ErrCode = 95006 @@ -4282,11 +4468,26 @@ const ErrCode95023 ErrCode = 95023 // 排查方法: 请改善后再试 const ErrCode95026 ErrCode = 95026 -// ErrCode95027 企业未认证,仅可创建10个客服帐号 +// ErrCode95027 企业未认证,仅可创建10个客服账号 // // 排查方法: 认证企业后即可继续创建 const ErrCode95027 ErrCode = 95027 +// ErrCode95030 客服组件设置的禁发消息类型 +// +// 排查方法: - +const ErrCode95030 ErrCode = 95030 + +// ErrCode95031 客户48小时内未发起过咨询 +// +// 排查方法: 需要客户发起咨询后才可调用 +const ErrCode95031 ErrCode = 95031 + +// ErrCode95032 客服组件获取客服聊天记录权限已失效 +// +// 排查方法: 请联系官方对接人员沟通处理 +const ErrCode95032 ErrCode = 95032 + // ErrCode301002 无权限操作指定的应用 // // 排查方法: [查看帮助] @@ -4445,6 +4646,11 @@ const ErrCode301060 ErrCode = 301060 // 排查方法: - const ErrCode301061 ErrCode = 301061 +// ErrCode301073 设置排班的时间参数不合法 +// +// 排查方法: openapi目前仅支持对未来日期设置排班 +const ErrCode301073 ErrCode = 301073 + // ErrCode301080 应打卡时间非法 // // 排查方法: 卡点不需要打卡或没有这个应打卡时间的卡点 @@ -4539,6 +4745,36 @@ const ErrCode301096 ErrCode = 301096 // 排查方法: 打卡规则中位置和wifi信息不能同时为空 const ErrCode301097 ErrCode = 301097 +// ErrCode301099 所属的打卡规则,不可提交【迟到】补卡 +// +// 排查方法: - +const ErrCode301099 ErrCode = 301099 + +// ErrCode301101 所属的打卡规则,不可提交【早退】补卡 +// +// 排查方法: - +const ErrCode301101 ErrCode = 301101 + +// ErrCode301102 所属的打卡规则,不可提交【缺卡】补卡 +// +// 排查方法: - +const ErrCode301102 ErrCode = 301102 + +// ErrCode301103 所属的打卡规则,不可提交【其他异常】补卡 +// +// 排查方法: - +const ErrCode301103 ErrCode = 301103 + +// ErrCode301104 所属的打卡规则,不可提交【迟到】、【其他异常】补卡 +// +// 排查方法: - +const ErrCode301104 ErrCode = 301104 + +// ErrCode301105 所属的打卡规则,不可提交【早退】、【其他异常】补卡 +// +// 排查方法: - +const ErrCode301105 ErrCode = 301105 + // ErrCode301056 审批应用已停用 // // 排查方法: - @@ -5053,12 +5289,12 @@ const ErrCode610014 ErrCode = 610014 // // 排查方法: [查看帮助] // -// 小程序对应的开放平台帐号未认证。请确认: -// 1) 小程序绑定的开放平台帐号是否已认证。 -// 2) 若开放平台帐号是通过api代注册所创建,可通过微信的「[认证代注册的帐号]」完成认证。 +// 小程序对应的开放平台账号未认证。请确认: +// 1) 小程序绑定的开放平台账号是否已认证。 +// 2) 若开放平台账号是通过api代注册所创建,可通过微信的「[认证代注册的账号]」完成认证。 // // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A610015 -// [认证代注册的帐号]: https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/product/Open_Platform_Account_Management.html#%E5%9B%9B%E3%80%81%E8%AE%A4%E8%AF%81%E4%BB%A3%E6%B3%A8%E5%86%8C%E7%9A%84%E5%B8%90%E5%8F%B7 +// [认证代注册的账号]: https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/product/Open_Platform_Account_Management.html#%E5%9B%9B%E3%80%81%E8%AE%A4%E8%AF%81%E4%BB%A3%E6%B3%A8%E5%86%8C%E7%9A%84%E5%B8%90%E5%8F%B7 const ErrCode610015 ErrCode = 610015 // ErrCode610016 企业未认证 @@ -5071,8 +5307,8 @@ const ErrCode610016 ErrCode = 610016 // 排查方法: [查看帮助] // // 小程序和企业主体不一致。确认: -// 1) 小程序的主体名称,或者小程序绑定的开放平台帐号主体名称,与企业的主体名称是否一致。 -// 2) 如果小程序、开放平台帐号、企业三者的主体一致,那需要调用接口传入的openid与unionid是否该小程序获取。 +// 1) 小程序的主体名称,或者小程序绑定的开放平台账号主体名称,与企业的主体名称是否一致。 +// 2) 如果小程序、开放平台账号、企业三者的主体一致,那需要调用接口传入的openid与unionid是否该小程序获取。 // // [查看帮助]: https://developer.work.weixin.qq.com/document/path/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A610017 const ErrCode610017 ErrCode = 610017 @@ -5252,7 +5488,7 @@ const ErrCode640029 ErrCode = 640029 // 排查方法: - const ErrCode640032 ErrCode = 640032 -// ErrCode640035 免费帐号调用微盘api累计上限为1000次/月,每个企业内所有免费账号共用此次数限制 +// ErrCode640035 免费账号调用微盘api累计上限为1000次/月,每个企业内所有免费账号共用此次数限制 // // 排查方法: 升级为付费账号,不受此限制 const ErrCode640035 ErrCode = 640035 @@ -5482,6 +5718,51 @@ const ErrCode740005 ErrCode = 740005 // 排查方法: 要求域名ICP备案跟企业主体一致 const ErrCode740006 ErrCode = 740006 +// ErrCode770001 高级功能额度不足 +// +// 排查方法: - +const ErrCode770001 ErrCode = 770001 + +// ErrCode770003 高级功能全企业购买模式不允许分配和撤销高级功能账户 +// +// 排查方法: - +const ErrCode770003 ErrCode = 770003 + +// ErrCode770004 不存在合法的用户列表 +// +// 排查方法: - +const ErrCode770004 ErrCode = 770004 + +// ErrCode770005 未开通业务高级功能 +// +// 排查方法: - +const ErrCode770005 ErrCode = 770005 + +// ErrCode770006 不合法的jobid +// +// 排查方法: - +const ErrCode770006 ErrCode = 770006 + +// ErrCode770007 任务正在处理中 +// +// 排查方法: - +const ErrCode770007 ErrCode = 770007 + +// ErrCode770008 存在用户账户分配高级功能超过7天,不允许撤销 +// +// 排查方法: - +const ErrCode770008 ErrCode = 770008 + +// ErrCode770009 请求中所有合法用户账户均是高级功能账户 +// +// 排查方法: - +const ErrCode770009 ErrCode = 770009 + +// ErrCode770010 已在腾讯会议侧购买高级功能,不允许通过此接口操作 +// +// 排查方法: - +const ErrCode770010 ErrCode = 770010 + // ErrCode842002 代开发应用模版未上线 // // 排查方法: - @@ -5529,7 +5810,7 @@ const ErrCode845002 ErrCode = 845002 // ErrCode845003 unionid认证主体和企业认证主体不一致 // -// 排查方法: 请确保开放平台帐号已经认证,且认证的主体名称与企业的主体名称一致 +// 排查方法: 请确保开放平台账号已经认证,且认证的主体名称与企业的主体名称一致 const ErrCode845003 ErrCode = 845003 // ErrCode846000 不是代开发或者第三方应用 @@ -5592,12 +5873,12 @@ const ErrCode60257 ErrCode = 60257 // 排查方法: - const ErrCode301007 ErrCode = 301007 -// ErrCode701001 不是license基础帐号 +// ErrCode701001 不是license基础账号 // // 排查方法: - const ErrCode701001 ErrCode = 701001 -// ErrCode701002 不合法的license帐号 +// ErrCode701002 不合法的license账号 // // 排查方法: - const ErrCode701002 ErrCode = 701002 @@ -5617,12 +5898,12 @@ const ErrCode701004 ErrCode = 701004 // 排查方法: - const ErrCode701005 ErrCode = 701005 -// ErrCode701006 不合法的license帐号类型 +// ErrCode701006 不合法的license账号类型 // // 排查方法: - const ErrCode701006 ErrCode = 701006 -// ErrCode701007 不合法的帐号类型 +// ErrCode701007 不合法的账号类型 // // 排查方法: - const ErrCode701007 ErrCode = 701007 @@ -5670,17 +5951,17 @@ const ErrCode701014 ErrCode = 701014 // 排查方法: - const ErrCode701015 ErrCode = 701015 -// ErrCode701016 帐号未激活或者已经过期 +// ErrCode701016 账号未激活或者已经过期 // // 排查方法: - const ErrCode701016 ErrCode = 701016 -// ErrCode701017 帐号30天内迁移过 +// ErrCode701017 账号30天内迁移过 // // 排查方法: - const ErrCode701017 ErrCode = 701017 -// ErrCode701018 迁移帐号重叠,接收帐号已有相同类型的帐号 +// ErrCode701018 迁移账号重叠,接收账号已有相同类型的账号 // // 排查方法: - const ErrCode701018 ErrCode = 701018 @@ -5725,7 +6006,7 @@ const ErrCode701025 ErrCode = 701025 // 排查方法: - const ErrCode701026 ErrCode = 701026 -// ErrCode701027 测试企业购买帐号个数超限。 +// ErrCode701027 测试企业购买账号个数超限。 // // 排查方法: - const ErrCode701027 ErrCode = 701027 @@ -5962,6 +6243,41 @@ const ErrCode701132 ErrCode = 701132 // 排查方法: - const ErrCode701133 ErrCode = 701133 +// ErrCode701134 操作过于频繁,请稍后再试 +// +// 排查方法: - +const ErrCode701134 ErrCode = 701134 + +// ErrCode701135 接口许可购买时长不可低于应用订单时长 +// +// 排查方法: - +const ErrCode701135 ErrCode = 701135 + +// ErrCode701150 测试企业不支持分配许可 +// +// 排查方法: - +const ErrCode701150 ErrCode = 701150 + +// ErrCode701160 存在未通过支付检查的企业 +// +// 排查方法: - +const ErrCode701160 ErrCode = 701160 + +// ErrCode701161 订单已经指定使用微信网银支付 +// +// 排查方法: - +const ErrCode701161 ErrCode = 701161 + +// ErrCode701170 群活码第三方限免许可到期 +// +// 排查方法: - +const ErrCode701170 ErrCode = 701170 + +// ErrCode701200 其他镜像发布中,禁止发布 +// +// 排查方法: - +const ErrCode701200 ErrCode = 701200 + // ErrCode730000 非法的tmp_openid // // 排查方法: - @@ -6022,6 +6338,51 @@ const ErrCode730010 ErrCode = 730010 // 排查方法: - const ErrCode730011 ErrCode = 730011 +// ErrCode790000 不合法的jobid +// +// 排查方法: - +const ErrCode790000 ErrCode = 790000 + +// ErrCode790001 不合法的任务状态 +// +// 排查方法: - +const ErrCode790001 ErrCode = 790001 + +// ErrCode790002 任务已经在执行 +// +// 排查方法: - +const ErrCode790002 ErrCode = 790002 + +// ErrCode790003 任务执行结果已经上报 +// +// 排查方法: - +const ErrCode790003 ErrCode = 790003 + +// ErrCode790004 未授权会话组件 +// +// 排查方法: - +const ErrCode790004 ErrCode = 790004 + +// ErrCode790005 nonce重复 +// +// 排查方法: - +const ErrCode790005 ErrCode = 790005 + +// ErrCode790006 没有已发布的镜像 +// +// 排查方法: - +const ErrCode790006 ErrCode = 790006 + +// ErrCode790007 无法解密消息 +// +// 排查方法: - +const ErrCode790007 ErrCode = 790007 + +// ErrCode790008 任务不在运行中 +// +// 排查方法: - +const ErrCode790008 ErrCode = 790008 + // ErrCode830001 用于上传临时素材的url非法 // // 排查方法: 确认url是否支持Range分块下载 @@ -6287,6 +6648,16 @@ const ErrCode710200 ErrCode = 710200 // 排查方法: - const ErrCode710201 ErrCode = 710201 +// ErrCode751001 企业未开通邮箱高级功能 +// +// 排查方法: - +const ErrCode751001 ErrCode = 751001 + +// ErrCode751002 企业邮箱高级功能分配余额不足 +// +// 排查方法: - +const ErrCode751002 ErrCode = 751002 + // ErrCode2400001 请求参数错误 // // 排查方法: - @@ -6341,3 +6712,13 @@ const ErrCode400012 ErrCode = 400012 // // 排查方法: - const ErrCode400013 ErrCode = 400013 + +// ErrCode400301 5分钟内有相同的会议正在创建中,暂不可再创建 +// +// 排查方法: 若相同的会议未创建成功,可于相同会议首次创建时间5分钟后重新创建 +const ErrCode400301 ErrCode = 400301 + +// ErrCode400302 相同的会议已经创建成功 +// +// 排查方法: 若明确需要创建相同的会议,可在创建会议请求包中设置"skip_duplicate_check":true 跳过重复会议检查 +const ErrCode400302 ErrCode = 400302 diff --git a/external_contact.go b/external_contact.go index bf39567..a8e03c1 100644 --- a/external_contact.go +++ b/external_contact.go @@ -331,6 +331,80 @@ func (c *WorkwxApp) ExternalContactDelContactWay(configID string) error { return err } +// ExternalContactAddGroupChatJoinWay 配置客户群「加入群聊」方式 +func (c *WorkwxApp) ExternalContactAddGroupChatJoinWay(externalGroupChatJoinWay ExternalGroupChatJoinWay) (string, error) { + resp, err := c.execAddGroupChatJoinWayExternalContact( + reqAddGroupChatJoinWayExternalContact{ + ExternalGroupChatJoinWay: externalGroupChatJoinWay, + }) + if err != nil { + return "", err + } + + return resp.ConfigID, nil +} + +// ExternalContactGetGroupChatJoinWay 获取企业已配置的客户群「加入群聊」方式 +func (c *WorkwxApp) ExternalContactGetGroupChatJoinWay(configID string) (*ExternalContactGroupChatJoinWay, error) { + resp, err := c.execGetGroupChatJoinWayExternalContact(reqGetGroupChatJoinWayExternalContact{ConfigID: configID}) + if err != nil { + return nil, err + } + + return &resp.JoinWay, nil +} + +// GetGroupChatList 获取客户群列表 +func (c *WorkwxApp) GetGroupChatList(req ReqChatList) (*RespGroupChatList, error) { + resp, err := c.execGroupChatListGet(reqGroupChatList{ + ReqChatList: req, + }) + if err != nil { + return nil, err + } + return resp.RespGroupChatList, nil +} + +// GetGroupChatInfo 获取客户群详细信息 +func (c *WorkwxApp) GetGroupChatInfo(chatID string, chatNeedName int64) (*RespGroupChatInfo, error) { + resp, err := c.execGroupChatInfoGet(reqGroupChatInfo{ + ChatID: chatID, + NeedName: chatNeedName, + }) + if err != nil { + return nil, err + } + return resp.GroupChat, nil +} + +// ConvertOpenGIDToChatID 客户群opengid转换 +func (c *WorkwxApp) ConvertOpenGIDToChatID(openGID string) (string, error) { + resp, err := c.execConvertOpenGIDToChatID(reqConvertOpenGIDToChatID{ + OpenGID: openGID, + }) + if err != nil { + return "", err + } + return resp.ChatID, nil +} + +// ExternalContactUpdateGroupChatJoinWay 更新企业已配置的客户群「加入群聊」方式 +func (c *WorkwxApp) ExternalContactUpdateGroupChatJoinWay(configID string, externalGroupChatJoinWay ExternalGroupChatJoinWay) error { + _, err := c.execUpdateGroupChatJoinWayExternalContact(reqUpdateGroupChatJoinWayExternalContact{ + ConfigID: configID, + ExternalGroupChatJoinWay: externalGroupChatJoinWay, + }) + + return err +} + +// ExternalContactDelGroupChatJoinWay 删除企业已配置的客户群「加入群聊」方式 +func (c *WorkwxApp) ExternalContactDelGroupChatJoinWay(configID string) error { + _, err := c.execDelGroupChatJoinWayExternalContact(reqDelGroupChatJoinWayExternalContact{ConfigID: configID}) + + return err +} + // ExternalContactCloseTempChat 结束临时会话 func (c *WorkwxApp) ExternalContactCloseTempChat(userID, externalUserID string) error { _, err := c.execCloseTempChatExternalContact(reqCloseTempChatExternalContact{ diff --git a/external_contact.md.go b/external_contact.md.go index 7a32e36..8f65d8f 100644 --- a/external_contact.md.go +++ b/external_contact.md.go @@ -308,6 +308,24 @@ type ExternalContactWay struct { Conclusions Conclusions `json:"conclusions"` } +// ExternalGroupChatJoinWay 配置客户群「加入群聊」方式 +type ExternalGroupChatJoinWay struct { + // Scene 场景,1 - 群的小程序插件,2 - 群的二维码插件 + Scene int `json:"scene"` + // Remark 联系方式的备注信息,用于助记,超过30个字符将被截断 + Remark string `json:"remark"` + // AutoCreateRoom 当群满了后,是否自动新建群。0-否;1-是。 默认为1 + AutoCreateRoom int `json:"auto_create_room"` + // RoomBaseName 自动建群的群名前缀,当auto_create_room为1时有效。最长40个utf8字符 + RoomBaseName string `json:"room_base_name"` + // RoomBaseID 自动建群的群起始序号,当auto_create_room为1时有效 + RoomBaseID int `json:"room_base_id"` + // ChatIDs 使用该配置的客户群ID列表,支持5个。 + ChatIDs []string `json:"chat_id_list"` + // State 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符 + State string `json:"state"` +} + // Conclusions 结束语,会话结束时自动发送给客户 type Conclusions struct { // Text 文本消息 diff --git a/go.mod b/go.mod index 4af2fdd..70255e5 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 github.com/smartystreets/goconvey v1.7.2 github.com/urfave/cli/v2 v2.24.4 - golang.org/x/net v0.11.0 + golang.org/x/net v0.17.0 ) require ( @@ -18,5 +18,5 @@ require ( github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/smartystreets/assertions v1.2.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/text v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 225944f..d5747ef 100644 --- a/go.sum +++ b/go.sum @@ -24,7 +24,7 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -35,8 +35,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -48,20 +48,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/kf.go b/kf.go new file mode 100644 index 0000000..96e7a73 --- /dev/null +++ b/kf.go @@ -0,0 +1,141 @@ +package workwx + +// CreateKfAccount 创建客服账号 +func (c *WorkwxApp) CreateKfAccount(name, mediaID string) (openKfID string, err error) { + resp, err := c.execKfAccountCreate(reqKfAccountCreate{ + Name: name, + MediaID: mediaID, + }) + if err != nil { + return "", err + } + return resp.OpenKfID, nil +} + +// DeleteKfAccount 删除客服账号 +func (c *WorkwxApp) DeleteKfAccount(openKfID string) (err error) { + _, err = c.execKfAccountDelete(reqKfAccountDelete{ + OpenKfID: openKfID, + }) + if err != nil { + return err + } + return nil +} + +// UpdateKfAccount 修改客服账号 +func (c *WorkwxApp) UpdateKfAccount(openKfID, name, mediaID string) (err error) { + _, err = c.execKfAccountUpdate(reqKfAccountUpdate{ + OpenKfID: openKfID, + Name: name, + MediaID: mediaID, + }) + if err != nil { + return err + } + return nil +} + +// ListKfAccount 获取客服账号列表 +func (c *WorkwxApp) ListKfAccount(offset, limit int64) ([]*KfAccount, error) { + resp, err := c.execKfAccountList(reqKfAccountList{ + Offset: offset, + Limit: limit, + }) + if err != nil { + return nil, err + } + + return resp.AccountList, nil +} + +// AddKfContact 获取客服账号链接 +func (c *WorkwxApp) AddKfContact(openKfID, scene string) (url string, err error) { + resp, err := c.execAddKfContact(reqAddKfContact{ + OpenKfID: openKfID, + Scene: scene, + }) + if err != nil { + return "", err + } + return resp.URL, nil +} + +// CreateKfServicer 创建接待人员 +func (c *WorkwxApp) CreateKfServicer(openKfID string, userIDs []string, departmentIDs []int64) (resultList []*KfServicerResult, err error) { + resp, err := c.execKfServicerCreate(reqKfServicerCreate{ + OpenKfID: openKfID, + UserIDs: userIDs, + DepartmentIDs: departmentIDs, + }) + if err != nil { + return nil, err + } + return resp.ResultList, nil +} + +// DeleteKfServicer 删除接待人员 +func (c *WorkwxApp) DeleteKfServicer(openKfID string, userIDs []string, departmentIDs []int64) (resultList []*KfServicerResult, err error) { + resp, err := c.execKfServicerDelete(reqKfServicerDelete{ + OpenKfID: openKfID, + UserIDs: userIDs, + DepartmentIDs: departmentIDs, + }) + if err != nil { + return nil, err + } + return resp.ResultList, nil +} + +// ListKfServicer 获取接待人员列表 +func (c *WorkwxApp) ListKfServicer(openKfID string) ([]*KfServicer, error) { + resp, err := c.execKfServicerList(reqKfServicerList{ + OpenKfID: openKfID, + }) + if err != nil { + return nil, err + } + + return resp.ServicerList, nil +} + +// GetKfServiceState 获取会话状态 +func (c *WorkwxApp) GetKfServiceState(openKfID, externalUserID string) (KfServiceState, string, error) { + resp, err := c.execKfServiceStateGet(reqKfServiceStateGet{ + OpenKfID: openKfID, + ExternalUserID: externalUserID, + }) + if err != nil { + return KfServiceStateUntreated, "", err + } + return resp.ServiceState, resp.ServicerUserID, nil +} + +// TransKfServiceState 变更会话状态 +func (c *WorkwxApp) TransKfServiceState(openKfID, externalUserID, servicerUserID string, ServiceState KfServiceState) (string, error) { + resp, err := c.execKfServiceStateTrans(reqKfServiceStateTrans{ + OpenKfID: openKfID, + ExternalUserID: externalUserID, + ServiceState: ServiceState, + ServicerUserID: servicerUserID, + }) + if err != nil { + return "", err + } + return resp.MsgCode, nil +} + +// KfSyncMsg 微信客服获取消息列表 +func (c *WorkwxApp) KfSyncMsg(openKfID, token, cursor string, limit int64, voiceFormat int) ([]KfMsg, int, string, error) { + resp, err := c.execKfSyncMsg(reqKfSyncMsg{ + OpenKfID: openKfID, + Cursor: cursor, + Token: token, + Limit: limit, + VoiceFormat: voiceFormat, + }) + if err != nil { + return nil, 0, "", err + } + return resp.MsgList, resp.HasMore, resp.NextCursor, nil +} diff --git a/kf.md.go b/kf.md.go new file mode 100644 index 0000000..8003c69 --- /dev/null +++ b/kf.md.go @@ -0,0 +1,159 @@ +// Code generated by sdkcodegen; DO NOT EDIT. + +package workwx + +// KfAccount 客服账号 +type KfAccount struct { + // OpenKfID 客服账号ID + OpenKfID string `json:"open_kfid"` + // Name 客服名称 + Name string `json:"name"` + // Avatar 客服头像URL + Avatar string `json:"avatar"` + // ManagePrivilege 当前调用接口的应用身份,是否有该客服账号的管理权限(编辑客服账号信息、分配会话和收发消息)。组件应用不返回此字段 + ManagePrivilege bool `json:"manage_privilege,omitempty"` +} + +// KfServicer 客服接待人员 +type KfServicer struct { + // UserID 接待人员的userid。第三方应用获取到的为密文userid,即open_userid + UserID string `json:"userid,omitempty"` + // Status 接待人员的接待状态。0:接待中,1:停止接待。 + Status int `json:"status"` + // StopType 接待人员的接待状态为「停止接待」的子类型。0:停止接待,1:暂时挂起 + StopType int `json:"stop_type"` + // DepartmentID 接待人员部门的id + DepartmentID int64 `json:"department_id,omitempty"` +} + +// KfServicerResult 接待人员数据 +type KfServicerResult struct { + // UserID 接待人员的userid + UserID string `json:"userid,omitempty"` + // DepartmentID 接待人员部门的id + DepartmentID int64 `json:"department_id,omitempty"` + // ErrCode 该条记录的结果 + ErrCode int64 `json:"errcode"` + // ErrMsg 结果信息 + ErrMsg string `json:"errmsg"` +} + +// KfMsg 客服消息数据 +type KfMsg struct { + // MsgID 消息ID + MsgID string `json:"msgid,omitempty"` + // OpenKfID 客服账号ID(msgtype为event,该字段不返回) + OpenKfID string `json:"open_kfid,omitempty"` + // ExternalUserID 客客户UserID(msgtype为event,该字段不返回) + ExternalUserID string `json:"external_userid,omitempty"` + // SendTime 消息发送时间 + SendTime int64 `json:"send_time,omitempty"` + // Origin 消息来源。3-微信客户发送的消息 4-系统推送的事件消息 5-接待人员在企业微信客户端发送的消息 + Origin int `json:"origin,omitempty"` + // ServicerUserID 从企业微信给客户发消息的接待人员userid(即仅origin为5才返回;msgtype为event,该字段不返回) + ServicerUserID string `json:"servicer_userid,omitempty"` + // MsgType 消息类型 + MsgType MessageType `json:"msgtype"` + // Text 文本消息 + Text Text `json:"text,omitempty"` + // Image 图片消息 + Image Image `json:"image,omitempty"` + // Link 链接消息 + Link Link `json:"link,omitempty"` + // MiniProgram 小程序消息 + MiniProgram MiniProgram `json:"mini_program,omitempty"` + // Event 事件类型 + Event KfEvent `json:"event,omitempty"` +} + +// KfEvent 客服会话事件 +type KfEvent struct { + // EventType 事件类型 + EventType KfEventType `json:"event_type"` + // OpenKfID 客服账号ID + OpenKfID string `json:"open_kfid"` + // ExternalUserID 客户UserID,注意不是企业成员的帐号 + ExternalUserID string `json:"external_userid,omitempty"` + // ServicerUserID 接待人员userid + ServicerUserID string `json:"servicer_userid,omitempty"` + // Scene 用户进入会话事件特有。进入会话的场景值,获取客服账号链接开发者自定义的场景值 + Scene string `json:"scene,omitempty"` + // SceneParam 用户进入会话事件特有。进入会话的自定义参数,获取客服账号链接返回的url,开发者按规范拼接的scene_param参数 + SceneParam string `json:"scene_param,omitempty"` + // WelcomeCode 用户进入会话事件特有。如果满足发送欢迎语条件(条件为:用户在过去48小时里未收过欢迎语,且未向客服发过消息),会返回该字段。可用该welcome_code调用发送事件响应消息接口给客户发送欢迎语。 + WelcomeCode string `json:"welcome_code,omitempty"` + // WechatChannels 用户进入会话事件特有。进入会话的视频号信息,从视频号进入会话才有值 + WechatChannels KfWechatChannels `json:"wechat_channels,omitempty"` + // FailMsgID 消息发送失败事件特有。发送失败的消息msgid + FailMsgID string `json:"fail_msgid,omitempty"` + // FailType 消息发送失败事件特有。失败类型。0-未知原因 1-客服账号已删除 2-应用已关闭 4-会话已过期,超过48小时 5-会话已关闭 6-超过5条限制 8-主体未验证 10-用户拒收 11-企业未有成员登录企业微信App(排查方法:企业至少一个成员通过手机号验证/微信授权登录企业微信App即可)12-发送的消息为客服组件禁发的消息类型 + FailType int `json:"fail_type,omitempty"` + // Status 接待人员接待状态变更事件特有。状态类型。1-接待中 2-停止接待 + Status int `json:"status,omitempty"` + // StopType 接待人员接待状态变更事件特有。接待人员的状态为「停止接待」的子类型。0:停止接待,1:暂时挂起 + StopType int `json:"stop_type,omitempty"` + // ChangeType 会话状态变更事件特有。变更类型,均为接待人员在企业微信客户端操作触发。1-从接待池接入会话 2-转接会话 3-结束会话 4-重新接入已结束/已转接会话 + ChangeType KfServiceState `json:"change_type,omitempty"` + // OldServicerUserID 会话状态变更事件特有。老的接待人员userid。仅change_type为2、3和4有值 + OldServicerUserID string `json:"old_servicer_userid,omitempty"` + // NewServicerUserid 会话状态变更事件特有。新的接待人员userid。仅change_type为1、2和4有值 + NewServicerUserid string `json:"new_servicer_userid,omitempty"` + // MsgCode 会话状态变更事件特有。用于发送事件响应消息的code,仅change_type为1和3时,会返回该字段。可用该msg_code调用发送事件响应消息接口给客户发送回复语或结束语。 + MsgCode string `json:"msg_code,omitempty"` + // RecallMsgID 撤回消息事件特有。 撤回的消息msgid + RecallMsgID string `json:"recall_msgid,omitempty"` + // RejectSwitch 拒收客户消息变更事件特有。 拒收客户消息,1表示接待人员拒收了客户消息,0表示接待人员取消拒收客户消息 + RejectSwitch int `json:"reject_switch,omitempty"` +} + +// KfWechatChannels 进入会话的视频号信息,从视频号进入会话才有值 +type KfWechatChannels struct { + // NickName 视频号名称,视频号场景值为1、2、3时返回此项 + NickName string `json:"nickname,omitempty"` + // ShopNickName 视频号小店名称,视频号场景值为4、5时返回此项 + ShopNickName string `json:"shop_nickname,omitempty"` + // Scene 视频号场景值。1:视频号主页,2:视频号直播间商品列表页,3:视频号商品橱窗页,4:视频号小店商品详情页,5:视频号小店订单页 + Scene int64 `json:"scene"` +} + +// KfEventType 事件类型 +type KfEventType string + +const ( + // KfEventTypeEnterSession 用户进入会话事件 + KfEventTypeEnterSession KfEventType = "enter_session" + // KfEventTypeMsgSendFail 消息发送失败事件 + KfEventTypeMsgSendFail KfEventType = "msg_send_fail" + // KfEventTypeServicerStatusChange 接待人员接待状态变更事件 + KfEventTypeServicerStatusChange KfEventType = "servicer_status_change" + // KfEventTypeSessionStatusChange 会话状态变更事件 + KfEventTypeSessionStatusChange KfEventType = "session_status_change" + // KfEventTypeUserRecallMsg 用户撤回消息事件 + KfEventTypeUserRecallMsg KfEventType = "user_recall_msg" + // KfEventTypeServicerRecallMsg 接待人员撤回消息事件 + KfEventTypeServicerRecallMsg KfEventType = "servicer_recall_msg" + // KfEventTypeRejectCustomerMsgSwitchChange 拒收客户消息变更事件 + KfEventTypeRejectCustomerMsgSwitchChange KfEventType = "reject_customer_msg_switch_change" +) + +// KfServiceState 客服会话状态 +// +// 0 未处理 新会话接入 +// 1 由智能助手接待 +// 2 待接入池排队中 +// 3 由人工接待 +// 4 已结束/未开始 +type KfServiceState int + +const ( + // KfServiceStateUntreated 未处理 新会话接入 + KfServiceStateUntreated KfServiceState = iota + // KfServiceStateRobotReception 由智能助手接待 + KfServiceStateRobotReception + // KfServiceStateInQueue 待接入池排队中 + KfServiceStateInQueue + // KfServiceStateManualReception 由人工接待 + KfServiceStateManualReception + // KfServiceStateFinished 已结束/未开始 + KfServiceStateFinished +) diff --git a/message.go b/message.go index 928bea5..a51729e 100644 --- a/message.go +++ b/message.go @@ -122,20 +122,14 @@ func (c *WorkwxApp) SendTextCardMessage( // 否则为单纯的【发送应用消息】接口调用。 func (c *WorkwxApp) SendNewsMessage( recipient *Recipient, - title string, - description string, - url string, - picURL string, + articles []Article, isSafe bool, ) error { return c.sendMessage( recipient, "news", map[string]interface{}{ - "title": title, - "description": description, // TODO: 零值 - "url": url, - "picurl": picURL, // TODO: 零值 + "articles": articles, }, isSafe, ) } @@ -146,29 +140,14 @@ func (c *WorkwxApp) SendNewsMessage( // 否则为单纯的【发送应用消息】接口调用。 func (c *WorkwxApp) SendMPNewsMessage( recipient *Recipient, - title string, - thumbMediaID string, - author string, - sourceContentURL string, - content string, - digest string, + mparticles []MPArticle, isSafe bool, ) error { return c.sendMessage( recipient, "mpnews", map[string]interface{}{ - // TODO: 支持发送多条图文 - "articles": []interface{}{ - map[string]interface{}{ - "title": title, - "thumb_media_id": thumbMediaID, - "author": author, // TODO: 零值 - "content_source_url": sourceContentURL, // TODO: 零值 - "content": content, - "digest": digest, - }, - }, + "articles": mparticles, }, isSafe, ) } @@ -210,9 +189,26 @@ func (c *WorkwxApp) SendTaskCardMessage( ) } +// SendTemplateCardMessage 发送卡片模板消息 +func (c *WorkwxApp) SendTemplateCardMessage( + recipient *Recipient, + templateCard TemplateCard, + isSafe bool, +) error { + return c.sendMessage( + recipient, + "template_card", + map[string]interface{}{ + "template_card": templateCard, + }, isSafe, + ) +} + // sendMessage 发送消息底层接口 // // 收件人参数如果仅设置了 `ChatID` 字段,则为【发送消息到群聊会话】接口调用; +// 收件人参数如果仅设置了 `OpenKfID` 字段,则为【客服发送消息】接口调用; +// 收件人参数如果仅设置了 `Code` 字段,则为【发送欢迎语等事件响应消息】接口调用; // 否则为单纯的【发送应用消息】接口调用。 func (c *WorkwxApp) sendMessage( recipient *Recipient, @@ -220,15 +216,18 @@ func (c *WorkwxApp) sendMessage( content map[string]interface{}, isSafe bool, ) error { - isApichatSendRequest := false + sendRequestFunc := c.execMessageSend if !recipient.isValidForMessageSend() { - if !recipient.isValidForAppchatSend() { + if recipient.isValidForAppchatSend() { + sendRequestFunc = c.execAppchatSend + } else if recipient.isValidForKfSend() { + sendRequestFunc = c.execKfSend + } else if recipient.isValidForKfOnEventSend() { + sendRequestFunc = c.execKfOnEventSend + } else { // TODO: better error return errors.New("recipient invalid for message sending") } - - // 发送给群聊 - isApichatSendRequest = true } req := reqMessage{ @@ -242,13 +241,7 @@ func (c *WorkwxApp) sendMessage( IsSafe: isSafe, } - var resp respMessageSend - var err error - if isApichatSendRequest { - resp, err = c.execAppchatSend(req) - } else { - resp, err = c.execMessageSend(req) - } + resp, err := sendRequestFunc(req) if err != nil { return err diff --git a/models.go b/models.go index 5c9bf52..4bb83ed 100644 --- a/models.go +++ b/models.go @@ -119,7 +119,11 @@ func (x reqMessage) intoBody() ([]byte, error) { } // msgtype polymorphism - obj[x.MsgType] = x.Content + if x.MsgType != "template_card" { + obj[x.MsgType] = x.Content + } else { + obj[x.MsgType] = x.Content["template_card"] + } // 复用这个结构体,因为是 package-private 的所以这么做没风险 if x.ChatID != "" { @@ -154,31 +158,27 @@ func (x reqUserGet) intoURLValues() url.Values { } } -// respUserDetail 成员详细信息的公共字段 -type respUserDetail struct { - UserID string `json:"userid"` - Name string `json:"name"` - DeptIDs []int64 `json:"department"` - DeptOrder []uint32 `json:"order"` - Position string `json:"position"` - Mobile string `json:"mobile"` - Gender string `json:"gender"` - Email string `json:"email"` - IsLeaderInDept []int `json:"is_leader_in_dept"` - AvatarURL string `json:"avatar"` - Telephone string `json:"telephone"` - IsEnabled int `json:"enable"` - Alias string `json:"alias"` - Status int `json:"status"` - QRCodeURL string `json:"qr_code"` - // TODO: extattr external_profile external_position -} - // respUserGet 读取成员响应 type respUserGet struct { respCommon - respUserDetail + UserDetail +} + +// reqUserUpdate 更新成员请求 +type reqUserUpdate struct { + UserDetail *UserDetail +} + +var _ bodyer = reqUserUpdate{} + +func (x reqUserUpdate) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x.UserDetail) +} + +// respUserUpdate 更新成员响应 +type respUserUpdate struct { + respCommon } // reqUserList 部门成员请求 @@ -205,7 +205,79 @@ func (x reqUserList) intoURLValues() url.Values { type respUserList struct { respCommon - Users []*respUserDetail `json:"userlist"` + Users []*UserDetail `json:"userlist"` +} + +// reqConvertUserIDToOpenID userid转openid 请求 +type reqConvertUserIDToOpenID struct { + UserID string `json:"userid"` +} + +var _ bodyer = reqConvertUserIDToOpenID{} + +// respConvertUserIDToOpenID userid转openid 响应 +type respConvertUserIDToOpenID struct { + respCommon + + OpenID string `json:"openid"` +} + +func (x reqConvertUserIDToOpenID) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// reqConvertOpenIDToUserID openid转userid 请求 +type reqConvertOpenIDToUserID struct { + OpenID string `json:"openid"` +} + +var _ bodyer = reqConvertOpenIDToUserID{} + +// respConvertUserIDToOpenID openid转userid 响应 +type respConvertOpenIDToUserID struct { + respCommon + + UserID string `json:"userid"` +} + +func (x reqConvertOpenIDToUserID) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// SizeType qrcode尺寸类型 +// +// 1: 171 x 171; 2: 399 x 399; 3: 741 x 741; 4: 2052 x 2052 +type SizeType int + +const ( + // SizeTypeMini 171 x 171 + SizeTypeMini SizeType = iota + 1 + // SizeTypeSmall 399 x 399 + SizeTypeSmall + // SizeTypeMedium 741 x 741 + SizeTypeMedium + // SizeTypeLarge 2052 x 2052 + SizeTypeLarge +) + +// reqUserJoinQrcode 获取加入企业二维码 请求 +type reqUserJoinQrcode struct { + SizeType SizeType `json:"size_type"` +} + +var _ urlValuer = reqUserJoinQrcode{} + +func (x reqUserJoinQrcode) intoURLValues() url.Values { + return url.Values{ + "size_type": {strconv.Itoa(int(x.SizeType))}, + } +} + +// respUserJoinQrcode 获取加入企业二维码 响应 +type respUserJoinQrcode struct { + respCommon + + JoinQrcode string `json:"join_qrcode"` } // reqUserIDByMobile 手机号获取 userid 请求 @@ -226,6 +298,38 @@ type respUserIDByMobile struct { UserID string `json:"userid"` } +// EmailType 用户邮箱的类型 +// +// 1表示用户邮箱是企业邮箱(默认) +// 2表示用户邮箱是个人邮箱 +type EmailType int + +const ( + // EmailTypeCorporate 企业邮箱 + EmailTypeCorporate EmailType = 1 + // EmailTypePersonal 个人邮箱 + EmailTypePersonal EmailType = 2 +) + +// reqUserIDByEmail 邮箱获取 userid 请求 +type reqUserIDByEmail struct { + Email string `json:"email"` + EmailType EmailType `json:"email_type"` +} + +var _ bodyer = reqUserIDByEmail{} + +func (x reqUserIDByEmail) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respUserIDByEmail 邮箱获取 userid 响应 +type respUserIDByEmail struct { + respCommon + + UserID string `json:"userid"` +} + // reqDeptCreate 创建部门 type reqDeptCreate struct { DeptInfo *DeptInfo @@ -335,6 +439,24 @@ type respAppchatCreate struct { ChatID string `json:"chatid"` } +// reqAppchatUpdate 修改群聊会话请求 +type reqAppchatUpdate struct { + ChatInfo + AddMemberUserIDs []string `json:"add_user_list"` + DelMemberUserIDs []string `json:"del_user_list"` +} + +var _ bodyer = reqAppchatUpdate{} + +func (x reqAppchatUpdate) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respAppchatUpdate 修改群聊会话响应 +type respAppchatUpdate struct { + respCommon +} + // reqMediaUpload 临时素材上传请求 type reqMediaUpload struct { Type string @@ -617,6 +739,33 @@ type JSCodeSession struct { SessionKey string `json:"session_key"` } +// reqAuthCode2UserInfo 获取访问用户身份 +type reqAuthCode2UserInfo struct { + Code string +} + +var _ urlValuer = reqAuthCode2UserInfo{} + +func (x reqAuthCode2UserInfo) intoURLValues() url.Values { + return url.Values{ + "code": {x.Code}, + } +} + +// respAuthCode2UserInfo 获取访问用户身份响应 +type respAuthCode2UserInfo struct { + respCommon + AuthCodeUserInfo +} + +// AuthCodeUserInfo 访问用户身份 +type AuthCodeUserInfo struct { + UserID string `json:"userid,omitempty"` + UserTicket string `json:"user_ticket,omitempty"` + OpenID string `json:"openid,omitempty"` + ExternalUserID string `json:"external_userid,omitempty"` +} + type reqMsgAuditListPermitUser struct { MsgAuditEdition MsgAuditEdition `json:"type"` } @@ -840,37 +989,6 @@ type respTransferGroupChatExternalContact struct { FailedChatList []ExternalContactGroupChatTransferFailed `json:"failed_chat_list"` } -type reqAppchatList struct { - ReqChatList ReqChatList -} - -func (x reqAppchatList) intoBody() ([]byte, error) { - return marshalIntoJSONBody(x.ReqChatList) -} - -var _ bodyer = reqAppchatList{} - -type respAppchatList struct { - respCommon - *RespAppchatList -} - -type reqAppchatInfo struct { - ChatID string `json:"chat_id"` - NeedName int64 `json:"need_name"` -} - -func (x reqAppchatInfo) intoBody() ([]byte, error) { - return marshalIntoJSONBody(x) -} - -var _ bodyer = reqAppchatInfo{} - -type respAppchatInfo struct { - respCommon - GroupChat *RespAppChatInfo `json:"group_chat"` -} - type reqOAGetTemplateDetail struct { TemplateID string `json:"template_id"` } @@ -953,6 +1071,286 @@ type TaskCardBtn struct { IsBold bool `json:"is_bold"` } +// Article news 类型的文章 +type Article struct { + // 标题,不超过128个字节,超过会自动截断(支持id转译) + Title string `json:"title"` + // 描述,不超过512个字节,超过会自动截断(支持id转译) + Description string `json:"description"` + // 点击后跳转的链接。 最长2048字节,请确保包含了协议头(http/https),小程序或者url必须填写一个 + URL string `json:"url"` + // 图文消息的图片链接,最长2048字节,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150 + PicURL string `json:"picurl"` + // 小程序appid,必须是与当前应用关联的小程序,appid和pagepath必须同时填写,填写后会忽略url字段 + AppID string `json:"appid"` + // 点击消息卡片后的小程序页面,最长128字节,仅限本小程序内的页面。appid和pagepath必须同时填写,填写后会忽略url字段 + PagePath string `json:"pagepath"` +} + +// MPArticle mpnews 类型的文章 +type MPArticle struct { + // 标题,不超过128个字节,超过会自动截断(支持id转译) + Title string `json:"title"` + // 图文消息缩略图的media_id, 可以通过素材管理接口获得。此处thumb_media_id即上传接口返回的media_id + ThumbMediaID string `json:"thumb_media_id"` + // 图文消息的作者,不超过64个字节 + Author string `json:"author"` + // 图文消息点击“阅读原文”之后的页面链接 + ContentSourceURL string `json:"content_source_url"` + // 图文消息的内容,支持html标签,不超过666 K个字节(支持id转译) + Content string `json:"content"` + // 图文消息的描述,不超过512个字节,超过会自动截断(支持id转译) + Digest string `json:"digest"` +} + +// Source 卡片来源样式信息,不需要来源样式可不填写 +type Source struct { + // 来源图片的url,来源图片的尺寸建议为72*72 + IconURL string `json:"icon_url"` + // 来源图片的描述,建议不超过20个字,(支持id转译) + Desc string `json:"desc"` + // 来源文字的颜色,目前支持:0(默认) 灰色,1 黑色,2 红色,3 绿色 + DescColor int `json:"desc_color"` +} + +// ActionList 操作列表,列表长度取值范围为 [1, 3] +type ActionList struct { + // 操作的描述文案 + Text string `json:"text"` + // 操作key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复 + Key string `json:"key"` +} + +// ActionMenu 卡片右上角更多操作按钮 +type ActionMenu struct { + // 更多操作界面的描述 + Desc string `json:"desc"` + ActionList []ActionList `json:"action_list"` +} + +// MainTitle 一级标题 +type MainTitle struct { + // 一级标题,建议不超过36个字,文本通知型卡片本字段非必填,但不可本字段和sub_title_text都不填,(支持id转译) + Title string `json:"title"` + // 标题辅助信息,建议不超过160个字,(支持id转译) + Desc string `json:"desc"` +} + +// QuoteArea 引用文献样式 +type QuoteArea struct { + // 引用文献样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 + Type int `json:"type"` + // 点击跳转的url,quote_area.type是1时必填 + URL string `json:"url"` + // 引用文献样式的标题 + Title string `json:"title"` + // 引用文献样式的引用文案 + QuoteText string `json:"quote_text"` + // 小程序appid,必须是与当前应用关联的小程序,appid和pagepath必须同时填写,填写后会忽略url字段 + AppID string `json:"appid"` + // 点击消息卡片后的小程序页面,最长128字节,仅限本小程序内的页面。appid和pagepath必须同时填写,填写后会忽略url字段 + PagePath string `json:"pagepath"` +} + +// EmphasisContent 关键数据样式 +type EmphasisContent struct { + // 关键数据样式的数据内容,建议不超过14个字 + Title string `json:"title"` + // 关键数据样式的数据描述内容,建议不超过22个字 + Desc string `json:"desc"` +} + +// HorizontalContentList 二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 +type HorizontalContentList struct { + // 二级标题,建议不超过5个字 + KeyName string `json:"keyname"` + // 二级文本,如果horizontal_content_list.type是2,该字段代表文件名称(要包含文件类型),建议不超过30个字,(支持id转译) + Value string `json:"value"` + // 链接类型,0或不填代表不是链接,1 代表跳转url,2 代表下载附件,3 代表点击跳转成员详情 + Type int `json:"type,omitempty"` + // 链接跳转的url,horizontal_content_list.type是1时必填 + URL string `json:"url,omitempty"` + // 附件的media_id,horizontal_content_list.type是2时必填 + MediaID string `json:"media_id,omitempty"` + // 成员详情的userid,horizontal_content_list.type是3时必填 + Userid string `json:"userid,omitempty"` +} + +// JumpList 跳转指引样式的列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过3 +type JumpList struct { + // 跳转链接类型,0或不填代表不是链接,1 代表跳转url,2 代表跳转小程序 + Type int `json:"type"` + // 跳转链接样式的文案内容,建议不超过18个字 + Title string `json:"title"` + // 跳转链接的url,jump_list.type是1时必填 + URL string `json:"url,omitempty"` + // 跳转链接的小程序的appid,必须是与当前应用关联的小程序,jump_list.type是2时必填 + Appid string `json:"appid,omitempty"` + // 跳转链接的小程序的pagepath,jump_list.type是2时选填 + PagePath string `json:"pagepath,omitempty"` +} + +// CardAction 整体卡片的点击跳转事件,text_notice必填本字段 +type CardAction struct { + // 跳转事件类型,1 代表跳转url,2 代表打开小程序。text_notice卡片模版中该字段取值范围为[1,2] + Type int `json:"type"` + // 跳转事件的url,card_action.type是1时必填 + URL string `json:"url"` + // 跳转事件的小程序的appid,必须是与当前应用关联的小程序,card_action.type是2时必填 + Appid string `json:"appid"` + // 跳转事件的小程序的pagepath,card_action.type是2时选填 + Pagepath string `json:"pagepath"` +} + +// ImageTextArea 左图右文样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 +type ImageTextArea struct { + // 左图右文样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 + Type int `json:"type"` + // 点击跳转的url,image_text_area.type是1时必填 + URL string `json:"url"` + // 点击跳转的小程序的appid,必须是与当前应用关联的小程序,image_text_area.type是2时必填 + AppID string `json:"appid,omitempty"` + // 点击跳转的小程序的pagepath,image_text_area.type是2时选填 + PagePath string `json:"pagepath,omitempty"` + // 左图右文样式的标题 + Title string `json:"title"` + // 左图右文样式的描述 + Desc string `json:"desc"` + // 左图右文样式的图片url + ImageURL string `json:"image_url"` +} + +// CardImage 图片样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 +type CardImage struct { + // 图片的url + URL string `json:"url"` + // 图片的宽高比,宽高比要小于2.25,大于1.3,不填该参数默认1.3 + AspectRatio float32 `json:"aspect_ratio"` +} + +// ButtonSelection 按钮交互型 +type ButtonSelection struct { + // 下拉式的选择器的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 + QuestionKey string `json:"question_key"` + // 下拉式的选择器的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 + Title string `json:"title"` + // 选项列表,下拉选项不超过 10 个,最少1个 + OptionList []struct { + // 下拉式的选择器选项的id,用户提交后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 + ID string `json:"id"` + // 下拉式的选择器选项的文案,建议不超过16个字 + Text string `json:"text"` + } `json:"option_list"` + // 默认选定的id,不填或错填默认第一个 + SelectedID string `json:"selected_id"` +} + +type Button struct { + // 按钮点击事件类型,0 或不填代表回调点击事件,1 代表跳转url + Type int `json:"type,omitempty"` + // 按钮文案,建议不超过10个字 + Text string `json:"text"` + // 按钮样式,目前可填1~4,不填或错填默认1 + Style int `json:"style,omitempty"` + // 按钮key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复,button_list.type是0时必填 + Key string `json:"key,omitempty"` + // 跳转事件的url,button_list.type是1时必填 + URL string `json:"url,omitempty"` +} + +// CheckBox 选择题样式 +type CheckBox struct { + // 选择题key值,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 + QuestionKey string `json:"question_key"` + // 选项list,选项个数不超过 20 个,最少1个 + OptionList []struct { + // 选项id,用户提交选项后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 + ID string `json:"id"` + // 选项文案描述,建议不超过17个字 + Text string `json:"text"` + // 该选项是否要默认选中 + IsChecked bool `json:"is_checked"` + } `json:"option_list" validate:"required,min=1,max=20"` + // 选择题模式,单选:0,多选:1,不填默认0 + Mode int `json:"mode" validate:"omitempty,oneof=0 1"` +} + +// SubmitButton 提交按钮样式 +type SubmitButton struct { + // 按钮文案,建议不超过10个字,不填默认为提交 + Text string `json:"text"` + // 提交按钮的key,会产生回调事件将本参数作为EventKey返回,最长支持1024字节 + Key string `json:"key"` +} + +// SelectList 下拉式的选择器列表,multiple_interaction类型的卡片该字段不可为空,一个消息最多支持 3 个选择器 +type SelectList struct { + // 下拉式的选择器题目的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节,不可重复 + QuestionKey string `json:"question_key"` + // 下拉式的选择器上面的title + Title string `json:"title,omitempty"` + // 默认选定的id,不填或错填默认第一个 + SelectedID string `json:"selected_id,omitempty"` + OptionList []OptionList `json:"option_list"` +} + +// 项列表,下拉选项不超过 10 个,最少1个 +type OptionList struct { + // 下拉式的选择器选项的id,用户提交选项后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 + ID string `json:"id"` + // 下拉式的选择器选项的文案,建议不超过16个字 + Text string `json:"text"` +} + +// TemplateCardType 模板卡片的类型 +type TemplateCardType string + +const ( + CardTypeTextNotice TemplateCardType = "text_notice" // 文本通知型 + CardTypeNewsNotice TemplateCardType = "news_notice" // 图文展示型 + CardTypeButtonInteraction TemplateCardType = "button_interaction" // 按钮交互型 + CardTypeVoteInteraction TemplateCardType = "vote_interaction" // 投票选择型 + CardTypeMultipleInteraction TemplateCardType = "multiple_interaction" // 多项选择型 +) + +type TemplateCard struct { + CardType TemplateCardType `json:"card_type"` + Source Source `json:"source"` + ActionMenu *ActionMenu `json:"action_menu,omitempty" validate:"required_with=TaskID"` + TaskID string `json:"task_id,omitempty" validate:"required_with=ActionMenu"` + MainTitle *MainTitle `json:"main_title"` + QuoteArea *QuoteArea `json:"quote_area,omitempty"` + // 文本通知型 + EmphasisContent *EmphasisContent `json:"emphasis_content,omitempty"` + SubTitleText string `json:"sub_title_text,omitempty"` + // 图文展示型 + ImageTextArea *ImageTextArea `json:"image_text_area,omitempty"` + CardImage *CardImage `json:"card_image,omitempty"` + HorizontalContentList []HorizontalContentList `json:"horizontal_content_list"` + JumpList []JumpList `json:"jump_list"` + CardAction *CardAction `json:"card_action,omitempty"` + // 按钮交互型 + ButtonSelection *ButtonSelection `json:"button_selection,omitempty"` + ButtonList []Button `json:"button_list,omitempty" validate:"omitempty,max=6"` + // 投票选择型 + CheckBox *CheckBox `json:"checkbox,omitempty"` + SelectList []SelectList `json:"select_list,omitempty" validate:"max=3"` + SubmitButton *SubmitButton `json:"submit_button,omitempty"` +} + +type TemplateCardUpdateMessage struct { + UserIds []string `json:"userids" validate:"omitempty,max=100"` + PartyIds []int64 `json:"partyids" validate:"omitempty,max=100"` + TagIds []int32 `json:"tagids" validate:"omitempty,max=100"` + AtAll int `json:"atall,omitempty"` + ResponseCode string `json:"response_code"` + Button struct { + ReplaceName string `json:"replace_name"` + } `json:"button" validate:"required_without=TemplateCard"` + TemplateCard TemplateCard `json:"template_card" validate:"required_without=Button"` + ReplaceText string `json:"replace_text,omitempty"` +} + type reqTransferCustomer struct { // HandoverUserID 原跟进成员的userid HandoverUserID string `json:"handover_userid"` @@ -1119,6 +1517,121 @@ type respDelContactWayExternalContact struct { respCommon } +type reqGroupChatList struct { + ReqChatList ReqChatList +} + +func (x reqGroupChatList) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x.ReqChatList) +} + +var _ bodyer = reqGroupChatList{} + +type respGroupChatList struct { + respCommon + *RespGroupChatList +} + +type reqGroupChatInfo struct { + ChatID string `json:"chat_id"` + NeedName int64 `json:"need_name"` +} + +func (x reqGroupChatInfo) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +var _ bodyer = reqGroupChatInfo{} + +type respGroupChatInfo struct { + respCommon + GroupChat *RespGroupChatInfo `json:"group_chat"` +} + +// reqConvertOpenGIDToChatID 客户群opengid转换 请求 +type reqConvertOpenGIDToChatID struct { + OpenGID string `json:"opengid"` +} + +var _ bodyer = reqConvertOpenGIDToChatID{} + +func (x reqConvertOpenGIDToChatID) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respConvertOpenGIDToChatID 客户群opengid转换 响应 +type respConvertOpenGIDToChatID struct { + respCommon + + ChatID string `json:"chat_id"` +} + +type reqAddGroupChatJoinWayExternalContact struct { + ExternalGroupChatJoinWay +} + +var _ bodyer = reqAddGroupChatJoinWayExternalContact{} + +func (x reqAddGroupChatJoinWayExternalContact) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +type respAddGroupChatJoinWayExternalContact struct { + respCommon + + ConfigID string `json:"config_id"` +} + +type reqGetGroupChatJoinWayExternalContact struct { + ConfigID string `json:"config_id"` +} + +var _ bodyer = reqGetGroupChatJoinWayExternalContact{} + +func (x reqGetGroupChatJoinWayExternalContact) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +type respGetGroupChatJoinWayExternalContact struct { + respCommon + JoinWay ExternalContactGroupChatJoinWay `json:"join_way"` +} + +type ExternalContactGroupChatJoinWay struct { + ConfigID string `json:"config_id"` + QRCode string `json:"qr_code"` + ExternalGroupChatJoinWay +} + +type reqUpdateGroupChatJoinWayExternalContact struct { + ConfigID string `json:"config_id"` + ExternalGroupChatJoinWay +} + +var _ bodyer = reqUpdateGroupChatJoinWayExternalContact{} + +func (x reqUpdateGroupChatJoinWayExternalContact) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +type respUpdateGroupChatJoinWayExternalContact struct { + respCommon +} + +type reqDelGroupChatJoinWayExternalContact struct { + ConfigID string `json:"config_id"` +} + +var _ bodyer = reqDelGroupChatJoinWayExternalContact{} + +func (x reqDelGroupChatJoinWayExternalContact) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +type respDelGroupChatJoinWayExternalContact struct { + respCommon +} + type reqCloseTempChatExternalContact struct { UserID string `json:"userid"` ExternalUserID string `json:"external_userid"` @@ -1179,3 +1692,276 @@ var _ bodyer = reqExternalContactAddCorpTagGroup{} func (x reqExternalContactAddCorpTagGroup) intoBody() ([]byte, error) { return marshalIntoJSONBody(x.ExternalContactAddCorpTagGroup) } + +// reqKfAccountCreate 创建客服账号 +type reqKfAccountCreate struct { + Name string `json:"name"` + MediaID string `json:"media_id"` +} + +var _ bodyer = reqKfAccountCreate{} + +func (x reqKfAccountCreate) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfAccountCreate 创建客服账号 响应 +type respKfAccountCreate struct { + respCommon + + OpenKfID string `json:"open_kfid"` +} + +// reqKfAccountDelete 删除客服账号 +type reqKfAccountDelete struct { + OpenKfID string `json:"open_kfid"` +} + +var _ bodyer = reqKfAccountDelete{} + +func (x reqKfAccountDelete) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfAccountDelete 删除客服账号 响应 +type respKfAccountDelete struct { + respCommon +} + +// reqKfAccountUpdate 修改客服账号 +type reqKfAccountUpdate struct { + OpenKfID string `json:"open_kfid"` + Name string `json:"name"` + MediaID string `json:"media_id"` +} + +var _ bodyer = reqKfAccountUpdate{} + +func (x reqKfAccountUpdate) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfAccountUpdate 修改客服账号 响应 +type respKfAccountUpdate struct { + respCommon +} + +// reqKfAccountList 获取客服账号列表 +type reqKfAccountList struct { + Offset int64 `json:"offset"` + Limit int64 `json:"limit"` +} + +var _ urlValuer = reqKfAccountList{} + +func (x reqKfAccountList) intoURLValues() url.Values { + return url.Values{ + "offset": {strconv.FormatInt(x.Offset, 10)}, + "limit": {strconv.FormatInt(x.Limit, 10)}, + } +} + +// respKfAccountList 客服账号列表 响应 +type respKfAccountList struct { + respCommon + + AccountList []*KfAccount `json:"account_list"` +} + +// reqAddKfContact 获取客服账号链接 +type reqAddKfContact struct { + OpenKfID string `json:"open_kfid"` + Scene string `json:"scene"` +} + +var _ bodyer = reqAddKfContact{} + +func (x reqAddKfContact) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respAddKfContact 获取客服账号链接 响应 +type respAddKfContact struct { + respCommon + + URL string `json:"url"` +} + +// reqKfServicerCreate 添加接待人员 +type reqKfServicerCreate struct { + OpenKfID string `json:"open_kfid"` + UserIDs []string `json:"userid_list"` + DepartmentIDs []int64 `json:"department_id_list"` +} + +var _ bodyer = reqKfServicerCreate{} + +func (x reqKfServicerCreate) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfServicerCreate 添加接待人员 响应 +type respKfServicerCreate struct { + respCommon + + ResultList []*KfServicerResult `json:"result_list"` +} + +// reqKfServicerDelete 删除接待人员 +type reqKfServicerDelete struct { + OpenKfID string `json:"open_kfid"` + UserIDs []string `json:"userid_list"` + DepartmentIDs []int64 `json:"department_id_list"` +} + +var _ bodyer = reqKfServicerDelete{} + +func (x reqKfServicerDelete) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfServicerDelete 删除接待人员 响应 +type respKfServicerDelete struct { + respCommon + + ResultList []*KfServicerResult `json:"result_list"` +} + +// reqKfServicerList 获取接待人员列表 +type reqKfServicerList struct { + OpenKfID string `json:"open_kfid"` +} + +var _ urlValuer = reqKfServicerList{} + +func (x reqKfServicerList) intoURLValues() url.Values { + return url.Values{ + "open_kfid": {x.OpenKfID}, + } +} + +// respKfServicerList 接待人员列表 响应 +type respKfServicerList struct { + respCommon + + ServicerList []*KfServicer `json:"servicer_list"` +} + +// reqKfServiceStateGet 获取会话状态 +type reqKfServiceStateGet struct { + OpenKfID string `json:"open_kfid"` + ExternalUserID string `json:"external_userid"` +} + +var _ bodyer = reqKfServiceStateGet{} + +func (x reqKfServiceStateGet) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfServiceStateGet 获取会话状态 响应 +type respKfServiceStateGet struct { + respCommon + + ServiceState KfServiceState `json:"service_state"` + ServicerUserID string `json:"servicer_userid"` +} + +// reqKfServiceStateTrans 变更会话状态 +type reqKfServiceStateTrans struct { + OpenKfID string `json:"open_kfid"` + ExternalUserID string `json:"external_userid"` + ServiceState KfServiceState `json:"service_state"` + ServicerUserID string `json:"servicer_userid"` +} + +var _ bodyer = reqKfServiceStateTrans{} + +func (x reqKfServiceStateTrans) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfServiceStateTrans 变更会话状态 响应 +type respKfServiceStateTrans struct { + respCommon + + MsgCode string `json:"msg_code"` +} + +// reqKfSyncMsg 读取消息 +type reqKfSyncMsg struct { + OpenKfID string `json:"open_kfid"` + Cursor string `json:"cursor"` + Token string `json:"token"` + Limit int64 `json:"limit"` + VoiceFormat int `json:"voice_format"` +} + +var _ bodyer = reqKfSyncMsg{} + +func (x reqKfSyncMsg) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respKfSyncMsg 读取消息 响应 +type respKfSyncMsg struct { + respCommon + NextCursor string `json:"next_cursor"` + HasMore int `json:"has_more"` + MsgList []KfMsg `json:"msg_list"` +} + +// reqOAGetCorpVacationConf 获取企业假期管理配置 +type reqOAGetCorpVacationConf struct { +} + +var _ urlValuer = reqOAGetCorpVacationConf{} + +func (x reqOAGetCorpVacationConf) intoURLValues() url.Values { + return url.Values{} +} + +// respOAGetCorpVacationConf 获取企业假期管理配置 响应 +type respOAGetCorpVacationConf struct { + respCommon + // Lists 假期列表 + Lists []CorpVacationConf `json:"lists"` +} + +// reqOAGetUserVacationQuota 获取成员假期余额 +type reqOAGetUserVacationQuota struct { + UserID string `json:"userid"` +} + +var _ bodyer = reqOAGetUserVacationQuota{} + +func (x reqOAGetUserVacationQuota) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respOAGetUserVacationQuota 获取成员假期余额 响应 +type respOAGetUserVacationQuota struct { + respCommon + // Lists 假期列表 + Lists []UserVacationQuota `json:"lists"` +} + +// reqOASetOneUserVacationQuota 修改成员假期余额 +type reqOASetOneUserVacationQuota struct { + UserID string `json:"userid"` + VacationID string `json:"vacation_id"` + LeftDuration string `json:"leftduration"` + TimeAttr int64 `json:"time_attr"` + Remarks string `json:"remarks"` +} + +var _ bodyer = reqOASetOneUserVacationQuota{} + +func (x reqOASetOneUserVacationQuota) intoBody() ([]byte, error) { + return marshalIntoJSONBody(x) +} + +// respOASetOneUserVacationQuota 修改成员假期余额 响应 +type respOASetOneUserVacationQuota struct { + respCommon +} diff --git a/oa.go b/oa.go index 1eebc9d..f26be16 100644 --- a/oa.go +++ b/oa.go @@ -53,6 +53,30 @@ func (c *WorkwxApp) GetOAApprovalDetail(spNo string) (*OAApprovalDetail, error) return &resp.Info, nil } +// GetOAGetCorpVacationConf 获取企业假期管理配置 +func (c *WorkwxApp) GetOAGetCorpVacationConf() ([]CorpVacationConf, error) { + resp, err := c.execOAGetCorpVacationConf(reqOAGetCorpVacationConf{}) + if err != nil { + return nil, err + } + return resp.Lists, nil +} + +// GetOAGetUserVacationQuota 获取成员假期余额 +func (c *WorkwxApp) GetOAGetUserVacationQuota(userID string) ([]UserVacationQuota, error) { + resp, err := c.execOAGetUserVacationQuota(reqOAGetUserVacationQuota{UserID: userID}) + if err != nil { + return nil, err + } + return resp.Lists, nil +} + +// SetOAOneUserVacationQuota 修改成员假期余额 +func (c *WorkwxApp) SetOAOneUserVacationQuota(req OASetOneUserVacationQuota) error { + _, err := c.execOASetOneUserVacationQuota(reqOASetOneUserVacationQuota(req)) + return err +} + // GetOAApprovalInfoReq 批量获取审批单号请求 type GetOAApprovalInfoReq struct { // StartTime 审批单提交的时间范围,开始时间,UNix时间戳 @@ -154,3 +178,12 @@ type OAApprovalInfoCommentUserInfo struct { // UserID 备注人userid UserID string `xml:"UserId"` } + +// OASetOneUserVacationQuota 修改成员假期余额 +type OASetOneUserVacationQuota struct { + UserID string + VacationID string + LeftDuration string + TimeAttr int64 + Remarks string +} diff --git a/oa.md.go b/oa.md.go index d648b01..d638288 100644 --- a/oa.md.go +++ b/oa.md.go @@ -78,12 +78,18 @@ type OAContentValue struct { Members []OAContentMember `json:"members"` // Departments 部门控件(control参数为Contact,且value参数为departments) Departments []OAContentDepartment `json:"departments"` + // Tips 说明文字控件(control参数为Tips) + Tips OATemplateControlConfigTips `json:"new_tips"` // Files 附件控件(control参数为File,且value参数为files) Files []OAContentFile `json:"files"` // Table 明细控件(control参数为Table) Table []OAContentTableList `json:"children"` // Vacation 假勤组件-请假组件(control参数为Vacation) Vacation OAContentVacation `json:"vacation"` + // Attendance 假勤组件-出差/外出/加班组件(control参数为Attendance) + Attendance OAContentVacationAttendance `json:"attendance"` + // PunchCorrection 假勤组件-出差/外出/加班组件(control参数为Attendance) + PunchCorrection OAContentPunchCorrection `json:"punch_correction"` // Location 位置控件(control参数为Location,且value参数为location) Location OAContentLocation `json:"location"` // RelatedApproval 关联审批单控件(control参数为RelatedApproval,且value参数为related_approval) @@ -92,6 +98,8 @@ type OAContentValue struct { Formula OAContentFormula `json:"formula"` // DateRange 时长组件(control参数为DateRange,且value参数为date_range) DateRange OAContentDateRange `json:"date_range"` + // BankAccount 收款账户控件(control参数为BankAccount) + BankAccount OAContentBankAccount `json:"bank_account"` } // OAContentDate 日期/日期+时间内容 @@ -114,6 +122,8 @@ type OAContentSelector struct { type OAContentSelectorOption struct { // Key 选项key,可通过“获取审批模板详情”接口获得 Key string `json:"key"` + // Value 选项值,若配置了多语言则会包含中英文的选项值 + Value []OAText `json:"value"` } // OAContentMember 所选成员内容,即申请人在此控件选择的成员,多选模式下可以有多个 @@ -158,6 +168,8 @@ type OAContentVacationAttendance struct { DateRange OAContentVacationAttendanceDateRange `json:"date_range"` // Type 假勤组件类型:1-请假;3-出差;4-外出;5-加班 Type uint8 `json:"type"` + // SliceInfo 时长支持按天分片信息, 2020/10/01之前的历史表单不支持时长分片 + SliceInfo OAContentVacationAttendanceSliceInfo `json:"slice_info"` } // OAContentVacationAttendanceDateRange 假勤组件时间选择范围 @@ -168,6 +180,36 @@ type OAContentVacationAttendanceDateRange struct { OAContentDateRange } +// OAContentVacationAttendanceSliceInfo 假勤组件时长支持按天分片信息, 2020/10/01之前的历史表单不支持时长分片 +type OAContentVacationAttendanceSliceInfo struct { + // Duration 总时长,单位是秒 + Duration uint64 `json:"duration"` + // State 时长计算来源类型: 1--系统自动计算;2--用户修改 + State uint8 `json:"state"` + // DayItems 时长计算来源类型: 1--系统自动计算;2--用户修改 + DayItems []OAContentVacationAttendanceSliceInfoDayItem `json:"day_items"` +} + +// OAContentVacationAttendanceSliceInfoDayItem 假勤组件时长支持按天分片信息,每一天的分片时长信息 +type OAContentVacationAttendanceSliceInfoDayItem struct { + // Daytime 日期的00:00:00时间戳,Unix时间 + Daytime uint64 `json:"daytime"` + // Duration 分隔当前日期的时长秒数 + Duration uint64 `json:"duration"` +} + +// OAContentPunchCorrection 补卡组件 +type OAContentPunchCorrection struct { + // State 异常状态说明 + State string `json:"state"` + // Time 补卡时间,Unix时间戳 + Time uint64 `json:"time"` + // Version 版本标识,为1的时候为新版补卡,daymonthyear有值 + Version uint8 `json:"version"` + // Daymonthyear 补卡日期0点Unix时间戳 + Daymonthyear uint64 `json:"daymonthyear"` +} + // OAContentLocation 位置控件 type OAContentLocation struct { // Latitude 纬度,精确到6位小数 @@ -196,12 +238,60 @@ type OAContentFormula struct { // OAContentDateRange 时长组件 type OAContentDateRange struct { + // Type 时间展示类型:halfday-日期;hour-日期+时间 + Type string `json:"type"` // NewBegin 开始时间,unix时间戳 NewBegin int `json:"new_begin"` // NewEnd 结束时间,unix时间戳 NewEnd int `json:"new_end"` // NewDuration 时长范围,单位秒 NewDuration int `json:"new_duration"` + // PerdayDuration 每天的工作时长 + PerdayDuration int `json:"perday_duration"` + // TimezoneInfo 时区信息,只有在非UTC+8的情况下会返回 + TimezoneInfo *OAContentDateRangeTimezoneInfo `json:"timezone_info"` +} + +// OAContentDateRangeTimezoneInfo 时区信息 +type OAContentDateRangeTimezoneInfo struct { + // ZoneOffset 时区偏移量 + ZoneOffset string `json:"zone_offset"` + // ZoneDesc 时区描述 + ZoneDesc string `json:"zone_desc"` +} + +// OAContentBankAccount 时长组件 +type OAContentBankAccount struct { + // AccountType 账户类型 :1:对公账户,2:个人账户 + AccountType uint8 `json:"account_type"` + // AccountName 账户名 + AccountName string `json:"account_name"` + // AccountNumber 账号 + AccountNumber string `json:"account_number"` + // Remark 备注 + Remark string `json:"remark"` + // Bank 银行信息 + Bank OAContentBankAccountBank `json:"bank"` +} + +// OAContentBankAccountBank 银行信息 +type OAContentBankAccountBank struct { + // BankAlias 银行名称 + BankAlias string `json:"bank_alias"` + // BankAliasCode 银行代码 + BankAliasCode string `json:"bank_alias_code"` + // Province 省份 + Province string `json:"province"` + // ProvinceCode 省份代码 + ProvinceCode uint8 `json:"province_code"` + // City 城市 + City string `json:"city"` + // CityCode 城市代码 + CityCode uint8 `json:"city_code"` + // BankBranchName 银行支行 + BankBranchName string `json:"bank_branch_name"` + // BankBranchId 银行支行联行号 + BankBranchId string `json:"bank_branch_id"` } // OATemplateDetail 审批模板详情 @@ -254,8 +344,12 @@ type OATemplateControlConfig struct { Contact OATemplateControlConfigContact `json:"contact"` // Table Table(明细控件) Table OATemplateControlConfigTable `json:"table"` - // Attendance Attendance控件(假勤控件) + // Attendance Attendance控件(假勤控件)【出差】【加班】【外出】模板特有的控件 Attendance OATemplateControlConfigAttendance `json:"attendance"` + // Vacation Vacation控件(假勤控件)【请假】模板特有控件, 请假类型强关联审批应用中的假期管理。 + Vacation OATemplateControlConfigVacation `json:"vacation_list"` + // Tips Tips控件(说明文字控件) + Tips OATemplateControlConfigTips `json:"tips"` } // OATemplateControlConfigDate 类型标志,日期/日期+时间控件的config中会包含此参数 @@ -322,6 +416,56 @@ type OATemplateControlConfigVacationItem struct { Name []OAText `json:"name"` } +// OATemplateControlConfigTips 类型标志,说明文字控件的config中会包含此参数 +type OATemplateControlConfigTips struct { + // TipsContent 说明文字数组,元素为不同语言的富文本说明文字 + TipsContent []OATemplateControlConfigTipsContent `json:"tips_content"` +} + +// OATemplateControlConfigTipsContent 类型标志,说明文字控件的config中会包含此参数 +type OATemplateControlConfigTipsContent struct { + // Text 某个语言的富文本说明文字数组,元素为不同文本类型的说明文字分段 + Text OATemplateControlConfigTipsContentText `json:"text"` + // Lang 语言类型 + Lang string `json:"lang"` +} + +// OATemplateControlConfigTipsContentText 类型标志,说明文字控件的config中会包含此参数 +type OATemplateControlConfigTipsContentText struct { + // SubText 说明文字分段 + SubText []OATemplateControlConfigTipsContentSubText `json:"sub_text"` +} + +// OATemplateControlConfigTipsContentSubText 类型标志,说明文字控件的config中会包含此参数 +type OATemplateControlConfigTipsContentSubText struct { + // Type 文本类型 1:纯文本 2:链接,每个说明文字中只支持包含一个链接 + Type uint8 `json:"type"` + // Content 内容 + Content OATemplateControlConfigTipsContentSubTextContent `json:"content"` +} + +// OATemplateControlConfigTipsContentSubTextContent 类型标志,说明文字控件的config中会包含此参数 +type OATemplateControlConfigTipsContentSubTextContent struct { + // Text 纯文本类型的内容 + Text *OATemplateControlConfigTipsContentSubTextContentPlain `json:"plain_text"` + // Lang 链接类型的内容 + Lang *OATemplateControlConfigTipsContentSubTextContentLink `json:"link"` +} + +// OATemplateControlConfigTipsContentSubTextContentPlain 类型标志,说明文字控件的config中会包含此参数 +type OATemplateControlConfigTipsContentSubTextContentPlain struct { + // Content 纯文本文字 + Content string `json:"content"` +} + +// OATemplateControlConfigTipsContentSubTextContentLink 类型标志,说明文字控件的config中会包含此参数 +type OATemplateControlConfigTipsContentSubTextContentLink struct { + // Title 链接标题 + Title string `json:"title"` + // URL 链接url + URL string `json:"url"` +} + // OAControl 控件类型 type OAControl string @@ -483,3 +627,99 @@ const OAApprovalInfoFilterKeyDepartment OAApprovalInfoFilterKey = "department" // OAApprovalInfoFilterKeySpStatus 审批状态 const OAApprovalInfoFilterKeySpStatus OAApprovalInfoFilterKey = "sp_status" + +// CorpVacationConf 企业假期管理配置 +type CorpVacationConf struct { + // ID 假期id + ID uint32 `json:"id"` + // Name 假期名称 + Name string `json:"name"` + // TimeAttr 假期时间刻度:0-按天请假;1-按小时请假 + TimeAttr uint32 `json:"time_attr"` + // DurationType 时长计算类型:0-自然日;1-工作日 + DurationType uint32 `json:"duration_type"` + // QuotaAttr 假期发放相关配置 + QuotaAttr CorpVacationConfQuotaAttr `json:"quota_attr"` + // PerdayDuration 单位换算值,即1天对应的秒数,可将此值除以3600得到一天对应的小时。 + PerdayDuration uint32 `json:"perday_duration"` + // IsNewovertime 是否关联加班调休,0-不关联,1-关联,关联后改假期类型变为调休假 + IsNewovertime *uint32 `json:"is_newovertime"` + // EnterCompTimeLimit 入职时间大于n个月可用该假期,单位为月 + EnterCompTimeLimit *uint32 `json:"enter_comp_time_limit"` + // ExpireRule 假期过期规则 + ExpireRule *CorpVacationConfExpireRule `json:"expire_rule"` +} + +// CorpVacationConfQuotaAttr 企业假期管理配置-假期发放相关配置 +type CorpVacationConfQuotaAttr struct { + // Type 假期发放类型:0-不限额;1-自动按年发放;2-手动发放;3-自动按月发放 + Type uint32 `json:"type"` + // AutoresetTime 自动发放时间戳,若假期发放为自动发放,此参数代表自动发放日期。注:返回时间戳的年份是无意义的,请只使用返回时间的月和日;若at_entry_date为true,该字段则无效,假期发放时间为员工入职时间 + AutoresetTime uint32 `json:"autoreset_time"` + // AutoresetDuration 自动发放时长,单位为秒。注:只有自动按年发放和自动按月发放时有效,若选择了按照工龄和司龄发放,该字段无效,发放时长请使用区间中的quota + AutoresetDuration uint32 `json:"autoreset_duration"` + // QuotaRuleType 额度计算类型,自动按年发放时有效,0-固定额度;1-按工龄计算;2-按司龄计算 + QuotaRuleType *uint32 `json:"quota_rule_type"` + // QuotaRules 额度计算规则,自动按年发放时有效 + QuotaRules *CorpVacationConfQuotaRules `json:"quota_rules"` + // AtEntryDate 是否按照入职日期发放假期,只有在自动按年发放类型有效,选择后发放假期的时间会成为员工入职的日期 + AtEntryDate *bool `json:"at_entry_date"` + // AutoResetMonthDay 自动按月发放的发放时间,只有自动按月发放类型有效 + AutoResetMonthDay *uint32 `json:"auto_reset_month_day"` +} + +// CorpVacationConfQuotaRules 企业假期管理配置-额度计算规则 +type CorpVacationConfQuotaRules struct { + // List 额度计算规则区间,只有在选择了按照工龄计算或者按照司龄计算时有效 + List []CorpVacationConfQuotaRule `json:"list"` +} + +// CorpVacationConfQuotaRule 企业假期管理配置-额度计算规则区间 +type CorpVacationConfQuotaRule struct { + // Quota 区间发放时长,单位为s + Quota uint32 `json:"quota"` + // Begin 区间开始点,单位为年 + Begin uint32 `json:"begin"` + // End 区间结束点,无穷大则为0,单位为年 + End uint32 `json:"end"` + // BasedOnActualWorkTime 是否根据实际入职时间计算假期,选择后会根据员工在今年的实际工作时间发放假期 + BasedOnActualWorkTime bool `json:"based_on_actual_work_time"` +} + +// CorpVacationConfExpireRule 企业假期管理配置-假期过期规则 +type CorpVacationConfExpireRule struct { + // Type 过期规则类型,1-按固定时间过期,2-从发放日按年过期,3-从发放日按月过期,4-不过期 + Type uint32 `json:"type"` + // Duration 有效期,按年过期为年,按月过期为月,只有在以上两种情况时有效 + Duration uint64 `json:"duration"` + // Date 失效日期,只有按固定时间过期时有效 + Date CorpVacationConfDate `json:"date"` + // ExternDurationEnable 是否允许延长有效期 + ExternDurationEnable bool `json:"extern_duration_enable"` + // ExternDuration 延长有效期的具体时间,只有在extern_duration_enable为true时有效 + ExternDuration CorpVacationConfDate `json:"extern_duration"` +} + +// CorpVacationConfDate 企业假期管理配置-失效日期 +type CorpVacationConfDate struct { + // Month 月份 + Month uint32 `json:"month"` + // Day 日 + Day uint32 `json:"day"` +} + +// UserVacationQuota 假期列表 +type UserVacationQuota struct { + // ID 假期id + ID uint32 `json:"id"` + // AssignDuration 发放时长,单位为秒 + AssignDuration uint32 `json:"assignduration"` + // UsedDuration 使用时长,单位为秒 + UsedDuration uint32 `json:"usedduration"` + // LeftDuration 剩余时长,单位为秒 + LeftDuration uint32 `json:"leftduration"` + // VacationName 假期名称 + VacationName string `json:"vacationname"` + // RealAssignDuration 假期的实际发放时长,通常在设置了按照实际工作时间发放假期后进行计算 + RealAssignDuration uint32 `json:"real_assignduration"` +} diff --git a/recipient.go b/recipient.go index f1ae146..9446157 100644 --- a/recipient.go +++ b/recipient.go @@ -10,6 +10,10 @@ type Recipient struct { TagIDs []string // ChatID 应用关联群聊ID,仅用于【发送消息到群聊会话】 ChatID string + // OpenKfID 应用关联客服ID,仅用于【客服发送消息】 + OpenKfID string + // Code 仅用于【客服发送欢迎语等事件响应消息】 + Code string } // isIndividualTargetsEmpty 对非群发收件人字段而言,是否全为空 @@ -23,6 +27,16 @@ func (x *Recipient) isIndividualTargetsEmpty() bool { // isValidForMessageSend 本结构体是否对【发送应用消息】请求有效 func (x *Recipient) isValidForMessageSend() bool { + if x.OpenKfID != "" { + // 这时候你应该用 KfSend 接口 + return false + } + + if x.Code != "" { + // 这时候你应该用 KfOnEventSend 接口 + return false + } + if x.ChatID != "" { // 这时候你应该用 AppchatSend 接口 return false @@ -43,9 +57,38 @@ func (x *Recipient) isValidForMessageSend() bool { // isValidForAppchatSend 本结构体是否对【发送消息到群聊会话】请求有效 func (x *Recipient) isValidForAppchatSend() bool { + if x.OpenKfID != "" { + // 这时候你应该用 KfSend 接口 + return false + } + + if x.Code != "" { + // 这时候你应该用 KfOnEventSend 接口 + return false + } + if !x.isIndividualTargetsEmpty() { return false } return x.ChatID != "" } + +// isValidForKfSend 本结构体是否对【客服发送消息】请求有效 +func (x *Recipient) isValidForKfSend() bool { + if x.Code != "" { + // 这时候你应该用 KfOnEventSend 接口 + return false + } + + if !x.isIndividualTargetsEmpty() { + return false + } + + return x.OpenKfID != "" +} + +// isValidForKfOnEventSend 本结构体是否对【客服发送欢迎语等事件响应消息】请求有效 +func (x *Recipient) isValidForKfOnEventSend() bool { + return x.Code != "" +} diff --git a/rx_msg.go b/rx_msg.go index 6bd73fb..d3be45d 100644 --- a/rx_msg.go +++ b/rx_msg.go @@ -206,6 +206,12 @@ func (m *RxMessage) EventAppUnsubscribe() (*rxEventAppUnsubscribe, bool) { return y, ok } +// EventKfMsgOrEvent 如果消息为客服接收消息和事件,则拿出相应消息参数,否则返回 nil, false +func (m *RxMessage) EventKfMsgOrEvent() (*rxEventKfMsgOrEvent, bool) { + y, ok := m.extras.(*rxEventKfMsgOrEvent) + return y, ok +} + // EventUnknown 未定义的event类型 func (m *RxMessage) EventUnknown() (*rxEventUnknown, bool) { y, ok := m.extras.(*rxEventUnknown) diff --git a/rx_msg.md.go b/rx_msg.md.go index 6c89300..0719b13 100644 --- a/rx_msg.md.go +++ b/rx_msg.md.go @@ -61,6 +61,9 @@ const EventTypeSysApprovalChange EventType = "sys_approval_change" // EventTypeChangeContact 通讯录回调通知 const EventTypeChangeContact EventType = "change_contact" +// EventTypeKfMsgOrEvent 客服回调通知 +const EventTypeKfMsgOrEvent EventType = "kf_msg_or_event" + // ChangeType 变更类型 type ChangeType string @@ -366,6 +369,14 @@ type rxEventAppUnsubscribe struct { EventKey string `xml:"EventKey"` } +// rxEventKfMsgOrEvent 接受的事件消息,客服接收消息和事件 +type rxEventKfMsgOrEvent struct { + // OpenKfID 有新消息的客服账号。可通过sync_msg接口指定open_kfid获取此客服账号的消息 + OpenKfID string `xml:"OpenKfId"` + // Token 调用拉取消息接口时,需要传此token,用于校验请求的合法性 + Token string `xml:"Token"` +} + // rxEventUnknown 接受的事件消息,未定义的事件类型 type rxEventUnknown struct { // EventType 事件类型 diff --git a/rx_msg_extras.go b/rx_msg_extras.go index f089b94..ca22b75 100644 --- a/rx_msg_extras.go +++ b/rx_msg_extras.go @@ -178,6 +178,14 @@ func extractMessageExtras(common rxMessageCommon, body []byte) (messageKind, err return nil, err } return &x, nil + case EventTypeKfMsgOrEvent: + var x rxEventKfMsgOrEvent + err := xml.Unmarshal(body, &x) + if err != nil { + return nil, err + } + return &x, nil + default: // 返回一个未定义的事件类型 return &rxEventUnknown{EventType: string(common.Event), Raw: string(body)}, nil @@ -707,6 +715,36 @@ func (r rxEventAppUnsubscribe) formatInto(w io.Writer) { _, _ = fmt.Fprintf(w, "EventKey: %#v", r.EventKey) } +// EventKfMsgOrEvent 客服接收消息和事件 +type EventKfMsgOrEvent interface { + messageKind + + // GetOpenKfID 客服账号ID + GetOpenKfID() string + + // GetToken 调用拉取消息接口时,需要传此token,用于校验请求的合法性 + GetToken() string +} + +var _ EventKfMsgOrEvent = (*rxEventKfMsgOrEvent)(nil) + +func (r *rxEventKfMsgOrEvent) formatInto(w io.Writer) { + _, _ = fmt.Fprintf( + w, + "OpenKfID: %#v, Token: %#v", + r.OpenKfID, + r.Token, + ) +} + +func (r *rxEventKfMsgOrEvent) GetOpenKfID() string { + return r.OpenKfID +} + +func (r *rxEventKfMsgOrEvent) GetToken() string { + return r.Token +} + func (r rxEventUnknown) formatInto(w io.Writer) { _, _ = fmt.Fprintf(w, "Raw: %#v", r.Raw) } diff --git a/token.go b/token.go index f388aee..0a943c3 100644 --- a/token.go +++ b/token.go @@ -234,3 +234,12 @@ func (c *WorkwxApp) JSCode2Session(jscode string) (*JSCodeSession, error) { } return &resp.JSCodeSession, nil } + +// AuthCode2UserInfo 获取访问用户身份 +func (c *WorkwxApp) AuthCode2UserInfo(code string) (*AuthCodeUserInfo, error) { + resp, err := c.execAuthCode2UserInfo(reqAuthCode2UserInfo{Code: code}) + if err != nil { + return nil, err + } + return &resp.AuthCodeUserInfo, nil +} diff --git a/user_info.go b/user_info.go index d3985b8..4beb0e0 100644 --- a/user_info.go +++ b/user_info.go @@ -1,5 +1,25 @@ package workwx +// UserDetail 成员详细信息的公共字段 +type UserDetail struct { + UserID string `json:"userid"` + Name string `json:"name,omitempty"` + DeptIDs []int64 `json:"department"` + DeptOrder []uint32 `json:"order"` + Position string `json:"position"` + Mobile string `json:"mobile,omitempty"` + Gender string `json:"gender,omitempty"` + Email string `json:"email,omitempty"` + IsLeaderInDept []int `json:"is_leader_in_dept"` + AvatarURL string `json:"avatar"` + Telephone string `json:"telephone"` + IsEnabled int `json:"enable"` + Alias string `json:"alias"` + Status int `json:"status"` + QRCodeURL string `json:"qr_code"` + // TODO: extattr external_profile external_position +} + // GetUser 读取成员 func (c *WorkwxApp) GetUser(userid string) (*UserInfo, error) { resp, err := c.execUserGet(reqUserGet{ @@ -18,6 +38,17 @@ func (c *WorkwxApp) GetUser(userid string) (*UserInfo, error) { return &obj, nil } +// UpdateUser 更新成员 +func (c *WorkwxApp) UpdateUser(userDetail *UserDetail) error { + _, err := c.execUserUpdate(reqUserUpdate{ + UserDetail: userDetail, + }) + if err != nil { + return err + } + return nil +} + // ListUsersByDeptID 获取部门成员详情 func (c *WorkwxApp) ListUsersByDeptID(deptID int64, fetchChild bool) ([]*UserInfo, error) { resp, err := c.execUserList(reqUserList{ @@ -38,6 +69,39 @@ func (c *WorkwxApp) ListUsersByDeptID(deptID int64, fetchChild bool) ([]*UserInf return users, nil } +// ConvertUserIDToOpenID userid转openid +func (c *WorkwxApp) ConvertUserIDToOpenID(userID string) (string, error) { + resp, err := c.execConvertUserIDToOpenID(reqConvertUserIDToOpenID{ + UserID: userID, + }) + if err != nil { + return "", err + } + return resp.OpenID, nil +} + +// ConvertOpenIDToUserID openid转userid +func (c *WorkwxApp) ConvertOpenIDToUserID(openID string) (string, error) { + resp, err := c.execConvertOpenIDToUserID(reqConvertOpenIDToUserID{ + OpenID: openID, + }) + if err != nil { + return "", err + } + return resp.UserID, nil +} + +// GetUserJoinQrcode 获取加入企业二维码 +func (c *WorkwxApp) GetUserJoinQrcode(sizeType SizeType) (string, error) { + resp, err := c.execUserJoinQrcode(reqUserJoinQrcode{ + SizeType: sizeType, + }) + if err != nil { + return "", err + } + return resp.JoinQrcode, nil +} + // GetUserIDByMobile 通过手机号获取 userid func (c *WorkwxApp) GetUserIDByMobile(mobile string) (string, error) { resp, err := c.execUserIDByMobile(reqUserIDByMobile{ @@ -49,6 +113,21 @@ func (c *WorkwxApp) GetUserIDByMobile(mobile string) (string, error) { return resp.UserID, nil } +// GetUserIDByEmail 通过邮箱获取 userid +func (c *WorkwxApp) GetUserIDByEmail(email string, emailType EmailType) (string, error) { + if emailType == 0 { + emailType = EmailTypeCorporate + } + resp, err := c.execUserIDByEmail(reqUserIDByEmail{ + Email: email, + EmailType: emailType, + }) + if err != nil { + return "", err + } + return resp.UserID, nil +} + // GetUserInfoByCode 获取访问用户身份,根据code获取成员信息 func (c *WorkwxApp) GetUserInfoByCode(code string) (*UserIdentityInfo, error) { resp, err := c.execUserInfoGet(reqUserInfoGet{ diff --git a/user_info_helper.go b/user_info_helper.go index a110743..3f2f7ff 100644 --- a/user_info_helper.go +++ b/user_info_helper.go @@ -54,7 +54,7 @@ func userGenderFromGenderStr(x string) (UserGender, error) { return UserGender(n), nil } -func (x respUserDetail) intoUserInfo() (UserInfo, error) { +func (x UserDetail) intoUserInfo() (UserInfo, error) { deptInfo, err := reshapeDeptInfo(x.DeptIDs, x.DeptOrder, x.IsLeaderInDept) if err != nil { return UserInfo{}, err