From 9529a72e65ffca4b96581364ec4d05bce4ac54b8 Mon Sep 17 00:00:00 2001 From: Ceelog Date: Tue, 18 Aug 2020 19:30:34 +0800 Subject: [PATCH] rebuild --- .github/workflows/ci.yaml | 16 + .gitignore | 7 + LICENSE | 13 + README.md | 78 ++ apis/appinfo/app_manage/app_manage.go | 206 +++ apis/appinfo/app_manage/app_manage_test.go | 286 ++++ .../app_manage/example_app_manage_test.go | 89 ++ apis/appinfo/appstore/appstore.go | 74 + apis/appinfo/appstore/appstore_test.go | 106 ++ .../appinfo/appstore/example_appstore_test.go | 43 + apis/bot/bot_manage/bot_manage.go | 104 ++ apis/bot/bot_manage/bot_manage_test.go | 138 ++ .../bot/bot_manage/example_bot_manage_test.go | 50 + .../group_manage/example_group_manage_test.go | 91 ++ apis/bot/group_manage/group_manage.go | 209 +++ apis/bot/group_manage/group_manage_test.go | 284 ++++ apis/capabilities/approve/approve.go | 401 ++++++ apis/capabilities/approve/approve_test.go | 535 ++++++++ .../approve/example_approve_test.go | 162 +++ apis/capabilities/calendar/calendar.go | 369 +++++ apis/capabilities/calendar/calendar_test.go | 474 +++++++ .../calendar/example_calendar_test.go | 143 ++ apis/capabilities/document/document.go | 749 ++++++++++ apis/capabilities/document/document_test.go | 1211 +++++++++++++++++ .../document/example_document_test.go | 345 +++++ .../meeting/example_meeting_test.go | 181 +++ apis/capabilities/meeting/meeting.go | 459 +++++++ apis/capabilities/meeting/meeting_test.go | 615 +++++++++ apis/contact/async_batch/async_batch.go | 110 ++ apis/contact/async_batch/async_batch_test.go | 141 ++ .../async_batch/example_async_batch_test.go | 53 + apis/contact/contact.go | 57 + apis/contact/contact_test.go | 66 + apis/contact/department/department.go | 188 +++ apis/contact/department/department_test.go | 251 ++++ .../department/example_department_test.go | 83 ++ apis/contact/example_contact_test.go | 30 + apis/contact/user/example_user_test.go | 132 ++ apis/contact/user/user.go | 320 +++++ apis/contact/user/user_test.go | 435 ++++++ apis/message/example_message_test.go | 93 ++ apis/message/message.go | 216 +++ apis/message/message_test.go | 286 ++++ apis/user_group/example_user_group_test.go | 53 + apis/user_group/user_group.go | 104 ++ apis/user_group/user_group_test.go | 143 ++ client.go | 137 ++ cmd/apiconfig.go | 727 ++++++++++ cmd/apiconfig2.go | 945 +++++++++++++ cmd/build.go | 370 +++++ cmd/data/.gitignore | 2 + cmd/parseDocLink.go | 158 +++ cmd/parseEvent.go | 147 ++ doc/apilist.md | 260 ++++ doc/img/sdk.gliffy | 1 + doc/img/sdk.jpg | Bin 0 -> 80176 bytes feishu.go | 102 ++ go.mod | 12 + go.sum | 83 ++ internal_app.go | 237 ++++ public_app.go | 306 +++++ server.go | 322 +++++ server_test.go | 94 ++ test/test.go | 55 + types/event_types/app_event.go | 190 +++ types/event_types/approve_event.go | 314 +++++ types/event_types/bot_message_event.go | 508 +++++++ types/event_types/calendar_event.go | 60 + types/event_types/contact_event.go | 159 +++ types/event_types/event.go | 39 + types/event_types/group_chat_event.go | 107 ++ util/aes_crypto.go | 65 + util/aes_crypto_test.go | 74 + util/randstring.go | 38 + util/randstring_test.go | 68 + 75 files changed, 15779 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 apis/appinfo/app_manage/app_manage.go create mode 100644 apis/appinfo/app_manage/app_manage_test.go create mode 100644 apis/appinfo/app_manage/example_app_manage_test.go create mode 100644 apis/appinfo/appstore/appstore.go create mode 100644 apis/appinfo/appstore/appstore_test.go create mode 100644 apis/appinfo/appstore/example_appstore_test.go create mode 100644 apis/bot/bot_manage/bot_manage.go create mode 100644 apis/bot/bot_manage/bot_manage_test.go create mode 100644 apis/bot/bot_manage/example_bot_manage_test.go create mode 100644 apis/bot/group_manage/example_group_manage_test.go create mode 100644 apis/bot/group_manage/group_manage.go create mode 100644 apis/bot/group_manage/group_manage_test.go create mode 100644 apis/capabilities/approve/approve.go create mode 100644 apis/capabilities/approve/approve_test.go create mode 100644 apis/capabilities/approve/example_approve_test.go create mode 100644 apis/capabilities/calendar/calendar.go create mode 100644 apis/capabilities/calendar/calendar_test.go create mode 100644 apis/capabilities/calendar/example_calendar_test.go create mode 100644 apis/capabilities/document/document.go create mode 100644 apis/capabilities/document/document_test.go create mode 100644 apis/capabilities/document/example_document_test.go create mode 100644 apis/capabilities/meeting/example_meeting_test.go create mode 100644 apis/capabilities/meeting/meeting.go create mode 100644 apis/capabilities/meeting/meeting_test.go create mode 100644 apis/contact/async_batch/async_batch.go create mode 100644 apis/contact/async_batch/async_batch_test.go create mode 100644 apis/contact/async_batch/example_async_batch_test.go create mode 100644 apis/contact/contact.go create mode 100644 apis/contact/contact_test.go create mode 100644 apis/contact/department/department.go create mode 100644 apis/contact/department/department_test.go create mode 100644 apis/contact/department/example_department_test.go create mode 100644 apis/contact/example_contact_test.go create mode 100644 apis/contact/user/example_user_test.go create mode 100644 apis/contact/user/user.go create mode 100644 apis/contact/user/user_test.go create mode 100644 apis/message/example_message_test.go create mode 100644 apis/message/message.go create mode 100644 apis/message/message_test.go create mode 100644 apis/user_group/example_user_group_test.go create mode 100644 apis/user_group/user_group.go create mode 100644 apis/user_group/user_group_test.go create mode 100644 client.go create mode 100644 cmd/apiconfig.go create mode 100644 cmd/apiconfig2.go create mode 100644 cmd/build.go create mode 100644 cmd/data/.gitignore create mode 100644 cmd/parseDocLink.go create mode 100644 cmd/parseEvent.go create mode 100644 doc/apilist.md create mode 100644 doc/img/sdk.gliffy create mode 100644 doc/img/sdk.jpg create mode 100644 feishu.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal_app.go create mode 100644 public_app.go create mode 100644 server.go create mode 100644 server_test.go create mode 100644 test/test.go create mode 100644 types/event_types/app_event.go create mode 100644 types/event_types/approve_event.go create mode 100644 types/event_types/bot_message_event.go create mode 100644 types/event_types/calendar_event.go create mode 100644 types/event_types/contact_event.go create mode 100644 types/event_types/event.go create mode 100644 types/event_types/group_chat_event.go create mode 100644 util/aes_crypto.go create mode 100644 util/aes_crypto_test.go create mode 100644 util/randstring.go create mode 100644 util/randstring_test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..7cff7a6 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,16 @@ +on: push +name: golang-ci +jobs: + checks: + name: run + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - name: run + uses: cedrickring/golang-action@1.5.2 + env: + GO111MODULE: "on" + - name: Use Go 1.13 + uses: cedrickring/golang-action/go1.13@1.5.2 + env: + GO111MODULE: "on" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5ac7d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.idea/ +book/ +*tmp.go +TODO +demo +venv diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f929b60 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe48de4 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# fastwego/feishu + +A fast feishu development sdk written in Golang + +## 快速开始 & demo + +```shell script +go get github.com/fastwego/feishu +``` +```go +App = feishu.NewApp(feishu.AppConfig{ + AppId: "APPID", + AppSecret: "SECRET", +}) + +// 调用 api 接口 +params = url.Values{} +params.Add("calendarId", "10086") +resp, err := calendar.GetCalendarById(App, params) +fmt.Println(string(resp), err) +``` + +完整演示项目: + +[https://github.com/fastwego/feishu-demo](https://github.com/fastwego/feishu-demo) + +API 列表: + +[doc/apilist.md](doc/apilist.md) + +## 架构设计 + +![sdk](./doc/img/sdk.jpg) + +## 框架特点 + +### 快速 + +「快」作为框架设计的核心理念,体现在方方面面: + +- 使用 Go 语言,开发快、编译快、部署快、运行快,轻松服务海量用户 +- 丰富的[文档](https://pkg.go.dev/github.com/fastwego/feishu) 和 [演示代码](https://github.com/fastwego/feishu-demo) ,快速上手,5 分钟即可搭建一个完整地飞书 App +- 独立清晰的模块划分,快速熟悉整个框架,没有意外,一切都是你期望的样子 +- 甚至连框架自身的大部分代码也是自动生成的,维护更新快到超乎想象 + +### 符合直觉 + +作为第三方开发框架,尽可能贴合官方文档和设计,不引入新的概念,不给开发者添加学习负担 + +### 简洁而不过度封装 + +作为具体业务和飞书服务的中间层,专注于通道的角色:帮业务把配置/材料投递到飞书,将飞书响应/推送透传回业务 + +至于 AccessToken 管理 和 消息加解密处理,框架内部完成得干净利落,开发者甚至觉察不到存在 + +### 官方文档就是最好的文档 + +每个接口的注释都附带官方文档的链接,让你随时翻阅,省时省心 + +### 完备的单元测试 + +100% 覆盖每一个接口,让你每一次调用都信心满满 + +### 详细的日志 + +每个关键环节都为你完整记录,Debug 倍轻松,你可以自由定义日志输出,甚至可以关闭日志 + +### 活跃的开发者社区 + +FastWeGo 是一套丰富的 Go 服务开发框架,支持飞书、微信等服务,拥有庞大的开发者用户群体 + +你遇到的所有问题几乎都可以在社区找到解决方案 + +## 参与贡献 + +欢迎提交 pull request / issue / 文档,一起让微信开发更快更好! + +Faster we go together! diff --git a/apis/appinfo/app_manage/app_manage.go b/apis/appinfo/app_manage/app_manage.go new file mode 100644 index 0000000..c73473d --- /dev/null +++ b/apis/appinfo/app_manage/app_manage.go @@ -0,0 +1,206 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package app_manage 应用信息/应用管理 +package app_manage + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiIsUserAdmin = "/open-apis/application/v3/is_user_admin" + apiAdminScopeGet = "/open-apis/contact/v1/user/admin_scope/get" + apiAppVisibility = "/open-apis/application/v2/app/visibility" + apiVisibleApps = "/open-apis/application/v1/user/visible_apps" + apiAppList = "/open-apis/application/v3/app/list" + apiUpdateVisibility = "/open-apis/application/v3/app/update_visibility" + apiAppAdminUserList = "/open-apis/user/v4/app_admin_user/list" +) + +/* +校验应用管理员 + + +该接口用于查询用户是否为应用管理员,需要申请 校验用户是否为应用管理员 权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uITN1EjLyUTNx4iM1UTM + +GET https://open.feishu.cn/open-apis/application/v3/is_user_admin +*/ +func IsUserAdmin(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiIsUserAdmin, header) +} + +/* +获取应用管理员管理范围 + +该接口用于获取应用管理员的管理范围,即该应用管理员能够管理哪些部门。 + + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uMzN3QjLzczN04yM3cDN + +GET https://open.feishu.cn/open-apis/contact/v1/user/admin_scope/get?employee_id=2fab1234 +*/ +func AdminScopeGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiAdminScopeGet+"?"+params.Encode(), header) +} + +/* +获取应用在企业内的可用范围 + + +该接口用于查询应用在该企业内可以被使用的范围,只能被企业自建应用调用且需要“获取应用信息”权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uIjM3UjLyIzN14iMycTN + +GET https://open.feishu.cn/open-apis/application/v2/app/visibility?app_id=cli_9db45f86b7799104&user_page_token=0&user_page_size=100 +*/ +func AppVisibility(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiAppVisibility+"?"+params.Encode(), header) +} + +/* +获取用户可用的应用 + + +该接口用于查询用户可用的应用列表,只能被企业自建应用调用且需要“获取用户可用的应用”权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uMjM3UjLzIzN14yMycTN + +GET https://open.feishu.cn/open-apis/application/v1/user/visible_apps?user_id=79affdge&page_token=0&lang=zh_cn&page_size=5 +*/ +func VisibleApps(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiVisibleApps+"?"+params.Encode(), header) +} + +/* +获取企业安装的应用 + + +该接口用于查询企业安装的应用列表,只能被企业自建应用调用且需要“获取应用信息”权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uYDN3UjL2QzN14iN0cTN + +GET https://open.feishu.cn/open-apis/application/v3/app/list?page_size=5&page_token=0&lang=zh_cn&status=1 +*/ +func AppList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiAppList+"?"+params.Encode(), header) +} + +/* +更新应用可用范围 + + +该接口用于增加或者删除指定应用被哪些人可用,只能被企业自建应用调用且需要“管理应用可见范围”权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ucDN3UjL3QzN14yN0cTN + +POST https://open.feishu.cn/open-apis/application/v3/app/update_visibility +*/ +func UpdateVisibility(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiUpdateVisibility, bytes.NewReader(payload), header) +} + +/* +查询应用管理员列表 + +查询应用管理员列表,返回应用的最新10个管理员账户id列表。 + +**权限说明** : +本接口需要申请 获取应用管理员ID 权限才能访问。 +回包数据中的user_id 需要申请 获取用户 userid 权限才会返回 +回包数据中的union_id 需要申请 获取用户统一ID 权限才会返回 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ucDOwYjL3gDM24yN4AjN + +GET https://open.feishu.cn/open-apis/user/v4/app_admin_user/list +*/ +func AppAdminUserList(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiAppAdminUserList, header) +} diff --git a/apis/appinfo/app_manage/app_manage_test.go b/apis/appinfo/app_manage/app_manage_test.go new file mode 100644 index 0000000..11ee6df --- /dev/null +++ b/apis/appinfo/app_manage/app_manage_test.go @@ -0,0 +1,286 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app_manage + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestIsUserAdmin(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiIsUserAdmin, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := IsUserAdmin(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("IsUserAdmin() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("IsUserAdmin() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAdminScopeGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAdminScopeGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := AdminScopeGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("AdminScopeGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("AdminScopeGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAppVisibility(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAppVisibility, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := AppVisibility(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("AppVisibility() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("AppVisibility() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestVisibleApps(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiVisibleApps, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := VisibleApps(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("VisibleApps() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("VisibleApps() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAppList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAppList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := AppList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("AppList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("AppList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestUpdateVisibility(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiUpdateVisibility, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := UpdateVisibility(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("UpdateVisibility() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("UpdateVisibility() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAppAdminUserList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAppAdminUserList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := AppAdminUserList(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("AppAdminUserList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("AppAdminUserList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/appinfo/app_manage/example_app_manage_test.go b/apis/appinfo/app_manage/example_app_manage_test.go new file mode 100644 index 0000000..66a4e49 --- /dev/null +++ b/apis/appinfo/app_manage/example_app_manage_test.go @@ -0,0 +1,89 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app_manage_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/appinfo/app_manage" +) + +func ExampleIsUserAdmin() { + var ctx *feishu.App + + resp, err := app_manage.IsUserAdmin(ctx) + + fmt.Println(resp, err) +} + +func ExampleAdminScopeGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := app_manage.AdminScopeGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleAppVisibility() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := app_manage.AppVisibility(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleVisibleApps() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := app_manage.VisibleApps(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleAppList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := app_manage.AppList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleUpdateVisibility() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := app_manage.UpdateVisibility(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleAppAdminUserList() { + var ctx *feishu.App + + resp, err := app_manage.AppAdminUserList(ctx) + + fmt.Println(resp, err) +} diff --git a/apis/appinfo/appstore/appstore.go b/apis/appinfo/appstore/appstore.go new file mode 100644 index 0000000..8bb758f --- /dev/null +++ b/apis/appinfo/appstore/appstore.go @@ -0,0 +1,74 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package appstore 应用信息/应用商店 +package appstore + +import ( + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiOrderList = "/open-apis/pay/v1/order/list" + apiOrderGet = "/open-apis/pay/v1/order/get" +) + +/* +查询租户购买的付费方案 + +该接口用于分页查询应用租户下的已付费订单,每次购买对应一个唯一的订单,订单会记录购买的套餐的相关信息,业务方需要自行处理套餐的有效期和付费方案的升级。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uETNwUjLxUDM14SM1ATN + +GET https://open.feishu.cn/open-apis/pay/v1/order/list?status=all&page_size=10&page_token=10&tenant_key=2e5c3a3ae38f175f +*/ +func OrderList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiOrderList+"?"+params.Encode(), header) +} + +/* +查询订单详情 + +该接口用于查询某个订单的具体信息 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uITNwUjLyUDM14iM1ATN + +GET https://open.feishu.cn/open-apis/pay/v1/order/get?order_id=6708978506916697671 +*/ +func OrderGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiOrderGet+"?"+params.Encode(), header) +} diff --git a/apis/appinfo/appstore/appstore_test.go b/apis/appinfo/appstore/appstore_test.go new file mode 100644 index 0000000..b323895 --- /dev/null +++ b/apis/appinfo/appstore/appstore_test.go @@ -0,0 +1,106 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package appstore + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestOrderList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiOrderList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := OrderList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("OrderList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("OrderList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestOrderGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiOrderGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := OrderGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("OrderGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("OrderGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/appinfo/appstore/example_appstore_test.go b/apis/appinfo/appstore/example_appstore_test.go new file mode 100644 index 0000000..0077a84 --- /dev/null +++ b/apis/appinfo/appstore/example_appstore_test.go @@ -0,0 +1,43 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package appstore_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/appinfo/appstore" +) + +func ExampleOrderList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := appstore.OrderList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleOrderGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := appstore.OrderGet(ctx, params) + + fmt.Println(resp, err) +} diff --git a/apis/bot/bot_manage/bot_manage.go b/apis/bot/bot_manage/bot_manage.go new file mode 100644 index 0000000..0d05f40 --- /dev/null +++ b/apis/bot/bot_manage/bot_manage.go @@ -0,0 +1,104 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package bot_manage 机器人/机器人信息和管理 +package bot_manage + +import ( + "bytes" + "net/http" + + "github.com/fastwego/feishu" +) + +const ( + apiInfo = "/open-apis/bot/v3/info/" + apiAdd = "/open-apis/bot/v4/add" + apiRemove = "/open-apis/bot/v4/remove" +) + +/* +获取机器人信息 + +获取机器人的基本信息 + +**权限说明** :需要启用机器人能力 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uAjMxEjLwITMx4CMyETM + +GET https://open.feishu.cn/open-apis/bot/v3/info/ +*/ +func Info(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiInfo, header) +} + +/* +拉机器人进群 + +拉机器人进群 + +**权限说明** :需要启用机器人能力;机器人的owner需要已经在群里 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uYDO04iN4QjL2gDN + +POST https://open.feishu.cn/open-apis/bot/v4/add +*/ +func Add(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiAdd, bytes.NewReader(payload), header) +} + +/* +将机器人移出群 + +将机器人移出群。 + +**权限说明** :需要启用机器人能力 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ucDO04yN4QjL3gDN + +POST https://open.feishu.cn/open-apis/bot/v4/remove +*/ +func Remove(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiRemove, bytes.NewReader(payload), header) +} diff --git a/apis/bot/bot_manage/bot_manage_test.go b/apis/bot/bot_manage/bot_manage_test.go new file mode 100644 index 0000000..ffeb664 --- /dev/null +++ b/apis/bot/bot_manage/bot_manage_test.go @@ -0,0 +1,138 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bot_manage + +import ( + "net/http" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestInfo(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiInfo, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Info(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Info() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Info() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAdd(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAdd, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Add(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Add() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Add() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestRemove(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiRemove, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Remove(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Remove() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Remove() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/bot/bot_manage/example_bot_manage_test.go b/apis/bot/bot_manage/example_bot_manage_test.go new file mode 100644 index 0000000..b8144be --- /dev/null +++ b/apis/bot/bot_manage/example_bot_manage_test.go @@ -0,0 +1,50 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bot_manage_test + +import ( + "fmt" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/bot/bot_manage" +) + +func ExampleInfo() { + var ctx *feishu.App + + resp, err := bot_manage.Info(ctx) + + fmt.Println(resp, err) +} + +func ExampleAdd() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := bot_manage.Add(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleRemove() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := bot_manage.Remove(ctx, payload) + + fmt.Println(resp, err) +} diff --git a/apis/bot/group_manage/example_group_manage_test.go b/apis/bot/group_manage/example_group_manage_test.go new file mode 100644 index 0000000..e492472 --- /dev/null +++ b/apis/bot/group_manage/example_group_manage_test.go @@ -0,0 +1,91 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package group_manage_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/bot/group_manage" +) + +func ExampleChatCreate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := group_manage.ChatCreate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleChatList() { + var ctx *feishu.App + + resp, err := group_manage.ChatList(ctx) + + fmt.Println(resp, err) +} + +func ExampleChatInfo() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := group_manage.ChatInfo(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleChatUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := group_manage.ChatUpdate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleChatterAdd() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := group_manage.ChatterAdd(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleChatterDelete() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := group_manage.ChatterDelete(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleDisband() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := group_manage.Disband(ctx, payload) + + fmt.Println(resp, err) +} diff --git a/apis/bot/group_manage/group_manage.go b/apis/bot/group_manage/group_manage.go new file mode 100644 index 0000000..800c6a7 --- /dev/null +++ b/apis/bot/group_manage/group_manage.go @@ -0,0 +1,209 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package group_manage 机器人/群信息和群管理 +package group_manage + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiChatCreate = "/open-apis/chat/v4/create/" + apiChatList = "/open-apis/chat/v4/list" + apiChatInfo = "/open-apis/chat/v4/info" + apiChatUpdate = "/open-apis/chat/v4/update/" + apiChatterAdd = "/open-apis/chat/v4/chatter/add/" + apiChatterDelete = "/open-apis/chat/v4/chatter/delete/" + apiDisband = "/open-apis/chat/v4/disband" +) + +/* +创建群 + +机器人创建群并拉指定用户进群。 + +**权限说明** :需要启用机器人能力 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ukDO5QjL5gTO04SO4kDN + +POST https://open.feishu.cn/open-apis/chat/v4/create/ +*/ +func ChatCreate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiChatCreate, bytes.NewReader(payload), header) +} + +/* +获取群列表 + +获取机器人所在的群列表。 + +**权限说明** :需要启用机器人能力 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uITO5QjLykTO04iM5kDN + +GET https://open.feishu.cn/open-apis/chat/v4/list +*/ +func ChatList(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiChatList, header) +} + +/* +获取群信息 + +获取群名称、群主 ID、成员列表 ID 等群基本信息。 + +**权限说明** :需要启用机器人能力;机器人必须在群里 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uMTO5QjLzkTO04yM5kDN + +GET https://open.feishu.cn/open-apis/chat/v4/info?chat_id=oc_eb9e82d5657777ebf1bb5b9024f549ef +*/ +func ChatInfo(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiChatInfo+"?"+params.Encode(), header) +} + +/* +更新群信息 + +更新群名称、群配置、转让群主等。 + +**权限说明** :需要启用机器人能力;机器人必须是群主,才能执行修改群配置和转让群主的操作(机器人创建的群,机器人默认是群主。) + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uYTO5QjL2kTO04iN5kDN + +POST https://open.feishu.cn/open-apis/chat/v4/update/ +*/ +func ChatUpdate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiChatUpdate, bytes.NewReader(payload), header) +} + +/* +拉用户进群 + +机器人拉用户进群,机器人必须在群里。 + +**权限说明** :需要启用机器人能力;机器人必须在群里 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ucTO5QjL3kTO04yN5kDN + +POST https://open.feishu.cn/open-apis/chat/v4/chatter/add/ +*/ +func ChatterAdd(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiChatterAdd, bytes.NewReader(payload), header) +} + +/* +移除用户出群 + +机器人移除用户出群。 + +**权限说明** :需要启用机器人能力;机器人必须是群主(机器人创建的群,机器人默认是群主。) + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uADMwUjLwADM14CMwATN + +POST https://open.feishu.cn/open-apis/chat/v4/chatter/delete/ +*/ +func ChatterDelete(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiChatterDelete, bytes.NewReader(payload), header) +} + +/* +解散群 + +机器人解散群。 + +**权限说明** :需要启用机器人能力;机器人必须是群主(机器人创建的群,机器人默认是群主。) + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUDN5QjL1QTO04SN0kDN + +POST https://open.feishu.cn/open-apis/chat/v4/disband +*/ +func Disband(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiDisband, bytes.NewReader(payload), header) +} diff --git a/apis/bot/group_manage/group_manage_test.go b/apis/bot/group_manage/group_manage_test.go new file mode 100644 index 0000000..1e69bb2 --- /dev/null +++ b/apis/bot/group_manage/group_manage_test.go @@ -0,0 +1,284 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package group_manage + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestChatCreate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiChatCreate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ChatCreate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ChatCreate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ChatCreate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestChatList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiChatList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ChatList(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ChatList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ChatList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestChatInfo(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiChatInfo, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ChatInfo(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ChatInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ChatInfo() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestChatUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiChatUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ChatUpdate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ChatUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ChatUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestChatterAdd(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiChatterAdd, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ChatterAdd(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ChatterAdd() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ChatterAdd() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestChatterDelete(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiChatterDelete, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ChatterDelete(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ChatterDelete() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ChatterDelete() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDisband(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDisband, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Disband(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Disband() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Disband() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/capabilities/approve/approve.go b/apis/capabilities/approve/approve.go new file mode 100644 index 0000000..37b1803 --- /dev/null +++ b/apis/capabilities/approve/approve.go @@ -0,0 +1,401 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package approve 审批 +package approve + +import ( + "bytes" + "net/http" + + "github.com/fastwego/feishu" +) + +const ( + apiGet = "/approval/openapi/v2/approval/get" + apiInstanceList = "/approval/openapi/v2/instance/list" + apiInstanceGet = "/approval/openapi/v2/instance/get" + apiInstanceCreate = "/approval/openapi/v2/instance/create" + apiApprove = "/approval/openapi/v2/instance/approve" + apiReject = "/approval/openapi/v2/instance/reject" + apiTransfer = "/approval/openapi/v2/instance/transfer" + apiCancel = "/approval/openapi/v2/instance/cancel" + apiUpload = "/approval/openapi/v2/file/upload" + apiExternalInstanceCreate = "/approval/openapi/v2/external/instance/create" + apiCreate = "/approval/openapi/v2/approval/create" + apiInstanceCc = "/approval/openapi/v2/instance/cc" + apiSubscribe = "/approval/openapi/v2/subscription/subscribe" + apiUnsubscribe = "/approval/openapi/v2/subscription/unsubscribe" +) + +/* +查看审批定义 + + +根据 Approval Code 获取某个审批定义的详情,用于构造创建审批实例的请求。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADNyUjLwQjM14CM0ITN + +POST https://www.feishu.cn/approval/openapi/v2/approval/get +*/ +func Get(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiGet, bytes.NewReader(payload), header) +} + +/* +批量获取审批实例ID + + +根据 approval_code 批量获取审批实例的 instance_code,用于拉取租户下某个审批定义的全部审批实例。 +默认以审批创建时间排序。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQDOyUjL0gjM14CN4ITN + +POST https://www.feishu.cn/approval/openapi/v2/instance/list +*/ +func InstanceList(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiInstanceList, bytes.NewReader(payload), header) +} + +/* +获取单个审批实例详情 + + +通过审批实例 Instance Code 获取审批实例详情。Instance Code 由 [批量获取审批实例](https://open.feishu.cn/document/ukTMukTMukTM/uQDOyUjL0gjM14CN4ITN) 接口获取。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEDNyUjLxQjM14SM0ITN + +POST https://www.feishu.cn/approval/openapi/v2/instance/get +*/ +func InstanceGet(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiInstanceGet, bytes.NewReader(payload), header) +} + +/* +创建审批实例 + + +创建一个审批实例,调用方需对审批定义的表单有详细了解,将按照定义的表单结构,将表单 Value 通过接口传入。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIDNyUjLyQjM14iM0ITN + +POST https://www.feishu.cn/approval/openapi/v2/instance/create +*/ +func InstanceCreate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiInstanceCreate, bytes.NewReader(payload), header) +} + +/* +审批任务同意 + + +对于单个审批任务进行同意操作。同意后审批流程会流转到下一个审批人。 + + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMDNyUjLzQjM14yM0ITN + +POST https://www.feishu.cn/approval/openapi/v2/instance/approve +*/ +func Approve(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiApprove, bytes.NewReader(payload), header) +} + +/* +审批任务拒绝 + + +对于单个审批任务进行拒绝操作。拒绝后审批流程结束。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQDNyUjL0QjM14CN0ITN + +POST https://www.feishu.cn/approval/openapi/v2/instance/reject +*/ +func Reject(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiReject, bytes.NewReader(payload), header) +} + +/* +审批任务转交 + + +对于单个审批任务进行转交操作。转交后审批流程流转给被转交人。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUDNyUjL1QjM14SN0ITN + +POST https://www.feishu.cn/approval/openapi/v2/instance/transfer +*/ +func Transfer(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiTransfer, bytes.NewReader(payload), header) +} + +/* +审批实例撤回 + + +对于单个审批实例进行撤销操作。撤销后审批流程结束。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYDNyUjL2QjM14iN0ITN + +POST https://www.feishu.cn/approval/openapi/v2/instance/cancel +*/ +func Cancel(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiCancel, bytes.NewReader(payload), header) +} + +/* +上传文件 + + +当审批表单中有图片或附件控件时,开发者需在创建审批实例前通过审批上传文件接口将文件上传到审批系统。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUDOyUjL1gjM14SN4ITN + +POST https://www.feishu.cn/approval/openapi/v2/file/upload +*/ +func Upload(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiUpload, bytes.NewReader(payload), header) +} + +/* +三方审批实例同步 + + +用于第三方审批的实例同步。 +使用前需确保已经在审批后台创建第三方审批。 + +> 审批实例:员工发起审批时产生的对象,详情参见 [开发指南](/ssl:ttdoc/ugTM5UjL4ETO14COxkTN/ukDNyUjL5QjM14SO0ITN) +> +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uczM3UjL3MzN14yNzcTN + +POST https://www.feishu.cn/approval/openapi/v2/external/instance/create +*/ +func ExternalInstanceCreate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiExternalInstanceCreate, bytes.NewReader(payload), header) +} + +/* +创建审批定义 + + +用于通过接口创建简单的审批定义,可以灵活指定定义的基础信息、表单和流程等。不推荐企业自建应用使用,如有需要尽量联系管理员在审批管理后台创建定义。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUzNyYjL1cjM24SN3IjN + +POST https://www.feishu.cn/approval/openapi/v2/approval/create +*/ +func Create(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiCreate, bytes.NewReader(payload), header) +} + +/* +审批实例抄送 + + +通过接口可以将当前审批实例抄送给其他人。 + +**权限说明** :需要获取 访问审批应用 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOzYjLwgzM24CM4MjN + +POST https://www.feishu.cn/approval/openapi/v2/instance/cc +*/ +func InstanceCc(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiInstanceCc, bytes.NewReader(payload), header) +} + +/* +订阅审批事件 + + +订阅 approval_code 后,可以收到该审批定义对应实例的事件通知。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucDOyUjL3gjM14yN4ITN + +POST https://www.feishu.cn/approval/openapi/v2/subscription/subscribe +*/ +func Subscribe(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiSubscribe, bytes.NewReader(payload), header) +} + +/* +取消订阅审批事件 + + +取消订阅 approval_code 后,无法再收到该审批定义对应实例的事件通知。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDOyUjL4gjM14CO4ITN + +POST https://www.feishu.cn/approval/openapi/v2/subscription/unsubscribe +*/ +func Unsubscribe(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiUnsubscribe, bytes.NewReader(payload), header) +} diff --git a/apis/capabilities/approve/approve_test.go b/apis/capabilities/approve/approve_test.go new file mode 100644 index 0000000..4975edf --- /dev/null +++ b/apis/capabilities/approve/approve_test.go @@ -0,0 +1,535 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package approve + +import ( + "net/http" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Get(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Get() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestInstanceList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiInstanceList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := InstanceList(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("InstanceList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestInstanceGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiInstanceGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := InstanceGet(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("InstanceGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestInstanceCreate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiInstanceCreate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := InstanceCreate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceCreate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("InstanceCreate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestApprove(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiApprove, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Approve(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Approve() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Approve() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestReject(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiReject, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Reject(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Reject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Reject() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestTransfer(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiTransfer, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Transfer(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Transfer() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Transfer() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCancel(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCancel, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Cancel(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Cancel() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Cancel() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestUpload(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiUpload, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Upload(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Upload() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Upload() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestExternalInstanceCreate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiExternalInstanceCreate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ExternalInstanceCreate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ExternalInstanceCreate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ExternalInstanceCreate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCreate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCreate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Create(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Create() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestInstanceCc(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiInstanceCc, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := InstanceCc(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceCc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("InstanceCc() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSubscribe(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSubscribe, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Subscribe(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Subscribe() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Subscribe() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestUnsubscribe(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiUnsubscribe, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Unsubscribe(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Unsubscribe() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Unsubscribe() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/capabilities/approve/example_approve_test.go b/apis/capabilities/approve/example_approve_test.go new file mode 100644 index 0000000..ed5ee9c --- /dev/null +++ b/apis/capabilities/approve/example_approve_test.go @@ -0,0 +1,162 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package approve_test + +import ( + "fmt" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/capabilities/approve" +) + +func ExampleGet() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Get(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleInstanceList() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.InstanceList(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleInstanceGet() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.InstanceGet(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleInstanceCreate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.InstanceCreate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleApprove() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Approve(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleReject() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Reject(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleTransfer() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Transfer(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleCancel() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Cancel(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleUpload() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Upload(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleExternalInstanceCreate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.ExternalInstanceCreate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleCreate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Create(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleInstanceCc() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.InstanceCc(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleSubscribe() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Subscribe(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleUnsubscribe() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := approve.Unsubscribe(ctx, payload) + + fmt.Println(resp, err) +} diff --git a/apis/capabilities/calendar/calendar.go b/apis/capabilities/calendar/calendar.go new file mode 100644 index 0000000..cea5645 --- /dev/null +++ b/apis/capabilities/calendar/calendar.go @@ -0,0 +1,369 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package calendar 日历 +package calendar + +import ( + "bytes" + "net/http" + "net/url" + "strings" + + "github.com/fastwego/feishu" +) + +const ( + apiGetCalendarById = "/open-apis/calendar/v3/calendar_list/:calendarId" + apiCalendarList = "/open-apis/calendar/v3/calendar_list" + apiCreateCalendars = "/open-apis/calendar/v3/calendars" + apiDeleteCalendarById = "/open-apis/calendar/v3/calendars/:calendarId" + apiGetEventById = "/open-apis/calendar/v3/calendars/:calendarId/events/:eventId" + apiCreateEvent = "/open-apis/calendar/v3/calendars/:calendarId/events" + apiAttendees = "/open-apis/calendar/v3/calendars/:calendarId/events/:eventId/attendees" + apiAcl = "/open-apis/calendar/v3/calendars/:calendarId/acl" + apiDeleteAclByRuleId = "/open-apis/calendar/v3/calendars/:calendarId/acl/:ruleId" + apiFreeBusyQuery = "/open-apis/calendar/v3/freebusy/query" + apiSharedCalendarQuery = "/open-apis/calendar/v3/shared_calendar_list/shared_calendar/query" + apiSharedCalendarEvents = "/open-apis/calendar/v3/shared/calendars/:calendarId/events" +) + +/* +获取日历 + + +该接口用于根据日历 ID 获取日历信息。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMDN04yM0QjLzQDN + +GET https://open.feishu.cn/open-apis/calendar/v3/calendar_list/:calendarId +*/ +func GetCalendarById(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + api := apiGetCalendarById + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +获取日历列表 + + +该接口用于获取应用在企业内的日历列表。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMTM14yMxUjLzETN + +GET https://open.feishu.cn/open-apis/calendar/v3/calendar_list +*/ +func CalendarList(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiCalendarList, header) +} + +/* +创建日历 + + +该接口用于为应用在企业内创建一个日历。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTM14CNxUjL0ETN + +POST https://open.feishu.cn/open-apis/calendar/v3/calendars +*/ +func CreateCalendars(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiCreateCalendars, bytes.NewReader(payload), header) +} + +/* +删除日历 + + +该接口用于删除应用在企业内的指定日历。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUTM14SNxUjL1ETN + +DELETE https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId +*/ +func DeleteCalendarById(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + api := apiDeleteCalendarById + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPDelete(api, header) +} + +/* +获取日程 + + +该接口用于获取指定日历下的指定日程。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucTM14yNxUjL3ETN + +GET https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/events/:eventId +*/ +func GetEventById(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + api := apiGetEventById + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +创建日程 + + +该接口用于在日历中创建一个日程。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugTM14COxUjL4ETN + +POST https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/events +*/ +func CreateEvent(ctx *feishu.App, payload []byte, params url.Values) (resp []byte, err error) { + + api := apiCreateEvent + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +邀请/移除日程参与者 + + +邀请一个或多个用户加入日程; +从日程移除一个或多个用户。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIjM14iMyUjLyITN + +POST https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/events/:eventId/attendees +*/ +func Attendees(ctx *feishu.App, payload []byte, params url.Values) (resp []byte, err error) { + + api := apiAttendees + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +获取访问控制列表 + + +该接口用于查看指定日历的成员列表。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjM14yMyUjLzITN + +GET https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/acl +*/ +func Acl(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + api := apiAcl + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +删除访问控制 + + +该接口用于从日历中移除一个用户。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUjM14SNyUjL1ITN + +DELETE https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/acl/:ruleId +*/ +func DeleteAclByRuleId(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + api := apiDeleteAclByRuleId + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPDelete(api, header) +} + +/* +查询日历的忙闲状态 + + +该接口用于查询指定日历或用户主日历的忙闲状态。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYjM14iNyUjL2ITN + +POST https://open.feishu.cn/open-apis/calendar/v3/freebusy/query +*/ +func FreeBusyQuery(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiFreeBusyQuery, bytes.NewReader(payload), header) +} + +/* +查询公共日历 + + +该接口用于通过关键字查询公共日历信息。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukDMwYjL5ADM24SOwAjN + +GET https://open.feishu.cn/open-apis/calendar/v3/shared_calendar_list/shared_calendar/query?query=ByteDance +*/ +func SharedCalendarQuery(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiSharedCalendarQuery+"?"+params.Encode(), header) +} + +/* +获取公共日历日程列表 + + +该接口用于获取公共日历下的日程列表。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIzNwYjLycDM24iM3AjN + +GET https://open.feishu.cn/open-apis/calendar/v3/shared/calendars/:calendarId/events +*/ +func SharedCalendarEvents(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + api := apiSharedCalendarEvents + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} diff --git a/apis/capabilities/calendar/calendar_test.go b/apis/capabilities/calendar/calendar_test.go new file mode 100644 index 0000000..8233168 --- /dev/null +++ b/apis/capabilities/calendar/calendar_test.go @@ -0,0 +1,474 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package calendar + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestGetCalendarById(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiGetCalendarById, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := GetCalendarById(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("GetCalendarById() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("GetCalendarById() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCalendarList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCalendarList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CalendarList(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CalendarList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CalendarList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCreateCalendars(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCreateCalendars, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CreateCalendars(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CreateCalendars() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CreateCalendars() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDeleteCalendarById(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDeleteCalendarById, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DeleteCalendarById(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DeleteCalendarById() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DeleteCalendarById() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestGetEventById(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiGetEventById, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := GetEventById(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("GetEventById() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("GetEventById() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCreateEvent(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCreateEvent, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CreateEvent(tt.args.ctx, tt.args.payload, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CreateEvent() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CreateEvent() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAttendees(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAttendees, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Attendees(tt.args.ctx, tt.args.payload, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Attendees() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Attendees() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAcl(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAcl, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Acl(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Acl() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Acl() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDeleteAclByRuleId(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDeleteAclByRuleId, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DeleteAclByRuleId(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DeleteAclByRuleId() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DeleteAclByRuleId() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestFreeBusyQuery(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiFreeBusyQuery, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := FreeBusyQuery(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("FreeBusyQuery() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("FreeBusyQuery() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSharedCalendarQuery(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSharedCalendarQuery, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SharedCalendarQuery(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SharedCalendarQuery() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SharedCalendarQuery() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSharedCalendarEvents(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSharedCalendarEvents, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SharedCalendarEvents(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SharedCalendarEvents() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SharedCalendarEvents() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/capabilities/calendar/example_calendar_test.go b/apis/capabilities/calendar/example_calendar_test.go new file mode 100644 index 0000000..1e55e3e --- /dev/null +++ b/apis/capabilities/calendar/example_calendar_test.go @@ -0,0 +1,143 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package calendar_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/capabilities/calendar" +) + +func ExampleGetCalendarById() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := calendar.GetCalendarById(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleCalendarList() { + var ctx *feishu.App + + resp, err := calendar.CalendarList(ctx) + + fmt.Println(resp, err) +} + +func ExampleCreateCalendars() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := calendar.CreateCalendars(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleDeleteCalendarById() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := calendar.DeleteCalendarById(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleGetEventById() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := calendar.GetEventById(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleCreateEvent() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + + resp, err := calendar.CreateEvent(ctx, payload, params) + + fmt.Println(resp, err) +} + +func ExampleAttendees() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + + resp, err := calendar.Attendees(ctx, payload, params) + + fmt.Println(resp, err) +} + +func ExampleAcl() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := calendar.Acl(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleDeleteAclByRuleId() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := calendar.DeleteAclByRuleId(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleFreeBusyQuery() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := calendar.FreeBusyQuery(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleSharedCalendarQuery() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := calendar.SharedCalendarQuery(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleSharedCalendarEvents() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := calendar.SharedCalendarEvents(ctx, params) + + fmt.Println(resp, err) +} diff --git a/apis/capabilities/document/document.go b/apis/capabilities/document/document.go new file mode 100644 index 0000000..57eea33 --- /dev/null +++ b/apis/capabilities/document/document.go @@ -0,0 +1,749 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package document 云文档 +package document + +import ( + "bytes" + "net/http" + "net/url" + "strings" + + "github.com/fastwego/feishu" +) + +const ( + apiCreateFolder = "/open-apis/drive/explorer/v2/folder/:folderToken" + apiRootFolderMeta = "/open-apis/drive/explorer/v2/root_folder/meta" + apiFolderChildren = "/open-apis/drive/explorer/v2/folder/:folderToken/children" + apiCreateFile = "/open-apis/drive/explorer/v2/file/:folderToken" + apiCopyFile = "/open-apis/drive/explorer/v2/file/copy/files/:fileToken" + apiDeleteFile = "/open-apis/drive/explorer/v2/file/spreadsheets/:spreadsheetToken" + apiPermissionMemberCreate = "/open-apis/drive/permission/member/create" + apiPermissionMemberTransfer = "/open-apis/drive/permission/member/transfer" + apiPermissionPublicUpdate = "/open-apis/drive/permission/public/update" + apiPermissionMemberList = "/open-apis/drive/permission/member/list" + apiPermissionMemberDelete = "/open-apis/drive/permission/member/delete" + apiPermissionMemberUpdate = "/open-apis/drive/permission/member/update" + apiPermissionMemberPermitted = "/open-apis/drive/permission/member/permitted" + apiRawContent = "/open-apis/doc/v2/:docToken/raw_content" + apiSheetMeta = "/open-apis/doc/v2/:docToken/sheet_meta" + apiDocMeta = "/open-apis/doc/v2/meta/:docToken" + apiSpreadSheetsMetainfo = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/metainfo" + apiSheetsBatchUpdate = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/sheets_batch_update" + apiSpreadSheetsValuesPrepend = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_prepend" + apiSpreadSheetsValuesAppend = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_append" + apiSpreadSheetsInsertDimensionRange = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/insert_dimension_range" + apiSpreadSheetsDimensionRange = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/dimension_range" + apiSpreadSheetsProtectedDimension = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/protected_dimension" + apiSpreadSheetsMergeCells = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/merge_cells" + apiSpreadSheetsUnmergeCells = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/unmerge_cells" + apiSpreadSheetsRange = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values/:range" + apiSpreadSheetsValuesBatchGet = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_get" + apiSpreadSheetsValuesBatchUpdate = "/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_update" + apiDocsMeta = "/open-apis/suite/docs-api/meta" + apiSearchObject = "/open-apis/suite/docs-api/search/object" + apiCommentAddWhole = "/open-apis/comment/add_whole" +) + +/* +新建文件夹 + + + +该接口用于根据 folderToken 在该 folder 下创建文件夹 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTNzUjL5UzM14SO1MTN + +POST https://open.feishu.cn/open-apis/drive/explorer/v2/folder/:folderToken +*/ +func CreateFolder(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiCreateFolder + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +获取文件夹元信息 + + +# 获取root folder(我的空间) meta该接口用于获取 我的空间 的元信息 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uAjNzUjLwYzM14CM2MTN + +GET https://open.feishu.cn/open-apis/drive/explorer/v2/root_folder/meta +*/ +func RootFolderMeta(ctx *feishu.App, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiRootFolderMeta, header) +} + +/* +获取文件夹下的文档清单 + + + +该接口用于根据 folderToken 获取该文件夹的文档清单,如 doc、sheet、folder + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEjNzUjLxYzM14SM2MTN + +GET https://open.feishu.cn/open-apis/drive/explorer/v2/folder/:folderToken/children +*/ +func FolderChildren(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiFolderChildren + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +新建文档 + + +该接口用于根据 folderToken 创建 Doc或 Sheet 。 + +若没有特定的文件夹用于承载创建的文档,可以先调用「获取文件夹元信息」文档中的「获取 root folder (我的空间) meta」接口,获得我的空间的 token,然后再使用此接口。创建的文档将会在「我的空间」的「归我所有」列表里。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTNzUjL0UzM14CN1MTN + +POST https://open.feishu.cn/open-apis/drive/explorer/v2/file/:folderToken +*/ +func CreateFile(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiCreateFile + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +复制文档 + + + +该接口用于根据文件 token 复制 Doc 或 Sheet 到目标文件夹中。 + +若没有特定的文件夹用于承载创建的文档,可以先调用「获取文件夹元信息」文档中的「获取 root folder (我的空间) meta」接口,获得我的空间的 token,然后再使用此接口。复制的文档将会在「我的空间」的「归我所有」列表里。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTNzUjL2UzM14iN1MTN + +POST https://open.feishu.cn/open-apis/drive/explorer/v2/file/copy/files/:fileToken +*/ +func CopyFile(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiCopyFile + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +删除文档 + +Doc + +该接口用于根据 spreadsheetToken 删除对应的 sheet 文档。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATM2UjLwEjN14CMxYTN + +DELETE https://open.feishu.cn/open-apis/drive/explorer/v2/file/spreadsheets/:spreadsheetToken +*/ +func DeleteFile(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiDeleteFile + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPDelete(api, header) +} + +/* +增加权限 + + + +该接口用于根据 filetoken 给用户增加文档的权限 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMzNzUjLzczM14yM3MTN + +POST https://open.feishu.cn/open-apis/drive/permission/member/create +*/ +func PermissionMemberCreate(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiPermissionMemberCreate, bytes.NewReader(payload), header) +} + +/* +转移拥有者 + + + +该接口用于根据文档信息和用户信息转移文档的所有者。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQzNzUjL0czM14CN3MTN + +POST https://open.feishu.cn/open-apis/drive/permission/member/transfer +*/ +func PermissionMemberTransfer(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiPermissionMemberTransfer, bytes.NewReader(payload), header) +} + +/* +更新文档公共设置 + + + +该接口用于根据 filetoken 更新文档的公共设置 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTM3UjL5EzN14SOxcTN + +POST https://open.feishu.cn/open-apis/drive/permission/public/update +*/ +func PermissionPublicUpdate(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiPermissionPublicUpdate, bytes.NewReader(payload), header) +} + +/* +获取协作者列表 + + + +该接口用于根据 filetoken 查询协作者,目前包括人('user')和群('chat') + +**你能获取到协作者列表的前提是你对该文档有权限** + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATN3UjLwUzN14CM1cTN + +POST https://open.feishu.cn/open-apis/drive/permission/member/list +*/ +func PermissionMemberList(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiPermissionMemberList, bytes.NewReader(payload), header) +} + +/* +移除协作者权限 + + + +该接口用于根据 filetoken 移除文档协作者的权限 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTN3UjL2UzN14iN1cTN + +POST https://open.feishu.cn/open-apis/drive/permission/member/delete +*/ +func PermissionMemberDelete(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiPermissionMemberDelete, bytes.NewReader(payload), header) +} + +/* +更新协作者权限 + + + +该接口用于根据 filetoken 更新文档协作者的权限 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucTN3UjL3UzN14yN1cTN + +POST https://open.feishu.cn/open-apis/drive/permission/member/update +*/ +func PermissionMemberUpdate(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiPermissionMemberUpdate, bytes.NewReader(payload), header) +} + +/* +判断协作者是否有某权限 + + + +该接口用于根据 filetoken 判断当前登录用户是否具有某权限 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzN3UjL2czN14iN3cTN + +POST https://open.feishu.cn/open-apis/drive/permission/member/permitted +*/ +func PermissionMemberPermitted(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiPermissionMemberPermitted, bytes.NewReader(payload), header) +} + +/* +获取文档文本内容 + + +该接口用于获取文档的纯文本内容,不包含富文本格式信息,主要用于搜索,如导入 es 等。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukzNzUjL5czM14SO3MTN + +GET https://open.feishu.cn/open-apis/doc/v2/:docToken/raw_content +*/ +func RawContent(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiRawContent + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +获取sheet@doc的元数据 + +sheet@doc 的元数据 + +该接口用于根据 docToken 获取 sheet@doc 的元数据。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOzUjLwgzM14CM4MTN + +GET https://open.feishu.cn/open-apis/doc/v2/:docToken/sheet_meta +*/ +func SheetMeta(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSheetMeta + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +获取文档元信息 + + +该接口用于根据 docToken 获取元数据。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uczN3UjL3czN14yN3cTN + +GET https://open.feishu.cn/open-apis/doc/v2/meta/:docToken +*/ +func DocMeta(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiDocMeta + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +获取表格元数据 + + +该接口用于根据 spreadsheetToken 获取表格元数据。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uETMzUjLxEzM14SMxMTN + +GET https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/metainfo +*/ +func SpreadSheetsMetainfo(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsMetainfo + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +操作子表 + + + +该接口用于根据 spreadsheetToken 操作表格,如增加sheet,复制sheet、删除sheet。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTMzUjL2EzM14iNxMTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/sheets_batch_update +*/ +func SheetsBatchUpdate(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSheetsBatchUpdate + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +插入数据 + + + +该接口用于根据 spreadsheetToken 和 range 向范围之前增加相应数据的行和相应的数据,相当于数组的插入操作;单次写入不超过5000行,100列,每个格子大小为0.5M。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIjMzUjLyIzM14iMyMTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_prepend +*/ +func SpreadSheetsValuesPrepend(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsValuesPrepend + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +追加数据 + + + +该接口用于根据 spreadsheetToken 和 range 遇到空行则进行覆盖追加或新增行追加数据。 空行:默认该行第一个格子是空,则认为是空行;单次写入不超过5000行,100列,每个格子大小为0.5M。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjMzUjLzIzM14yMyMTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_append +*/ +func SpreadSheetsValuesAppend(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsValuesAppend + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +插入行列 + + + +该接口用于根据 spreadsheetToken 和维度信息 插入空行/列 。如 startIndex=3, endIndex=7,则从第 4 行开始开始插入行列,一直到第 7 行,共插入 4 行;单次操作不超过5000行或列。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQjMzUjL0IzM14CNyMTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/insert_dimension_range +*/ +func SpreadSheetsInsertDimensionRange(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsInsertDimensionRange + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +增加行列 + + + +该接口用于根据 spreadsheetToken 和长度,在末尾增加空行/列;单次操作不超过5000行或列。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUjMzUjL1IzM14SNyMTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/dimension_range +*/ +func SpreadSheetsDimensionRange(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsDimensionRange + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +增加锁定单元格 + + + +该接口用于根据 spreadsheetToken 和维度信息增加多个范围的锁定单元格;单次操作不超过5000行或列。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDNzUjL4QzM14CO0MTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/protected_dimension +*/ +func SpreadSheetsProtectedDimension(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsProtectedDimension + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +合并单元格 + + + +该接口用于根据 spreadsheetToken 和维度信息合并单元格;单次操作不超过5000行,100列。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukDNzUjL5QzM14SO0MTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/merge_cells +*/ +func SpreadSheetsMergeCells(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsMergeCells + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +拆分单元格 + + + +该接口用于根据 spreadsheetToken 和维度信息拆分单元格;单次操作不超过5000行,100列。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATNzUjLwUzM14CM1MTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/unmerge_cells +*/ +func SpreadSheetsUnmergeCells(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsUnmergeCells + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +读取单个范围 + + + +该接口用于根据 spreadsheetToken 和 range 读取表格单个范围的值,返回数据限制为10M。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugTMzUjL4EzM14COxMTN + +GET https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values/:range +*/ +func SpreadSheetsRange(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsRange + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +读取多个范围 + + + +该接口用于根据 spreadsheetToken 和 ranges 读取表格多个范围的值,返回数据限制为10M。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTMzUjL5EzM14SOxMTN + +GET https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_get +*/ +func SpreadSheetsValuesBatchGet(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsValuesBatchGet + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(api, header) +} + +/* +向多个范围写入数据 + + + +该接口用于根据 spreadsheetToken 和 range 向多个范围写入数据,若范围内有数据,将被更新覆盖;单次写入不超过5000行,100列,每个格子大小为0.5M。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEjMzUjLxIzM14SMyMTN + +POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_update +*/ +func SpreadSheetsValuesBatchUpdate(ctx *feishu.App, payload []byte, params url.Values, header http.Header) (resp []byte, err error) { + + api := apiSpreadSheetsValuesBatchUpdate + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + } + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(api, bytes.NewReader(payload), header) +} + +/* +获取元数据 + + + +该接口用于根据 token 获取各类文件的元数据 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjN3UjLzYzN14yM2cTN + +POST https://open.feishu.cn/open-apis/suite/docs-api/meta +*/ +func DocsMeta(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiDocsMeta, bytes.NewReader(payload), header) +} + +/* +文档搜索 + + + +该接口用于根据搜索条件进行文档搜索 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDM4UjL4ADO14COwgTN + +POST https://open.feishu.cn/open-apis/suite/docs-api/search/object +*/ +func SearchObject(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiSearchObject, bytes.NewReader(payload), header) +} + +/* +添加全文评论 + + + +该接口用于根据 filetoken 给文档添加全文评论 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucDN4UjL3QDO14yN0gTN + +POST https://open.feishu.cn/open-apis/comment/add_whole +*/ +func CommentAddWhole(ctx *feishu.App, payload []byte, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiCommentAddWhole, bytes.NewReader(payload), header) +} diff --git a/apis/capabilities/document/document_test.go b/apis/capabilities/document/document_test.go new file mode 100644 index 0000000..6d7e228 --- /dev/null +++ b/apis/capabilities/document/document_test.go @@ -0,0 +1,1211 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package document + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestCreateFolder(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCreateFolder, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CreateFolder(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CreateFolder() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CreateFolder() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestRootFolderMeta(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiRootFolderMeta, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := RootFolderMeta(tt.args.ctx, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("RootFolderMeta() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("RootFolderMeta() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestFolderChildren(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiFolderChildren, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := FolderChildren(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("FolderChildren() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("FolderChildren() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCreateFile(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCreateFile, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CreateFile(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CreateFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CreateFile() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCopyFile(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCopyFile, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CopyFile(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CopyFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CopyFile() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDeleteFile(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDeleteFile, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DeleteFile(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DeleteFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DeleteFile() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestPermissionMemberCreate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiPermissionMemberCreate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := PermissionMemberCreate(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("PermissionMemberCreate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("PermissionMemberCreate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestPermissionMemberTransfer(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiPermissionMemberTransfer, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := PermissionMemberTransfer(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("PermissionMemberTransfer() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("PermissionMemberTransfer() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestPermissionPublicUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiPermissionPublicUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := PermissionPublicUpdate(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("PermissionPublicUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("PermissionPublicUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestPermissionMemberList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiPermissionMemberList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := PermissionMemberList(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("PermissionMemberList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("PermissionMemberList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestPermissionMemberDelete(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiPermissionMemberDelete, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := PermissionMemberDelete(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("PermissionMemberDelete() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("PermissionMemberDelete() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestPermissionMemberUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiPermissionMemberUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := PermissionMemberUpdate(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("PermissionMemberUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("PermissionMemberUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestPermissionMemberPermitted(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiPermissionMemberPermitted, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := PermissionMemberPermitted(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("PermissionMemberPermitted() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("PermissionMemberPermitted() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestRawContent(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiRawContent, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := RawContent(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("RawContent() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("RawContent() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSheetMeta(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSheetMeta, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SheetMeta(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SheetMeta() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SheetMeta() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDocMeta(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDocMeta, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DocMeta(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DocMeta() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DocMeta() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsMetainfo(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsMetainfo, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsMetainfo(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsMetainfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsMetainfo() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSheetsBatchUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSheetsBatchUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SheetsBatchUpdate(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SheetsBatchUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SheetsBatchUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsValuesPrepend(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsValuesPrepend, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsValuesPrepend(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsValuesPrepend() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsValuesPrepend() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsValuesAppend(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsValuesAppend, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsValuesAppend(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsValuesAppend() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsValuesAppend() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsInsertDimensionRange(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsInsertDimensionRange, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsInsertDimensionRange(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsInsertDimensionRange() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsInsertDimensionRange() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsDimensionRange(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsDimensionRange, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsDimensionRange(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsDimensionRange() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsDimensionRange() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsProtectedDimension(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsProtectedDimension, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsProtectedDimension(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsProtectedDimension() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsProtectedDimension() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsMergeCells(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsMergeCells, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsMergeCells(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsMergeCells() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsMergeCells() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsUnmergeCells(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsUnmergeCells, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsUnmergeCells(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsUnmergeCells() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsUnmergeCells() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsRange(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsRange, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsRange(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsRange() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsRange() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsValuesBatchGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsValuesBatchGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsValuesBatchGet(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsValuesBatchGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsValuesBatchGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSpreadSheetsValuesBatchUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSpreadSheetsValuesBatchUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SpreadSheetsValuesBatchUpdate(tt.args.ctx, tt.args.payload, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SpreadSheetsValuesBatchUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SpreadSheetsValuesBatchUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDocsMeta(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDocsMeta, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DocsMeta(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DocsMeta() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DocsMeta() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSearchObject(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSearchObject, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := SearchObject(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("SearchObject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("SearchObject() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCommentAddWhole(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCommentAddWhole, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CommentAddWhole(tt.args.ctx, tt.args.payload, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CommentAddWhole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CommentAddWhole() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/capabilities/document/example_document_test.go b/apis/capabilities/document/example_document_test.go new file mode 100644 index 0000000..1f3f27e --- /dev/null +++ b/apis/capabilities/document/example_document_test.go @@ -0,0 +1,345 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package document_test + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/capabilities/document" +) + +func ExampleCreateFolder() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.CreateFolder(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleRootFolderMeta() { + var ctx *feishu.App + + header := http.Header{} + resp, err := document.RootFolderMeta(ctx, header) + + fmt.Println(resp, err) +} + +func ExampleFolderChildren() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.FolderChildren(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExampleCreateFile() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.CreateFile(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleCopyFile() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.CopyFile(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleDeleteFile() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.DeleteFile(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExamplePermissionMemberCreate() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.PermissionMemberCreate(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExamplePermissionMemberTransfer() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.PermissionMemberTransfer(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExamplePermissionPublicUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.PermissionPublicUpdate(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExamplePermissionMemberList() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.PermissionMemberList(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExamplePermissionMemberDelete() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.PermissionMemberDelete(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExamplePermissionMemberUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.PermissionMemberUpdate(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExamplePermissionMemberPermitted() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.PermissionMemberPermitted(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExampleRawContent() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.RawContent(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExampleSheetMeta() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.SheetMeta(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExampleDocMeta() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.DocMeta(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsMetainfo() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsMetainfo(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExampleSheetsBatchUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SheetsBatchUpdate(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsValuesPrepend() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsValuesPrepend(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsValuesAppend() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsValuesAppend(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsInsertDimensionRange() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsInsertDimensionRange(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsDimensionRange() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsDimensionRange(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsProtectedDimension() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsProtectedDimension(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsMergeCells() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsMergeCells(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsUnmergeCells() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsUnmergeCells(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsRange() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsRange(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsValuesBatchGet() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsValuesBatchGet(ctx, params, header) + + fmt.Println(resp, err) +} + +func ExampleSpreadSheetsValuesBatchUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + params := url.Values{} + header := http.Header{} + resp, err := document.SpreadSheetsValuesBatchUpdate(ctx, payload, params, header) + + fmt.Println(resp, err) +} + +func ExampleDocsMeta() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.DocsMeta(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExampleSearchObject() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.SearchObject(ctx, payload, header) + + fmt.Println(resp, err) +} + +func ExampleCommentAddWhole() { + var ctx *feishu.App + + payload := []byte("{}") + header := http.Header{} + resp, err := document.CommentAddWhole(ctx, payload, header) + + fmt.Println(resp, err) +} diff --git a/apis/capabilities/meeting/example_meeting_test.go b/apis/capabilities/meeting/example_meeting_test.go new file mode 100644 index 0000000..8348bd5 --- /dev/null +++ b/apis/capabilities/meeting/example_meeting_test.go @@ -0,0 +1,181 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package meeting_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/capabilities/meeting" +) + +func ExampleBuildingList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.BuildingList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleBuildingBatchGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.BuildingBatchGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleMeetingRoomList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.MeetingRoomList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleMeetingRoomBatchGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.MeetingRoomBatchGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleMeetingRoomFreeBusyBatchGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.MeetingRoomFreeBusyBatchGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleInstanceReply() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := meeting.InstanceReply(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleBuildingCreate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := meeting.BuildingCreate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleBuildingUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := meeting.BuildingUpdate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleBuildingDelete() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := meeting.BuildingDelete(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleBuildingBatchGetById() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.BuildingBatchGetById(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleMeetingRoomCreate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := meeting.MeetingRoomCreate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleMeetingRoomUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := meeting.MeetingRoomUpdate(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleMeetingRoomDelete() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := meeting.MeetingRoomDelete(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleMeetingRoomBatchGetById() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.MeetingRoomBatchGetById(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleCountryList() { + var ctx *feishu.App + + resp, err := meeting.CountryList(ctx) + + fmt.Println(resp, err) +} + +func ExampleDistrictList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := meeting.DistrictList(ctx, params) + + fmt.Println(resp, err) +} diff --git a/apis/capabilities/meeting/meeting.go b/apis/capabilities/meeting/meeting.go new file mode 100644 index 0000000..aa383a8 --- /dev/null +++ b/apis/capabilities/meeting/meeting.go @@ -0,0 +1,459 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package meeting 会议室 +package meeting + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiBuildingList = "/open-apis/meeting_room/building/list" + apiBuildingBatchGet = "/open-apis/meeting_room/building/batch_get" + apiMeetingRoomList = "/open-apis/meeting_room/room/list" + apiMeetingRoomBatchGet = "/open-apis/meeting_room/room/batch_get" + apiMeetingRoomFreeBusyBatchGet = "/open-apis/meeting_room/freebusy/batch_get" + apiInstanceReply = "/open-apis/meeting_room/instance/reply" + apiBuildingCreate = "/open-apis/meeting_room/building/create" + apiBuildingUpdate = "/open-apis/meeting_room/building/update" + apiBuildingDelete = "/open-apis/meeting_room/building/delete" + apiBuildingBatchGetById = "/open-apis/meeting_room/building/batch_get_id" + apiMeetingRoomCreate = "/open-apis/meeting_room/room/create" + apiMeetingRoomUpdate = "/open-apis/meeting_room/room/update" + apiMeetingRoomDelete = "/open-apis/meeting_room/room/delete" + apiMeetingRoomBatchGetById = "/open-apis/meeting_room/room/batch_get_id" + apiCountryList = "/open-apis/meeting_room/country/list" + apiDistrictList = "/open-apis/meeting_room/district/list" +) + +/* +获取建筑物列表 + + +该接口用于获取本企业下的建筑物(办公大楼)。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugzNyUjL4cjM14CO3ITN + +GET https://open.feishu.cn/open-apis/meeting_room/building/list?page_size=1&page_token=0&order_by=name-asc&fields=* +*/ +func BuildingList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiBuildingList+"?"+params.Encode(), header) +} + +/* +查询建筑物详情 + + +该接口用于获取指定建筑物的详细信息。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukzNyUjL5cjM14SO3ITN + +GET https://open.feishu.cn/open-apis/meeting_room/building/batch_get?building_ids=omb_8ec170b937536a5d87c23b418b83f9bb&building_ids=omb_38570e4f0fd9ecf15030d3cc8b388f3a&fields=* +*/ +func BuildingBatchGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiBuildingBatchGet+"?"+params.Encode(), header) +} + +/* +获取会议室列表 + + +该接口用于获取指定建筑下的会议室。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOyUjLwgjM14CM4ITN + +GET https://open.feishu.cn/open-apis/meeting_room/room/list?building_id=omb_8ec170b937536a5d87c23b418b83f9bb&page_size=1&page_token=0&order_by=name-asc&fields=* +*/ +func MeetingRoomList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiMeetingRoomList+"?"+params.Encode(), header) +} + +/* +查询会议室详情 + + +该接口用于获取指定会议室的详细信息。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEDOyUjLxgjM14SM4ITN + +GET https://open.feishu.cn/open-apis/meeting_room/room/batch_get?room_ids=omm_eada1d61a550955240c28757e7dec3af&room_ids=omm_83d09ad4f6896e02029a6a075f71c9d1&fields=* +*/ +func MeetingRoomBatchGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiMeetingRoomBatchGet+"?"+params.Encode(), header) +} + +/* +会议室忙闲查询 + + +该接口用于获取指定会议室的忙闲日程实例列表。非重复日程只有唯一实例;重复日程可能存在多个实例,依据重复规则和时间范围扩展。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIDOyUjLygjM14iM4ITN + +GET https://open.feishu.cn/open-apis/meeting_room/freebusy/batch_get?room_ids=omm_83d09ad4f6896e02029a6a075f71c9d1&room_ids=omm_eada1d61a550955240c28757e7dec3af&time_min=2019-09-04T08:45:00%2B08:00&time_max=2019-09-04T09:45:00%2B08:00 +*/ +func MeetingRoomFreeBusyBatchGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiMeetingRoomFreeBusyBatchGet+"?"+params.Encode(), header) +} + +/* +回复会议室日程实例 + + +该接口用于回复会议室日程实例,包括未签到释放和提前结束释放。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzN4UjL2cDO14iN3gTN + +POST https://open.feishu.cn/open-apis/meeting_room/instance/reply +*/ +func InstanceReply(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiInstanceReply, bytes.NewReader(payload), header) +} + +/* +创建建筑物 + + +该接口对应管理后台的添加建筑,添加楼层的功能,可用于创建建筑物和建筑物的楼层信息。 + +**权限说明** :需要获取 管理会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATNwYjLwUDM24CM1AjN + +POST https://open.feishu.cn/open-apis/meeting_room/building/create +*/ +func BuildingCreate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiBuildingCreate, bytes.NewReader(payload), header) +} + +/* +更新建筑物 + + +该接口用于编辑建筑信息,添加楼层,删除楼层,编辑楼层信息。 + +**权限说明** :需要获取 管理会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uETNwYjLxUDM24SM1AjN + +POST https://open.feishu.cn/open-apis/meeting_room/building/update +*/ +func BuildingUpdate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiBuildingUpdate, bytes.NewReader(payload), header) +} + +/* +删除建筑物 + + +该接口用于删除建筑物(办公大楼)。 + +**权限说明** :需要获取 管理会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMzMxYjLzMTM24yMzEjN + +POST https://open.feishu.cn/open-apis/meeting_room/building/delete +*/ +func BuildingDelete(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiBuildingDelete, bytes.NewReader(payload), header) +} + +/* +查询建筑物ID + + +该接口用于根据租户自定义建筑 ID 查询建筑 ID。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQzMxYjL0MTM24CNzEjN + +GET https://open.feishu.cn/open-apis/meeting_room/building/batch_get_id?custom_building_ids=test01&custom_building_ids=test02 +*/ +func BuildingBatchGetById(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiBuildingBatchGetById+"?"+params.Encode(), header) +} + +/* +创建会议室 + + +该接口用于创建会议室。 + +**权限说明** :需要获取 管理会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uITNwYjLyUDM24iM1AjN + +POST https://open.feishu.cn/open-apis/meeting_room/room/create +*/ +func MeetingRoomCreate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiMeetingRoomCreate, bytes.NewReader(payload), header) +} + +/* +更新会议室 + + +该接口用于更新会议室。 + +**权限说明** :需要获取 管理会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMTNwYjLzUDM24yM1AjN + +POST https://open.feishu.cn/open-apis/meeting_room/room/update +*/ +func MeetingRoomUpdate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiMeetingRoomUpdate, bytes.NewReader(payload), header) +} + +/* +删除会议室 + + +该接口用于删除会议室。 + +**权限说明** :需要获取 管理会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUzMxYjL1MTM24SNzEjN + +POST https://open.feishu.cn/open-apis/meeting_room/room/delete +*/ +func MeetingRoomDelete(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiMeetingRoomDelete, bytes.NewReader(payload), header) +} + +/* +查询会议室ID + + +该接口用于根据租户自定义会议室ID查询会议室ID。 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzMxYjL2MTM24iNzEjN + +GET https://open.feishu.cn/open-apis/meeting_room/room/batch_get_id?custom_room_ids=test01&custom_room_ids=test02 +*/ +func MeetingRoomBatchGetById(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiMeetingRoomBatchGetById+"?"+params.Encode(), header) +} + +/* +获取国家地区列表 + + +新建建筑时需要标明所处国家/地区,该接口用于获得系统预先提供的可供选择的国家 /地区列表 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTNwYjL0UDM24CN1AjN + +GET https://open.feishu.cn/open-apis/meeting_room/country/list +*/ +func CountryList(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiCountryList, header) +} + +/* +获取城市列表 + + +新建建筑时需要选择所处国家/地区,该接口用于获得系统预先提供的可供选择的城市列表 + +**权限说明** :需要获取 获取会议室信息 权限。 + + +See: https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUTNwYjL1UDM24SN1AjN + +GET https://open.feishu.cn/open-apis/meeting_room/district/list?country_id=1814991 +*/ +func DistrictList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiDistrictList+"?"+params.Encode(), header) +} diff --git a/apis/capabilities/meeting/meeting_test.go b/apis/capabilities/meeting/meeting_test.go new file mode 100644 index 0000000..c353857 --- /dev/null +++ b/apis/capabilities/meeting/meeting_test.go @@ -0,0 +1,615 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package meeting + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestBuildingList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBuildingList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BuildingList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BuildingList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BuildingList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestBuildingBatchGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBuildingBatchGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BuildingBatchGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BuildingBatchGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BuildingBatchGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMeetingRoomList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMeetingRoomList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := MeetingRoomList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("MeetingRoomList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("MeetingRoomList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMeetingRoomBatchGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMeetingRoomBatchGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := MeetingRoomBatchGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("MeetingRoomBatchGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("MeetingRoomBatchGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMeetingRoomFreeBusyBatchGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMeetingRoomFreeBusyBatchGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := MeetingRoomFreeBusyBatchGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("MeetingRoomFreeBusyBatchGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("MeetingRoomFreeBusyBatchGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestInstanceReply(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiInstanceReply, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := InstanceReply(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceReply() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("InstanceReply() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestBuildingCreate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBuildingCreate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BuildingCreate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BuildingCreate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BuildingCreate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestBuildingUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBuildingUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BuildingUpdate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BuildingUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BuildingUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestBuildingDelete(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBuildingDelete, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BuildingDelete(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BuildingDelete() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BuildingDelete() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestBuildingBatchGetById(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBuildingBatchGetById, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BuildingBatchGetById(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BuildingBatchGetById() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BuildingBatchGetById() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMeetingRoomCreate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMeetingRoomCreate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := MeetingRoomCreate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("MeetingRoomCreate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("MeetingRoomCreate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMeetingRoomUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMeetingRoomUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := MeetingRoomUpdate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("MeetingRoomUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("MeetingRoomUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMeetingRoomDelete(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMeetingRoomDelete, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := MeetingRoomDelete(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("MeetingRoomDelete() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("MeetingRoomDelete() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMeetingRoomBatchGetById(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMeetingRoomBatchGetById, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := MeetingRoomBatchGetById(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("MeetingRoomBatchGetById() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("MeetingRoomBatchGetById() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestCountryList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiCountryList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := CountryList(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("CountryList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("CountryList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDistrictList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDistrictList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DistrictList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DistrictList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DistrictList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/contact/async_batch/async_batch.go b/apis/contact/async_batch/async_batch.go new file mode 100644 index 0000000..c82f1ba --- /dev/null +++ b/apis/contact/async_batch/async_batch.go @@ -0,0 +1,110 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package async_batch 异步批量接口 +package async_batch + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiDepartmentBatchAdd = "/open-apis/contact/v2/department/batch_add" + apiUserBatchAdd = "/open-apis/contact/v2/user/batch_add" + apiTaskGet = "/open-apis/contact/v2/task/get" +) + +/* +批量新增部门 + + +该接口用于向通讯录中批量新增多个部门。 +调用该接口需要具有所有新增部门父部门的授权范围。 +应用商店应用无权限调用此接口。 +调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uMDOwUjLzgDM14yM4ATN + +POST https://open.feishu.cn/open-apis/contact/v2/department/batch_add +*/ +func DepartmentBatchAdd(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiDepartmentBatchAdd, bytes.NewReader(payload), header) +} + +/* +批量新增用户 + + +该接口用于向通讯录中批量新增多个用户。 +调用该接口需要具有待添加用户所在部门的通讯录授权范围。 +应用商店应用无权限调用此接口。 +调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uIDOwUjLygDM14iM4ATN + +POST https://open.feishu.cn/open-apis/contact/v2/user/batch_add +*/ +func UserBatchAdd(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiUserBatchAdd, bytes.NewReader(payload), header) +} + +/* +查询批量任务执行状态 + + +该接口用于查询通讯录异步任务当前的执行状态以及执行结果。 +应用商店应用无权限调用此接口。 +调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUDOwUjL1gDM14SN4ATN + +GET https://open.feishu.cn/open-apis/contact/v2/task/get?task_id=0123456789abcdef0123456789abcdef +*/ +func TaskGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiTaskGet+"?"+params.Encode(), header) +} diff --git a/apis/contact/async_batch/async_batch_test.go b/apis/contact/async_batch/async_batch_test.go new file mode 100644 index 0000000..56d9e23 --- /dev/null +++ b/apis/contact/async_batch/async_batch_test.go @@ -0,0 +1,141 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package async_batch + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestDepartmentBatchAdd(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentBatchAdd, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentBatchAdd(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentBatchAdd() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentBatchAdd() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestUserBatchAdd(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiUserBatchAdd, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := UserBatchAdd(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("UserBatchAdd() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("UserBatchAdd() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestTaskGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiTaskGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := TaskGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("TaskGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("TaskGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/contact/async_batch/example_async_batch_test.go b/apis/contact/async_batch/example_async_batch_test.go new file mode 100644 index 0000000..964a0c4 --- /dev/null +++ b/apis/contact/async_batch/example_async_batch_test.go @@ -0,0 +1,53 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package async_batch_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/contact/async_batch" +) + +func ExampleDepartmentBatchAdd() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := async_batch.DepartmentBatchAdd(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleUserBatchAdd() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := async_batch.UserBatchAdd(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleTaskGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := async_batch.TaskGet(ctx, params) + + fmt.Println(resp, err) +} diff --git a/apis/contact/contact.go b/apis/contact/contact.go new file mode 100644 index 0000000..e8f9aba --- /dev/null +++ b/apis/contact/contact.go @@ -0,0 +1,57 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package contact 获取企业自定义用户属性配置 +package contact + +import ( + "net/http" + + "github.com/fastwego/feishu" +) + +const ( + apiTenantCustomAttrGet = "/open-apis/contact/v1/tenant/custom_attr/get" +) + +/* +获取企业自定义用户属性配置 + + +该接口用于获取企业配置的自定义用户属性。 + +**权限说明** :调用该接口需要申请 获取部门组织架构信息 和 以应用身份访问通讯录 权限 + +::: note + 1. 调用该接口前,需要先确认企业管理员已经在 **企业管理后台** > **通讯录设置** 自定义信息栏开启了“允许开放平台API调用“。 + 2. 调用该接口的应用需要具有当前企业通讯录的读取或者更新权限。 +::: + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ucTN3QjL3UzN04yN1cDN + +GET https://open.feishu.cn/open-apis/contact/v1/tenant/custom_attr/get +*/ +func TenantCustomAttrGet(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiTenantCustomAttrGet, header) +} diff --git a/apis/contact/contact_test.go b/apis/contact/contact_test.go new file mode 100644 index 0000000..d938b3d --- /dev/null +++ b/apis/contact/contact_test.go @@ -0,0 +1,66 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package contact + +import ( + "net/http" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestTenantCustomAttrGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiTenantCustomAttrGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := TenantCustomAttrGet(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("TenantCustomAttrGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("TenantCustomAttrGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/contact/department/department.go b/apis/contact/department/department.go new file mode 100644 index 0000000..b6330ea --- /dev/null +++ b/apis/contact/department/department.go @@ -0,0 +1,188 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package department 部门管理 +package department + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiDepartmentInfoGet = "/open-apis/contact/v1/department/info/get" + apiDepartmentSimpleList = "/open-apis/contact/v1/department/simple/list" + apiDepartmentDetailBatchGet = "/open-apis/contact/v1/department/detail/batch_get" + apiDepartmentAdd = "/open-apis/contact/v1/department/add" + apiDepartmentDelete = "/open-apis/contact/v1/department/delete" + apiDepartmentUpdate = "/open-apis/contact/v1/department/update" +) + +/* +获取部门详情 + +该接口用于获取部门详情信息。 + +**权限说明** :调用该接口需要具有 以应用身份访问通讯录 以及 [部门数据权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN)。应用需要拥有待查询部门的通讯录授权。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uAzNz4CM3MjLwczM + +GET https://open.feishu.cn/open-apis/contact/v1/department/info/get?department_id=TT-1234 +*/ +func DepartmentInfoGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiDepartmentInfoGet+"?"+params.Encode(), header) +} + +/* +获取子部门列表 + + +该接口用于获取当前部门子部门列表。 + +**权限说明**:调用该接口需要申请 获取部门组织架构信息 以及 以应用身份访问通讯录 权限。调用该接口需要具有当前部门的授权范围。企业根部门 ID 为 0,当获取根部门子部门列表时,通讯录授权范围必须为全员权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ugzN3QjL4czN04CO3cDN + +GET https://open.feishu.cn/open-apis/contact/v1/department/simple/list?department_id=TT-1234&page_size=10&fetch_child=true +*/ +func DepartmentSimpleList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiDepartmentSimpleList+"?"+params.Encode(), header) +} + +/* +批量获取部门详情 + + +该接口用于批量获取部门详情,只返回权限范围内的部门。 +**权限说明** : 调用该接口需要申请 以应用身份访问通讯录 以及[部门数据权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN)。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uczN3QjL3czN04yN3cDN + +GET https://open.feishu.cn/open-apis/contact/v1/department/detail/batch_get?department_ids=od-2efe30807a10608754862a63b108828f&department_ids=od-da6427b2adbceb91204d7fa6aeb7e8ff +*/ +func DepartmentDetailBatchGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiDepartmentDetailBatchGet+"?"+params.Encode(), header) +} + +/* +新增部门 + + +该接口用于向通讯录中增加新的部门。 + +**权限说明** :调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要拥有待新增部门的父部门的通讯录授权。应用商店应用无权限调用接口。 + + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uYzNz4iN3MjL2czM + +POST https://open.feishu.cn/open-apis/contact/v1/department/add +*/ +func DepartmentAdd(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiDepartmentAdd, bytes.NewReader(payload), header) +} + +/* +删除部门 + +该接口用于从通讯录中删除部门。 + +**权限说明** : 调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要同时拥有待删除部门及其父部门的通讯录授权。应用商店应用无权限调用该接口。 + + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ugzNz4CO3MjL4czM + +POST https://open.feishu.cn/open-apis/contact/v1/department/delete +*/ +func DepartmentDelete(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiDepartmentDelete, bytes.NewReader(payload), header) +} + +/* +更新部门信息 + +该接口用于更新通讯录中部门的信息。 + +**权限说明**:调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。调用该接口需要具有该部门以及更新操作涉及的部门的通讯录权限。应用商店应用无权限调用此接口。 + + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uczNz4yN3MjL3czM + +POST https://open.feishu.cn/open-apis/contact/v1/department/update +*/ +func DepartmentUpdate(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiDepartmentUpdate, bytes.NewReader(payload), header) +} diff --git a/apis/contact/department/department_test.go b/apis/contact/department/department_test.go new file mode 100644 index 0000000..1bd090a --- /dev/null +++ b/apis/contact/department/department_test.go @@ -0,0 +1,251 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package department + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestDepartmentInfoGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentInfoGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentInfoGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentInfoGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentInfoGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDepartmentSimpleList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentSimpleList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentSimpleList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentSimpleList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentSimpleList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDepartmentDetailBatchGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentDetailBatchGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentDetailBatchGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentDetailBatchGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentDetailBatchGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDepartmentAdd(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentAdd, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentAdd(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentAdd() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentAdd() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDepartmentDelete(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentDelete, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentDelete(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentDelete() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentDelete() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDepartmentUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentUpdate(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentUpdate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentUpdate() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/contact/department/example_department_test.go b/apis/contact/department/example_department_test.go new file mode 100644 index 0000000..8a7a376 --- /dev/null +++ b/apis/contact/department/example_department_test.go @@ -0,0 +1,83 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package department_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/contact/department" +) + +func ExampleDepartmentInfoGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := department.DepartmentInfoGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleDepartmentSimpleList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := department.DepartmentSimpleList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleDepartmentDetailBatchGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := department.DepartmentDetailBatchGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleDepartmentAdd() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := department.DepartmentAdd(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleDepartmentDelete() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := department.DepartmentDelete(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleDepartmentUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := department.DepartmentUpdate(ctx, payload) + + fmt.Println(resp, err) +} diff --git a/apis/contact/example_contact_test.go b/apis/contact/example_contact_test.go new file mode 100644 index 0000000..61d32ba --- /dev/null +++ b/apis/contact/example_contact_test.go @@ -0,0 +1,30 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package contact_test + +import ( + "fmt" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/contact" +) + +func ExampleTenantCustomAttrGet() { + var ctx *feishu.App + + resp, err := contact.TenantCustomAttrGet(ctx) + + fmt.Println(resp, err) +} diff --git a/apis/contact/user/example_user_test.go b/apis/contact/user/example_user_test.go new file mode 100644 index 0000000..d4fb9ea --- /dev/null +++ b/apis/contact/user/example_user_test.go @@ -0,0 +1,132 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user_test + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/contact/user" +) + +func ExampleBatchGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user.BatchGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleDepartmentUserList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user.DepartmentUserList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleDepartmentUserDetailList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user.DepartmentUserDetailList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleAdd() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := user.Add(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleDelete() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := user.Delete(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleUpdate() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := user.Update(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleRoleList() { + var ctx *feishu.App + + resp, err := user.RoleList(ctx) + + fmt.Println(resp, err) +} + +func ExampleRoleMembers() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user.RoleMembers(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleBatchGetId() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user.BatchGetId(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleUnionIdBatchGetList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user.UnionIdBatchGetList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleSearch() { + var ctx *feishu.App + + params := url.Values{} + header := http.Header{} + resp, err := user.Search(ctx, params, header) + + fmt.Println(resp, err) +} diff --git a/apis/contact/user/user.go b/apis/contact/user/user.go new file mode 100644 index 0000000..2a73a40 --- /dev/null +++ b/apis/contact/user/user.go @@ -0,0 +1,320 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package user 用户管理 +package user + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiBatchGet = "/open-apis/contact/v1/user/batch_get" + apiDepartmentUserList = "/open-apis/contact/v1/department/user/list" + apiDepartmentUserDetailList = "/open-apis/contact/v1/department/user/detail/list" + apiAdd = "/open-apis/contact/v1/user/add" + apiDelete = "/open-apis/contact/v1/user/delete" + apiUpdate = "/open-apis/contact/v1/user/update" + apiRoleList = "/open-apis/contact/v2/role/list" + apiRoleMembers = "/open-apis/contact/v2/role/members" + apiBatchGetId = "/open-apis/user/v1/batch_get_id" + apiUnionIdBatchGetList = "/open-apis/user/v1/union_id/batch_get/list" + apiSearch = "/open-apis/search/v1/user" +) + +/* +批量获取用户信息 + + +该接口用于批量获取用户详细信息。 + +**权限说明** : 调用该接口需要申请“以应用身份访问通讯录”以及[用户数据权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN)。请求的用户如果在当前应用的通讯录授权范围内,会返回该用户的详细信息;否则不会返回。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uIzNz4iM3MjLyczM + +GET https://open.feishu.cn/open-apis/contact/v1/user/batch_get?employee_ids=2fab1234&employee_ids=2f1234cd +*/ +func BatchGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiBatchGet+"?"+params.Encode(), header) +} + +/* +获取部门用户列表 + + +该接口用于获取部门用户列表。 +**权限说明**:调用该接口需要申请 获取部门组织架构信息 和 以应用身份访问通讯录 权限。应用需要有被调用部门的通讯录授权 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uEzNz4SM3MjLxczM + +GET https://open.feishu.cn/open-apis/contact/v1/department/user/list?department_id=TT-1234&page_size=100&fetch_child=true +*/ +func DepartmentUserList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiDepartmentUserList+"?"+params.Encode(), header) +} + +/* +获取部门用户详情 + + +该接口用于获取部门用户详情信息。 +**权限说明**:调用该接口需要申请 获取部门组织架构信息 和 以应用身份访问通讯录 权限,同时根据需要返回字段申请对应的[用户数据权限]。应用需要有被调用部门的通讯录授权。(https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN) + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uYzN3QjL2czN04iN3cDN + +GET https://open.feishu.cn/open-apis/contact/v1/department/user/detail/list?department_id=TT-1234&page_size=10&fetch_child=true +*/ +func DepartmentUserDetailList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiDepartmentUserDetailList+"?"+params.Encode(), header) +} + +/* +新增用户 + + +该接口用于向通讯录中新增用户。 + +**权限说明** :调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。新增用户的所有部门必须都在当前应用的通讯录授权范围内才允许新增用户。应用商店应用无权限调用此接口。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uMzNz4yM3MjLzczM + +POST https://open.feishu.cn/open-apis/contact/v1/user/add +*/ +func Add(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiAdd, bytes.NewReader(payload), header) +} + +/* +删除用户 + + +该接口用于从通讯录中删除用户。 + +**权限说明** : 调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要有待删除用户、待删除用户的所有部门的通讯录权限才能删除该用户。应用商店应用无权限调用接口。 + + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUzNz4SN3MjL1czM + +POST https://open.feishu.cn/open-apis/contact/v1/user/delete +*/ +func Delete(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiDelete, bytes.NewReader(payload), header) +} + +/* +更新用户信息 + + +该接口用于更新通讯录中用户信息。 + +**权限说明** : 调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要拥有待更新用户的通讯录授权,如果涉及到用户部门变更,还需要同时拥有所有新部门的通讯录授权。应用商店应用无权限调用此接口。 + + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uQzNz4CN3MjL0czM + +POST https://open.feishu.cn/open-apis/contact/v1/user/update +*/ +func Update(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiUpdate, bytes.NewReader(payload), header) +} + +/* +获取角色列表 + + +该接口用于获取企业的用户角色列表。 + +**权限说明** :调用该接口需要申请 获取角色 和 以应用身份访问通讯录 权限 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uYzMwUjL2MDM14iNzATN + +GET https://open.feishu.cn/open-apis/contact/v2/role/list +*/ +func RoleList(ctx *feishu.App) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiRoleList, header) +} + +/* +获取角色成员列表 + + +该接口用于获取角色下的用户列表。 + +**权限说明** :调用该接口需要申请 获取角色 和 以应用身份访问通讯录 权限。角色内不在当前应用通讯录授权范围内的用户不会返回。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uczMwUjL3MDM14yNzATN + +GET https://open.feishu.cn/open-apis/contact/v2/role/members?role_id=or_846ea69995a259a27cc690182f27de87&page_size=2&page_token=763bd1e74d05e958 +*/ +func RoleMembers(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiRoleMembers+"?"+params.Encode(), header) +} + +/* +使用手机号或邮箱获取用户 ID + +ID + +根据用户邮箱或手机号查询用户 open_id 和 user_id,支持批量查询。 +调用该接口需要申请 通过手机号或者邮箱获取用户ID 权限。 + +:::warnning +只能查询到应用可用性范围内的用户 ID,不在范围内的用户会表现为不存在。 +::: + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUzMyUjL1MjM14SNzITN + +GET https://open.feishu.cn/open-apis/user/v1/batch_get_id?emails=lisi@z.com&emails=wangwu@z.com&mobiles=13812345678&mobiles=%2b12126668888 +*/ +func BatchGetId(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiBatchGetId+"?"+params.Encode(), header) +} + +/* +使用统一 ID 获取用户 ID + +ID 获取用户 ID +使用统一 ID 获取用户 ID信息。 +调用该接口需要具有 获取用户统一ID 权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUTO5UjL1kTO14SN5kTN + +GET https://open.feishu.cn/open-apis/user/v1/union_id/batch_get/list?union_ids=on_94a1ee5551019f18cd73d9f111898cf2&union_ids=on_42f2ef9d07319a4d96fffd7ef5cbfc79 +*/ +func UnionIdBatchGetList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiUnionIdBatchGetList+"?"+params.Encode(), header) +} + +/* +搜索用户 + + +以用户身份搜索其他用户的信息,无法搜索到外部企业或已离职的用户。 +调用该接口需要申请 搜索用户 权限。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uMTM4UjLzEDO14yMxgTN + +GET https://open.feishu.cn/open-apis/search/v1/user?query=zhangsan&page_size=20&page_token=20 +*/ +func Search(ctx *feishu.App, params url.Values, header http.Header) (resp []byte, err error) { + + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiSearch+"?"+params.Encode(), header) +} diff --git a/apis/contact/user/user_test.go b/apis/contact/user/user_test.go new file mode 100644 index 0000000..b0b86c1 --- /dev/null +++ b/apis/contact/user/user_test.go @@ -0,0 +1,435 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestBatchGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBatchGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BatchGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BatchGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BatchGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDepartmentUserList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentUserList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentUserList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentUserList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentUserList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDepartmentUserDetailList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDepartmentUserDetailList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := DepartmentUserDetailList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("DepartmentUserDetailList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("DepartmentUserDetailList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAdd(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAdd, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Add(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Add() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Add() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestDelete(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiDelete, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Delete(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Delete() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestUpdate(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiUpdate, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Update(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Update() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Update() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestRoleList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiRoleList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := RoleList(tt.args.ctx) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("RoleList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("RoleList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestRoleMembers(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiRoleMembers, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := RoleMembers(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("RoleMembers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("RoleMembers() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestBatchGetId(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBatchGetId, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BatchGetId(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BatchGetId() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BatchGetId() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestUnionIdBatchGetList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiUnionIdBatchGetList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := UnionIdBatchGetList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("UnionIdBatchGetList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("UnionIdBatchGetList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSearch(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSearch, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + header http.Header + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp, header: http.Header{}}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Search(tt.args.ctx, tt.args.params, tt.args.header) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Search() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Search() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/message/example_message_test.go b/apis/message/example_message_test.go new file mode 100644 index 0000000..de15fa4 --- /dev/null +++ b/apis/message/example_message_test.go @@ -0,0 +1,93 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package message_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/message" +) + +func ExampleBatchSend() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := message.BatchSend(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleSend() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := message.Send(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleReadInfo() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := message.ReadInfo(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleImagePut() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := message.ImagePut(ctx, payload) + + fmt.Println(resp, err) +} + +func ExampleImageGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := message.ImageGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleFileGet() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := message.FileGet(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleAppNotify() { + var ctx *feishu.App + + payload := []byte("{}") + + resp, err := message.AppNotify(ctx, payload) + + fmt.Println(resp, err) +} diff --git a/apis/message/message.go b/apis/message/message.go new file mode 100644 index 0000000..16d06dc --- /dev/null +++ b/apis/message/message.go @@ -0,0 +1,216 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package message 消息 +package message + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiBatchSend = "/open-apis/message/v4/batch_send/" + apiSend = "/open-apis/message/v4/send/" + apiReadInfo = "/open-apis/message/v4/read_info/" + apiImagePut = "/open-apis/image/v4/put/" + apiImageGet = "/open-apis/image/v4/get" + apiFileGet = "/open-apis/open-file/v1/get" + apiAppNotify = "/open-apis/notify/v4/appnotify" +) + +/* +批量发送消息 + + +给多个用户或者多个部门发送消息。 + +**权限说明** :需要启用机器人能力;机器人需要拥有批量发送消息权限;机器人需要拥有对用户或部门的可见性 + + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ucDO1EjL3gTNx4yN4UTM + +POST https://open.feishu.cn/open-apis/message/v4/batch_send/ +*/ +func BatchSend(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiBatchSend, bytes.NewReader(payload), header) +} + +/* +发送文本消息 + + +给指定用户或者会话发送文本消息,其中会话包括私聊会话和群会话。 + +**权限说明** :需要启用机器人能力;私聊会话时机器人需要拥有对用户的可见性,群会话需要机器人在群里 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUjNz4SN2MjL1YzM + +POST https://open.feishu.cn/open-apis/message/v4/send/ +*/ +func Send(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiSend, bytes.NewReader(payload), header) +} + +/* +查询消息已读状态 + + +查询消息已读状态,只能查询最近七天机器人自身发送消息的已读信息。 + +**权限说明** :需要启用机器人能力 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/ukTM2UjL5EjN14SOxYTN + +POST https://open.feishu.cn/open-apis/message/v4/read_info/ +*/ +func ReadInfo(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiReadInfo, bytes.NewReader(payload), header) +} + +/* +上传图片 + + +上传图片,获取图片的 image_key。 + +**权限说明** :需要启用机器人能力 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uEDO04SM4QjLxgDN + +POST https://open.feishu.cn/open-apis/image/v4/put/ +*/ +func ImagePut(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiImagePut, bytes.NewReader(payload), header) +} + +/* +获取图片 + + +根据图片的image_key获取图片内容 + +**权限说明** :需要启用机器人能力;机器人只能获取自己上传或者收到推送的图片 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uYzN5QjL2cTO04iN3kDN + +GET https://open.feishu.cn/open-apis/image/v4/get?image_key=24383920-9321-4ecd-8b33-bf8ce74e84c8 +*/ +func ImageGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiImageGet+"?"+params.Encode(), header) +} + +/* +获取文件 + + +根据文件的 file_key 拉取文件内容,当前仅可用来获取用户与机器人单聊发送的文件 + +**权限说明** :需要启用机器人能力;机器人只能获取自己上传的文件 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uMDN4UjLzQDO14yM0gTN + +GET https://open.feishu.cn/open-apis/open-file/v1/get?file_key=file_36r377cb-c6h2-4b6d-ag67-0ac3e796008g +*/ +func FileGet(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiFileGet+"?"+params.Encode(), header) +} + +/* +应用发送通知给用户 + +给指定用户发送应用通知 + +**权限说明** :需要申请 以应用的身份发消息 的权限;同时需要启用小程序能力,并且支持**PC模式**。 + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uATOyUjLwkjM14CM5ITN + +POST https://open.feishu.cn/open-apis/notify/v4/appnotify +*/ +func AppNotify(ctx *feishu.App, payload []byte) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPPost(apiAppNotify, bytes.NewReader(payload), header) +} diff --git a/apis/message/message_test.go b/apis/message/message_test.go new file mode 100644 index 0000000..9ce7fa1 --- /dev/null +++ b/apis/message/message_test.go @@ -0,0 +1,286 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package message + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestBatchSend(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiBatchSend, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := BatchSend(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("BatchSend() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("BatchSend() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSend(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSend, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Send(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Send() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Send() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestReadInfo(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiReadInfo, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ReadInfo(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ReadInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ReadInfo() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestImagePut(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiImagePut, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ImagePut(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ImagePut() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ImagePut() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestImageGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiImageGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := ImageGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("ImageGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("ImageGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestFileGet(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiFileGet, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := FileGet(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("FileGet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("FileGet() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestAppNotify(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiAppNotify, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + payload []byte + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := AppNotify(tt.args.ctx, tt.args.payload) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("AppNotify() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("AppNotify() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/apis/user_group/example_user_group_test.go b/apis/user_group/example_user_group_test.go new file mode 100644 index 0000000..4fac870 --- /dev/null +++ b/apis/user_group/example_user_group_test.go @@ -0,0 +1,53 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user_group_test + +import ( + "fmt" + "net/url" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/apis/user_group" +) + +func ExampleGroupList() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user_group.GroupList(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleMembers() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user_group.Members(ctx, params) + + fmt.Println(resp, err) +} + +func ExampleSearch() { + var ctx *feishu.App + + params := url.Values{} + + resp, err := user_group.Search(ctx, params) + + fmt.Println(resp, err) +} diff --git a/apis/user_group/user_group.go b/apis/user_group/user_group.go new file mode 100644 index 0000000..b3782b2 --- /dev/null +++ b/apis/user_group/user_group.go @@ -0,0 +1,104 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package user_group 用户群组 +package user_group + +import ( + "net/http" + "net/url" + + "github.com/fastwego/feishu" +) + +const ( + apiGroupList = "/open-apis/user/v4/group_list" + apiMembers = "/open-apis/chat/v4/members" + apiSearch = "/open-apis/chat/v4/search" +) + +/* +获取用户所在的群列表 + +获取用户所在的群列表。 + +**权限说明** :应用需要“读取群信息”权限; + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uQzMwUjL0MDM14CNzATN + +GET https://open.feishu.cn/open-apis/user/v4/group_list?page_size=2&page_token=6592161138799017988 +*/ +func GroupList(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiGroupList+"?"+params.Encode(), header) +} + +/* +获取群成员列表 + +如果用户在群中,则返回该群的成员列表。 + +**权限说明** :应用需要“读取群信息”权限; + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUzMwUjL1MDM14SNzATN + +GET https://open.feishu.cn/open-apis/chat/v4/members?chat_id=oc_92c3f700c2ae31369cefee459fb93870&page_token=0&page_size=3 +*/ +func Members(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiMembers+"?"+params.Encode(), header) +} + +/* +搜索用户所在的群列表 + +搜索用户所在的群列表。 + +**权限说明** :应用需要“读取群信息”权限; + + +See: https://open.feishu.cn/document/ukTMukTMukTM/uUTOyUjL1kjM14SN5ITN + +GET https://open.feishu.cn/open-apis/chat/v4/search?page_size=10&page_token=24&query=rd +*/ +func Search(ctx *feishu.App, params url.Values) (resp []byte, err error) { + + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") + + return ctx.Client.HTTPGet(apiSearch+"?"+params.Encode(), header) +} diff --git a/apis/user_group/user_group_test.go b/apis/user_group/user_group_test.go new file mode 100644 index 0000000..776b8d6 --- /dev/null +++ b/apis/user_group/user_group_test.go @@ -0,0 +1,143 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package user_group + +import ( + "net/http" + "net/url" + "os" + "reflect" + "testing" + + "github.com/fastwego/feishu" + "github.com/fastwego/feishu/test" +) + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +func TestGroupList(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiGroupList, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := GroupList(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("GroupList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("GroupList() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestMembers(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiMembers, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Members(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Members() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Members() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} +func TestSearch(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(apiSearch, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + ctx *feishu.App + + params url.Values + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := Search(tt.args.ctx, tt.args.params) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("Search() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("Search() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +} diff --git a/client.go b/client.go new file mode 100644 index 0000000..c1e853e --- /dev/null +++ b/client.go @@ -0,0 +1,137 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package feishu + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" +) + +/* +飞书 api 服务器地址 +*/ +var FeishuServerUrl = "https://open.feishu.cn" + +/* +HttpClient 用于向 接口发送请求 +*/ +type Client struct { + Ctx *App +} + +// HTTPGet GET 请求 +func (client *Client) HTTPGet(uri string, header http.Header) (resp []byte, err error) { + url := FeishuServerUrl + uri + + httpClient := &http.Client{} + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return + } + req.Header = header + + if client.Ctx.Logger != nil { + client.Ctx.Logger.Printf("GET %s Headers %v", req.URL.String(), req.Header) + } + response, err := httpClient.Do(req) + if err != nil { + return + } + defer response.Body.Close() + return responseFilter(response) +} + +//HTTPPost POST 请求 +func (client *Client) HTTPPost(uri string, payload io.Reader, header http.Header) (resp []byte, err error) { + + url := FeishuServerUrl + uri + + httpClient := &http.Client{} + req, err := http.NewRequest(http.MethodPost, url, payload) + if err != nil { + return + } + req.Header = header + + if client.Ctx.Logger != nil { + client.Ctx.Logger.Printf("GET %s Headers %v", req.URL.String(), req.Header) + } + response, err := httpClient.Do(req) + if err != nil { + return + } + defer response.Body.Close() + return responseFilter(response) +} + +func (client *Client) HTTPDelete(uri string, header http.Header) (resp []byte, err error) { + url := FeishuServerUrl + uri + + httpClient := &http.Client{} + req, err := http.NewRequest(http.MethodDelete, url, nil) + if err != nil { + return + } + req.Header = header + + if client.Ctx.Logger != nil { + client.Ctx.Logger.Printf("DELETE %s Headers %v", req.URL.String(), req.Header) + } + response, err := httpClient.Do(req) + if err != nil { + return + } + defer response.Body.Close() + return responseFilter(response) +} + +/* +筛查 api 服务器响应,判断以下错误: + +- http 状态码 不为 200 + +- 接口响应码 code 不为 0 +*/ +func responseFilter(response *http.Response) (resp []byte, err error) { + if response.StatusCode != http.StatusOK { + err = fmt.Errorf("Status %s", response.Status) + return + } + + resp, err = ioutil.ReadAll(response.Body) + if err != nil { + return + } + + errorResponse := struct { + Code int64 `json:"code"` + Msg string `json:"msg"` + }{} + err = json.Unmarshal(resp, &errorResponse) + if err != nil { + return + } + + if errorResponse.Code != 0 { + err = errors.New(string(resp)) + return + } + + return +} diff --git a/cmd/apiconfig.go b/cmd/apiconfig.go new file mode 100644 index 0000000..24faaa0 --- /dev/null +++ b/cmd/apiconfig.go @@ -0,0 +1,727 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +type Param struct { + Name string + Type string +} + +type Api struct { + Name string + Description string + Request string + See string + FuncName string + AccessToken string + GetParams []Param + PathParams []Param +} + +type ApiGroup struct { + Name string + Apis []Api + Package string +} + +var apiConfig = []ApiGroup{ + { + Name: `用户管理`, + Package: `contact/user`, + Apis: []Api{ + { + Name: "批量获取用户信息", + Description: ` +该接口用于批量获取用户详细信息。 + +**权限说明** : 调用该接口需要申请“以应用身份访问通讯录”以及[用户数据权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN)。请求的用户如果在当前应用的通讯录授权范围内,会返回该用户的详细信息;否则不会返回。 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/user/batch_get?employee_ids=2fab1234&employee_ids=2f1234cd", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uIzNz4iM3MjLyczM", + FuncName: "BatchGet", + AccessToken: "tenant", + GetParams: []Param{ + {Name: `employee_ids`, Type: `string`}, + }, + }, + { + Name: "获取部门用户列表", + Description: ` +该接口用于获取部门用户列表。 +**权限说明**:调用该接口需要申请 获取部门组织架构信息 和 以应用身份访问通讯录 权限。应用需要有被调用部门的通讯录授权 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/department/user/list?department_id=TT-1234&page_size=100&fetch_child=true", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uEzNz4SM3MjLxczM", + FuncName: "DepartmentUserList", + GetParams: []Param{ + {Name: `page_size`, Type: `string`}, + {Name: `fetch_child`, Type: `string`}, + {Name: `department_id`, Type: `string`}, + }, + }, + { + Name: "获取部门用户详情", + Description: ` +该接口用于获取部门用户详情信息。 +**权限说明**:调用该接口需要申请 获取部门组织架构信息 和 以应用身份访问通讯录 权限,同时根据需要返回字段申请对应的[用户数据权限]。应用需要有被调用部门的通讯录授权。(https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN) +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/department/user/detail/list?department_id=TT-1234&page_size=10&fetch_child=true", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uYzN3QjL2czN04iN3cDN", + FuncName: "DepartmentUserDetailList", + GetParams: []Param{ + {Name: `department_id`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + {Name: `fetch_child`, Type: `string`}, + }, + }, + { + Name: "新增用户", + Description: ` +该接口用于向通讯录中新增用户。 + +**权限说明** :调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。新增用户的所有部门必须都在当前应用的通讯录授权范围内才允许新增用户。应用商店应用无权限调用此接口。 +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v1/user/add", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uMzNz4yM3MjLzczM", + FuncName: "Add", + }, + { + Name: "删除用户", + Description: ` +该接口用于从通讯录中删除用户。 + +**权限说明** : 调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要有待删除用户、待删除用户的所有部门的通讯录权限才能删除该用户。应用商店应用无权限调用接口。 + +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v1/user/delete", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUzNz4SN3MjL1czM", + FuncName: "Delete", + }, + { + Name: "更新用户信息", + Description: ` +该接口用于更新通讯录中用户信息。 + +**权限说明** : 调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要拥有待更新用户的通讯录授权,如果涉及到用户部门变更,还需要同时拥有所有新部门的通讯录授权。应用商店应用无权限调用此接口。 + +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v1/user/update", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uQzNz4CN3MjL0czM", + FuncName: "Update", + }, + { + Name: "获取角色列表", + Description: ` +该接口用于获取企业的用户角色列表。 + +**权限说明** :调用该接口需要申请 获取角色 和 以应用身份访问通讯录 权限 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v2/role/list", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uYzMwUjL2MDM14iNzATN", + FuncName: "RoleList", + }, + { + Name: "获取角色成员列表", + Description: ` +该接口用于获取角色下的用户列表。 + +**权限说明** :调用该接口需要申请 获取角色 和 以应用身份访问通讯录 权限。角色内不在当前应用通讯录授权范围内的用户不会返回。 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v2/role/members?role_id=or_846ea69995a259a27cc690182f27de87&page_size=2&page_token=763bd1e74d05e958", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uczMwUjL3MDM14yNzATN", + FuncName: "RoleMembers", + GetParams: []Param{ + {Name: `page_token`, Type: `string`}, + {Name: `role_id`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + }, + }, + { + Name: "使用手机号或邮箱获取用户 ID", + Description: `ID + +根据用户邮箱或手机号查询用户 open_id 和 user_id,支持批量查询。 +调用该接口需要申请 通过手机号或者邮箱获取用户ID 权限。 + +:::warnning +只能查询到应用可用性范围内的用户 ID,不在范围内的用户会表现为不存在。 +::: +`, + Request: "GET https://open.feishu.cn/open-apis/user/v1/batch_get_id?emails=lisi@z.com&emails=wangwu@z.com&mobiles=13812345678&mobiles=%2b12126668888", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUzMyUjL1MjM14SNzITN", + FuncName: "BatchGetId", + GetParams: []Param{ + {Name: `emails`, Type: `string`}, + {Name: `mobiles`, Type: `string`}, + }, + }, + { + Name: "使用统一 ID 获取用户 ID", + Description: `ID 获取用户 ID +使用统一 ID 获取用户 ID信息。 +调用该接口需要具有 获取用户统一ID 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/user/v1/union_id/batch_get/list?union_ids=on_94a1ee5551019f18cd73d9f111898cf2&union_ids=on_42f2ef9d07319a4d96fffd7ef5cbfc79", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUTO5UjL1kTO14SN5kTN", + FuncName: "UnionIdBatchGetList", + GetParams: []Param{ + {Name: `union_ids`, Type: `string`}, + }, + }, + { + Name: "搜索用户", + Description: ` +以用户身份搜索其他用户的信息,无法搜索到外部企业或已离职的用户。 +调用该接口需要申请 搜索用户 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/search/v1/user?query=zhangsan&page_size=20&page_token=20", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uMTM4UjLzEDO14yMxgTN", + FuncName: "Search", + AccessToken: "user", + GetParams: []Param{ + {Name: `query`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + }, + }, + }, + }, + { + Name: `部门管理`, + Package: `contact/department`, + Apis: []Api{ + { + Name: "获取部门详情", + Description: `该接口用于获取部门详情信息。 + +**权限说明** :调用该接口需要具有 以应用身份访问通讯录 以及 [部门数据权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN)。应用需要拥有待查询部门的通讯录授权。 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/department/info/get?department_id=TT-1234", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uAzNz4CM3MjLwczM", + FuncName: "DepartmentInfoGet", + GetParams: []Param{ + {Name: `department_id`, Type: `string`}, + }, + }, + { + Name: "获取子部门列表", + Description: ` +该接口用于获取当前部门子部门列表。 + +**权限说明**:调用该接口需要申请 获取部门组织架构信息 以及 以应用身份访问通讯录 权限。调用该接口需要具有当前部门的授权范围。企业根部门 ID 为 0,当获取根部门子部门列表时,通讯录授权范围必须为全员权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/department/simple/list?department_id=TT-1234&page_size=10&fetch_child=true", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ugzN3QjL4czN04CO3cDN", + FuncName: "DepartmentSimpleList", + GetParams: []Param{ + {Name: `fetch_child`, Type: `string`}, + {Name: `department_id`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + }, + }, + { + Name: "批量获取部门详情", + Description: ` +该接口用于批量获取部门详情,只返回权限范围内的部门。 +**权限说明** : 调用该接口需要申请 以应用身份访问通讯录 以及[部门数据权限](https://open.feishu.cn/document/ukTMukTMukTM/uQjN3QjL0YzN04CN2cDN)。 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/department/detail/batch_get?department_ids=od-2efe30807a10608754862a63b108828f&department_ids=od-da6427b2adbceb91204d7fa6aeb7e8ff", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uczN3QjL3czN04yN3cDN", + FuncName: "DepartmentDetailBatchGet", + GetParams: []Param{ + {Name: `department_ids`, Type: `string`}, + }, + }, + { + Name: "新增部门", + Description: ` +该接口用于向通讯录中增加新的部门。 + +**权限说明** :调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要拥有待新增部门的父部门的通讯录授权。应用商店应用无权限调用接口。 + +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v1/department/add", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uYzNz4iN3MjL2czM", + FuncName: "DepartmentAdd", + }, + { + Name: "删除部门", + Description: `该接口用于从通讯录中删除部门。 + +**权限说明** : 调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。应用需要同时拥有待删除部门及其父部门的通讯录授权。应用商店应用无权限调用该接口。 + +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v1/department/delete", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ugzNz4CO3MjL4czM", + FuncName: "DepartmentDelete", + }, + { + Name: "更新部门信息", + Description: `该接口用于更新通讯录中部门的信息。 + +**权限说明**:调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。调用该接口需要具有该部门以及更新操作涉及的部门的通讯录权限。应用商店应用无权限调用此接口。 + +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v1/department/update", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uczNz4yN3MjL3czM", + FuncName: "DepartmentUpdate", + }, + }, + }, + { + Name: `异步批量接口`, + Package: `contact/async_batch`, + Apis: []Api{ + { + Name: "批量新增部门", + Description: ` +该接口用于向通讯录中批量新增多个部门。 +调用该接口需要具有所有新增部门父部门的授权范围。 +应用商店应用无权限调用此接口。 +调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v2/department/batch_add", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uMDOwUjLzgDM14yM4ATN", + FuncName: "DepartmentBatchAdd", + }, + { + Name: "批量新增用户", + Description: ` +该接口用于向通讯录中批量新增多个用户。 +调用该接口需要具有待添加用户所在部门的通讯录授权范围。 +应用商店应用无权限调用此接口。 +调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/contact/v2/user/batch_add", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uIDOwUjLygDM14iM4ATN", + FuncName: "UserBatchAdd", + }, + { + Name: "查询批量任务执行状态", + Description: ` +该接口用于查询通讯录异步任务当前的执行状态以及执行结果。 +应用商店应用无权限调用此接口。 +调用该接口需要申请 更新通讯录 以及 以应用身份访问通讯录 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v2/task/get?task_id=0123456789abcdef0123456789abcdef", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUDOwUjL1gDM14SN4ATN", + FuncName: "TaskGet", + GetParams: []Param{ + {Name: `task_id`, Type: `string`}, + }, + }, + }, + }, + { + Name: `获取企业自定义用户属性配置`, + Package: `contact`, + Apis: []Api{ + { + Name: "获取企业自定义用户属性配置", + Description: ` +该接口用于获取企业配置的自定义用户属性。 + +**权限说明** :调用该接口需要申请 获取部门组织架构信息 和 以应用身份访问通讯录 权限 + +::: note + 1. 调用该接口前,需要先确认企业管理员已经在 **企业管理后台** > **通讯录设置** 自定义信息栏开启了“允许开放平台API调用“。 + 2. 调用该接口的应用需要具有当前企业通讯录的读取或者更新权限。 +::: +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/tenant/custom_attr/get", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ucTN3QjL3UzN04yN1cDN", + FuncName: "TenantCustomAttrGet", + }, + }, + }, + { + Name: `用户群组`, + Package: `user_group`, + Apis: []Api{ + { + Name: "获取用户所在的群列表", + Description: `获取用户所在的群列表。 + +**权限说明** :应用需要“读取群信息”权限; +`, + Request: "GET https://open.feishu.cn/open-apis/user/v4/group_list?page_size=2&page_token=6592161138799017988", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uQzMwUjL0MDM14CNzATN", + FuncName: "GroupList", + GetParams: []Param{ + {Name: `page_size`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + }, + }, + { + Name: "获取群成员列表", + Description: `如果用户在群中,则返回该群的成员列表。 + +**权限说明** :应用需要“读取群信息”权限; +`, + Request: "GET https://open.feishu.cn/open-apis/chat/v4/members?chat_id=oc_92c3f700c2ae31369cefee459fb93870&page_token=0&page_size=3", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUzMwUjL1MDM14SNzATN", + FuncName: "Members", + GetParams: []Param{ + {Name: `chat_id`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + }, + }, + { + Name: "搜索用户所在的群列表", + Description: `搜索用户所在的群列表。 + +**权限说明** :应用需要“读取群信息”权限; +`, + Request: "GET https://open.feishu.cn/open-apis/chat/v4/search?page_size=10&page_token=24&query=rd", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUTOyUjL1kjM14SN5ITN", + FuncName: "Search", + GetParams: []Param{ + {Name: `page_size`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + {Name: `query`, Type: `string`}, + }, + }, + }, + }, + { + Name: `应用信息/应用管理`, + Package: `appinfo/app_manage`, + Apis: []Api{ + { + Name: "校验应用管理员", + Description: ` +该接口用于查询用户是否为应用管理员,需要申请 校验用户是否为应用管理员 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/application/v3/is_user_admin", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uITN1EjLyUTNx4iM1UTM", + FuncName: "IsUserAdmin", + }, + { + Name: "获取应用管理员管理范围", + Description: `该接口用于获取应用管理员的管理范围,即该应用管理员能够管理哪些部门。 + +`, + Request: "GET https://open.feishu.cn/open-apis/contact/v1/user/admin_scope/get?employee_id=2fab1234", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uMzN3QjLzczN04yM3cDN", + FuncName: "AdminScopeGet", + GetParams: []Param{ + {Name: `employee_id`, Type: `string`}, + }, + }, + { + Name: "获取应用在企业内的可用范围", + Description: ` +该接口用于查询应用在该企业内可以被使用的范围,只能被企业自建应用调用且需要“获取应用信息”权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/application/v2/app/visibility?app_id=cli_9db45f86b7799104&user_page_token=0&user_page_size=100", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uIjM3UjLyIzN14iMycTN", + FuncName: "AppVisibility", + GetParams: []Param{ + {Name: `app_id`, Type: `string`}, + {Name: `user_page_token`, Type: `string`}, + {Name: `user_page_size`, Type: `string`}, + }, + }, + { + Name: "获取用户可用的应用", + Description: ` +该接口用于查询用户可用的应用列表,只能被企业自建应用调用且需要“获取用户可用的应用”权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/application/v1/user/visible_apps?user_id=79affdge&page_token=0&lang=zh_cn&page_size=5", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uMjM3UjLzIzN14yMycTN", + FuncName: "VisibleApps", + GetParams: []Param{ + {Name: `page_size`, Type: `string`}, + {Name: `user_id`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + {Name: `lang`, Type: `string`}, + }, + }, + { + Name: "获取企业安装的应用", + Description: ` +该接口用于查询企业安装的应用列表,只能被企业自建应用调用且需要“获取应用信息”权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/application/v3/app/list?page_size=5&page_token=0&lang=zh_cn&status=1", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uYDN3UjL2QzN14iN0cTN", + FuncName: "AppList", + GetParams: []Param{ + {Name: `lang`, Type: `string`}, + {Name: `status`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + }, + }, + { + Name: "更新应用可用范围", + Description: ` +该接口用于增加或者删除指定应用被哪些人可用,只能被企业自建应用调用且需要“管理应用可见范围”权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/application/v3/app/update_visibility", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ucDN3UjL3QzN14yN0cTN", + FuncName: "UpdateVisibility", + }, + { + Name: "查询应用管理员列表", + Description: `查询应用管理员列表,返回应用的最新10个管理员账户id列表。 + +**权限说明** : +本接口需要申请 获取应用管理员ID 权限才能访问。 +回包数据中的user_id 需要申请 获取用户 userid 权限才会返回 +回包数据中的union_id 需要申请 获取用户统一ID 权限才会返回 +`, + Request: "GET https://open.feishu.cn/open-apis/user/v4/app_admin_user/list", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ucDOwYjL3gDM24yN4AjN", + FuncName: "AppAdminUserList", + }, + }, + }, + { + Name: `应用信息/应用商店`, + Package: `appinfo/appstore`, + Apis: []Api{ + { + Name: "查询租户购买的付费方案", + Description: `该接口用于分页查询应用租户下的已付费订单,每次购买对应一个唯一的订单,订单会记录购买的套餐的相关信息,业务方需要自行处理套餐的有效期和付费方案的升级。 +`, + Request: "GET https://open.feishu.cn/open-apis/pay/v1/order/list?status=all&page_size=10&page_token=10&tenant_key=2e5c3a3ae38f175f", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uETNwUjLxUDM14SM1ATN", + FuncName: "OrderList", + GetParams: []Param{ + {Name: `status`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + {Name: `tenant_key`, Type: `string`}, + }, + }, + { + Name: "查询订单详情", + Description: `该接口用于查询某个订单的具体信息 +`, + Request: "GET https://open.feishu.cn/open-apis/pay/v1/order/get?order_id=6708978506916697671", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uITNwUjLyUDM14iM1ATN", + FuncName: "OrderGet", + GetParams: []Param{ + {Name: `order_id`, Type: `string`}, + }, + }, + }, + }, + { + Name: `机器人/群信息和群管理`, + Package: `bot/group_manage`, + Apis: []Api{ + { + Name: "创建群", + Description: `机器人创建群并拉指定用户进群。 + +**权限说明** :需要启用机器人能力 +`, + Request: "POST https://open.feishu.cn/open-apis/chat/v4/create/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ukDO5QjL5gTO04SO4kDN", + FuncName: "ChatCreate", + }, + { + Name: "获取群列表", + Description: `获取机器人所在的群列表。 + +**权限说明** :需要启用机器人能力 +`, + Request: "GET https://open.feishu.cn/open-apis/chat/v4/list", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uITO5QjLykTO04iM5kDN", + FuncName: "ChatList", + }, + { + Name: "获取群信息", + Description: `获取群名称、群主 ID、成员列表 ID 等群基本信息。 + +**权限说明** :需要启用机器人能力;机器人必须在群里 +`, + Request: "GET https://open.feishu.cn/open-apis/chat/v4/info?chat_id=oc_eb9e82d5657777ebf1bb5b9024f549ef", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uMTO5QjLzkTO04yM5kDN", + FuncName: "ChatInfo", + GetParams: []Param{ + {Name: `chat_id`, Type: `string`}, + }, + }, + { + Name: "更新群信息", + Description: `更新群名称、群配置、转让群主等。 + +**权限说明** :需要启用机器人能力;机器人必须是群主,才能执行修改群配置和转让群主的操作(机器人创建的群,机器人默认是群主。) +`, + Request: "POST https://open.feishu.cn/open-apis/chat/v4/update/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uYTO5QjL2kTO04iN5kDN", + FuncName: "ChatUpdate", + }, + { + Name: "拉用户进群", + Description: `机器人拉用户进群,机器人必须在群里。 + +**权限说明** :需要启用机器人能力;机器人必须在群里 +`, + Request: "POST https://open.feishu.cn/open-apis/chat/v4/chatter/add/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ucTO5QjL3kTO04yN5kDN", + FuncName: "ChatterAdd", + }, + { + Name: "移除用户出群", + Description: `机器人移除用户出群。 + +**权限说明** :需要启用机器人能力;机器人必须是群主(机器人创建的群,机器人默认是群主。) +`, + Request: "POST https://open.feishu.cn/open-apis/chat/v4/chatter/delete/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uADMwUjLwADM14CMwATN", + FuncName: "ChatterDelete", + }, + { + Name: "解散群", + Description: `机器人解散群。 + +**权限说明** :需要启用机器人能力;机器人必须是群主(机器人创建的群,机器人默认是群主。) +`, + Request: "POST https://open.feishu.cn/open-apis/chat/v4/disband", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUDN5QjL1QTO04SN0kDN", + FuncName: "Disband", + }, + }, + }, + { + Name: `机器人/机器人信息和管理`, + Package: `bot/bot_manage`, + Apis: []Api{ + { + Name: "获取机器人信息", + Description: `获取机器人的基本信息 + +**权限说明** :需要启用机器人能力 +`, + Request: "GET https://open.feishu.cn/open-apis/bot/v3/info/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uAjMxEjLwITMx4CMyETM", + FuncName: "Info", + }, + { + Name: "拉机器人进群", + Description: `拉机器人进群 + +**权限说明** :需要启用机器人能力;机器人的owner需要已经在群里 +`, + Request: "POST https://open.feishu.cn/open-apis/bot/v4/add", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uYDO04iN4QjL2gDN", + FuncName: "Add", + }, + { + Name: "将机器人移出群", + Description: `将机器人移出群。 + +**权限说明** :需要启用机器人能力 +`, + Request: "POST https://open.feishu.cn/open-apis/bot/v4/remove", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ucDO04yN4QjL3gDN", + FuncName: "Remove", + }, + }, + }, + { + Name: `消息`, + Package: `message`, + Apis: []Api{ + { + Name: "批量发送消息", + Description: ` +给多个用户或者多个部门发送消息。 + +**权限说明** :需要启用机器人能力;机器人需要拥有批量发送消息权限;机器人需要拥有对用户或部门的可见性 + +`, + Request: "POST https://open.feishu.cn/open-apis/message/v4/batch_send/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ucDO1EjL3gTNx4yN4UTM", + FuncName: "BatchSend", + }, + { + Name: "发送文本消息", + Description: ` +给指定用户或者会话发送文本消息,其中会话包括私聊会话和群会话。 + +**权限说明** :需要启用机器人能力;私聊会话时机器人需要拥有对用户的可见性,群会话需要机器人在群里 +`, + Request: "POST https://open.feishu.cn/open-apis/message/v4/send/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uUjNz4SN2MjL1YzM", + FuncName: "Send", + }, + { + Name: "查询消息已读状态", + Description: ` +查询消息已读状态,只能查询最近七天机器人自身发送消息的已读信息。 + +**权限说明** :需要启用机器人能力 +`, + Request: "POST https://open.feishu.cn/open-apis/message/v4/read_info/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/ukTM2UjL5EjN14SOxYTN", + FuncName: "ReadInfo", + }, + { + Name: "上传图片", + Description: ` +上传图片,获取图片的 image_key。 + +**权限说明** :需要启用机器人能力 +`, + Request: "POST https://open.feishu.cn/open-apis/image/v4/put/", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uEDO04SM4QjLxgDN", + FuncName: "ImagePut", + }, + { + Name: "获取图片", + Description: ` +根据图片的image_key获取图片内容 + +**权限说明** :需要启用机器人能力;机器人只能获取自己上传或者收到推送的图片 +`, + Request: "GET https://open.feishu.cn/open-apis/image/v4/get?image_key=24383920-9321-4ecd-8b33-bf8ce74e84c8", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uYzN5QjL2cTO04iN3kDN", + FuncName: "ImageGet", + GetParams: []Param{ + {Name: `image_key`, Type: `string`}, + }, + }, + { + Name: "获取文件", + Description: ` +根据文件的 file_key 拉取文件内容,当前仅可用来获取用户与机器人单聊发送的文件 + +**权限说明** :需要启用机器人能力;机器人只能获取自己上传的文件 +`, + Request: "GET https://open.feishu.cn/open-apis/open-file/v1/get?file_key=file_36r377cb-c6h2-4b6d-ag67-0ac3e796008g", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uMDN4UjLzQDO14yM0gTN", + FuncName: "FileGet", + GetParams: []Param{ + {Name: `file_key`, Type: `string`}, + }, + }, + { + Name: "应用发送通知给用户", + Description: `给指定用户发送应用通知 + +**权限说明** :需要申请 以应用的身份发消息 的权限;同时需要启用小程序能力,并且支持**PC模式**。 +`, + Request: "POST https://open.feishu.cn/open-apis/notify/v4/appnotify", + See: "https://open.feishu.cn/document/ukTMukTMukTM/uATOyUjLwkjM14CM5ITN", + FuncName: "AppNotify", + }, + }, + }, +} diff --git a/cmd/apiconfig2.go b/cmd/apiconfig2.go new file mode 100644 index 0000000..c29a47d --- /dev/null +++ b/cmd/apiconfig2.go @@ -0,0 +1,945 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +var apiConfig2 = []ApiGroup{ + { + Name: `日历`, + Package: `capabilities/calendar`, + Apis: []Api{ + { + Name: "获取日历", + Description: ` +该接口用于根据日历 ID 获取日历信息。 +`, + Request: "GET https://open.feishu.cn/open-apis/calendar/v3/calendar_list/:calendarId", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMDN04yM0QjLzQDN", + FuncName: "GetCalendarById", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取日历列表", + Description: ` +该接口用于获取应用在企业内的日历列表。 +`, + Request: "GET https://open.feishu.cn/open-apis/calendar/v3/calendar_list", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMTM14yMxUjLzETN", + FuncName: "CalendarList", + }, + { + Name: "创建日历", + Description: ` +该接口用于为应用在企业内创建一个日历。 +`, + Request: "POST https://open.feishu.cn/open-apis/calendar/v3/calendars", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTM14CNxUjL0ETN", + FuncName: "CreateCalendars", + }, + { + Name: "删除日历", + Description: ` +该接口用于删除应用在企业内的指定日历。 +`, + Request: "DELETE https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUTM14SNxUjL1ETN", + FuncName: "DeleteCalendarById", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取日程", + Description: ` +该接口用于获取指定日历下的指定日程。 +`, + Request: "GET https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/events/:eventId", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucTM14yNxUjL3ETN", + FuncName: "GetEventById", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "创建日程", + Description: ` +该接口用于在日历中创建一个日程。 +`, + Request: "POST https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/events", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugTM14COxUjL4ETN", + FuncName: "CreateEvent", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "邀请/移除日程参与者", + Description: ` +邀请一个或多个用户加入日程; +从日程移除一个或多个用户。 +`, + Request: "POST https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/events/:eventId/attendees", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIjM14iMyUjLyITN", + FuncName: "Attendees", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取访问控制列表", + Description: ` +该接口用于查看指定日历的成员列表。 +`, + Request: "GET https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/acl", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjM14yMyUjLzITN", + FuncName: "Acl", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "删除访问控制", + Description: ` +该接口用于从日历中移除一个用户。 +`, + Request: "DELETE https://open.feishu.cn/open-apis/calendar/v3/calendars/:calendarId/acl/:ruleId", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUjM14SNyUjL1ITN", + FuncName: "DeleteAclByRuleId", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "查询日历的忙闲状态", + Description: ` +该接口用于查询指定日历或用户主日历的忙闲状态。 +`, + Request: "POST https://open.feishu.cn/open-apis/calendar/v3/freebusy/query", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYjM14iNyUjL2ITN", + FuncName: "FreeBusyQuery", + }, + { + Name: "查询公共日历", + Description: ` +该接口用于通过关键字查询公共日历信息。 +`, + Request: "GET https://open.feishu.cn/open-apis/calendar/v3/shared_calendar_list/shared_calendar/query?query=ByteDance", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukDMwYjL5ADM24SOwAjN", + FuncName: "SharedCalendarQuery", + GetParams: []Param{ + {Name: `query`, Type: `string`}, + }, + }, + { + Name: "获取公共日历日程列表", + Description: ` +该接口用于获取公共日历下的日程列表。 +`, + Request: "GET https://open.feishu.cn/open-apis/calendar/v3/shared/calendars/:calendarId/events", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIzNwYjLycDM24iM3AjN", + FuncName: "SharedCalendarEvents", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + }, + }, + { + Name: `云文档`, + Package: `capabilities/document`, + Apis: []Api{ + + { + Name: "新建文件夹", + Description: ` + +该接口用于根据 folderToken 在该 folder 下创建文件夹 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/explorer/v2/folder/:folderToken", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTNzUjL5UzM14SO1MTN", + FuncName: "CreateFolder", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取文件夹元信息", + Description: ` +# 获取root folder(我的空间) meta该接口用于获取 我的空间 的元信息 +`, + Request: "GET https://open.feishu.cn/open-apis/drive/explorer/v2/root_folder/meta", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uAjNzUjLwYzM14CM2MTN", + FuncName: "RootFolderMeta", + AccessToken: "user", + }, + { + Name: "获取文件夹下的文档清单", + Description: ` + +该接口用于根据 folderToken 获取该文件夹的文档清单,如 doc、sheet、folder +`, + Request: "GET https://open.feishu.cn/open-apis/drive/explorer/v2/folder/:folderToken/children", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEjNzUjLxYzM14SM2MTN", + FuncName: "FolderChildren", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "新建文档", + Description: ` +该接口用于根据 folderToken 创建 Doc或 Sheet 。 + +若没有特定的文件夹用于承载创建的文档,可以先调用「获取文件夹元信息」文档中的「获取 root folder (我的空间) meta」接口,获得我的空间的 token,然后再使用此接口。创建的文档将会在「我的空间」的「归我所有」列表里。 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/explorer/v2/file/:folderToken", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTNzUjL0UzM14CN1MTN", + FuncName: "CreateFile", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "复制文档", + Description: ` + +该接口用于根据文件 token 复制 Doc 或 Sheet 到目标文件夹中。 + +若没有特定的文件夹用于承载创建的文档,可以先调用「获取文件夹元信息」文档中的「获取 root folder (我的空间) meta」接口,获得我的空间的 token,然后再使用此接口。复制的文档将会在「我的空间」的「归我所有」列表里。 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/explorer/v2/file/copy/files/:fileToken", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTNzUjL2UzM14iN1MTN", + FuncName: "CopyFile", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "删除文档", + Description: `Doc + +该接口用于根据 spreadsheetToken 删除对应的 sheet 文档。 +`, + Request: "DELETE https://open.feishu.cn/open-apis/drive/explorer/v2/file/spreadsheets/:spreadsheetToken", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATM2UjLwEjN14CMxYTN", + FuncName: "DeleteFile", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "增加权限", + Description: ` + +该接口用于根据 filetoken 给用户增加文档的权限 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/permission/member/create", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMzNzUjLzczM14yM3MTN", + FuncName: "PermissionMemberCreate", + AccessToken: "user", + }, + { + Name: "转移拥有者", + Description: ` + +该接口用于根据文档信息和用户信息转移文档的所有者。 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/permission/member/transfer", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQzNzUjL0czM14CN3MTN", + FuncName: "PermissionMemberTransfer", + AccessToken: "user", + }, + { + Name: "更新文档公共设置", + Description: ` + +该接口用于根据 filetoken 更新文档的公共设置 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/permission/public/update", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTM3UjL5EzN14SOxcTN", + FuncName: "PermissionPublicUpdate", + AccessToken: "user", + }, + { + Name: "获取协作者列表", + Description: ` + +该接口用于根据 filetoken 查询协作者,目前包括人('user')和群('chat') + +**你能获取到协作者列表的前提是你对该文档有权限** +`, + Request: "POST https://open.feishu.cn/open-apis/drive/permission/member/list", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATN3UjLwUzN14CM1cTN", + FuncName: "PermissionMemberList", + AccessToken: "user", + }, + { + Name: "移除协作者权限", + Description: ` + +该接口用于根据 filetoken 移除文档协作者的权限 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/permission/member/delete", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTN3UjL2UzN14iN1cTN", + FuncName: "PermissionMemberDelete", + AccessToken: "user", + }, + { + Name: "更新协作者权限", + Description: ` + +该接口用于根据 filetoken 更新文档协作者的权限 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/permission/member/update", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucTN3UjL3UzN14yN1cTN", + FuncName: "PermissionMemberUpdate", + AccessToken: "user", + }, + { + Name: "判断协作者是否有某权限", + Description: ` + +该接口用于根据 filetoken 判断当前登录用户是否具有某权限 +`, + Request: "POST https://open.feishu.cn/open-apis/drive/permission/member/permitted", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzN3UjL2czN14iN3cTN", + FuncName: "PermissionMemberPermitted", + AccessToken: "user", + }, + { + Name: "获取文档文本内容", + Description: ` +该接口用于获取文档的纯文本内容,不包含富文本格式信息,主要用于搜索,如导入 es 等。 +`, + Request: "GET https://open.feishu.cn/open-apis/doc/v2/:docToken/raw_content", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukzNzUjL5czM14SO3MTN", + FuncName: "RawContent", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取sheet@doc的元数据", + Description: `sheet@doc 的元数据 + +该接口用于根据 docToken 获取 sheet@doc 的元数据。 +`, + Request: "GET https://open.feishu.cn/open-apis/doc/v2/:docToken/sheet_meta", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOzUjLwgzM14CM4MTN", + FuncName: "SheetMeta", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取文档元信息", + Description: ` +该接口用于根据 docToken 获取元数据。 +`, + Request: "GET https://open.feishu.cn/open-apis/doc/v2/meta/:docToken", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uczN3UjL3czN14yN3cTN", + FuncName: "DocMeta", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取表格元数据", + Description: ` +该接口用于根据 spreadsheetToken 获取表格元数据。 +`, + Request: "GET https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/metainfo", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uETMzUjLxEzM14SMxMTN", + FuncName: "SpreadSheetsMetainfo", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "操作子表", + Description: ` + +该接口用于根据 spreadsheetToken 操作表格,如增加sheet,复制sheet、删除sheet。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/sheets_batch_update", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTMzUjL2EzM14iNxMTN", + FuncName: "SheetsBatchUpdate", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "插入数据", + Description: ` + +该接口用于根据 spreadsheetToken 和 range 向范围之前增加相应数据的行和相应的数据,相当于数组的插入操作;单次写入不超过5000行,100列,每个格子大小为0.5M。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_prepend", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIjMzUjLyIzM14iMyMTN", + FuncName: "SpreadSheetsValuesPrepend", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "追加数据", + Description: ` + +该接口用于根据 spreadsheetToken 和 range 遇到空行则进行覆盖追加或新增行追加数据。 空行:默认该行第一个格子是空,则认为是空行;单次写入不超过5000行,100列,每个格子大小为0.5M。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_append", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjMzUjLzIzM14yMyMTN", + FuncName: "SpreadSheetsValuesAppend", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "插入行列", + Description: ` + +该接口用于根据 spreadsheetToken 和维度信息 插入空行/列 。如 startIndex=3, endIndex=7,则从第 4 行开始开始插入行列,一直到第 7 行,共插入 4 行;单次操作不超过5000行或列。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/insert_dimension_range", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQjMzUjL0IzM14CNyMTN", + FuncName: "SpreadSheetsInsertDimensionRange", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "增加行列", + Description: ` + +该接口用于根据 spreadsheetToken 和长度,在末尾增加空行/列;单次操作不超过5000行或列。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/dimension_range", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUjMzUjL1IzM14SNyMTN", + FuncName: "SpreadSheetsDimensionRange", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "增加锁定单元格", + Description: ` + +该接口用于根据 spreadsheetToken 和维度信息增加多个范围的锁定单元格;单次操作不超过5000行或列。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/protected_dimension", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDNzUjL4QzM14CO0MTN", + FuncName: "SpreadSheetsProtectedDimension", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "合并单元格", + Description: ` + +该接口用于根据 spreadsheetToken 和维度信息合并单元格;单次操作不超过5000行,100列。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/merge_cells", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukDNzUjL5QzM14SO0MTN", + FuncName: "SpreadSheetsMergeCells", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "拆分单元格", + Description: ` + +该接口用于根据 spreadsheetToken 和维度信息拆分单元格;单次操作不超过5000行,100列。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/unmerge_cells", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATNzUjLwUzM14CM1MTN", + FuncName: "SpreadSheetsUnmergeCells", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "读取单个范围", + Description: ` + +该接口用于根据 spreadsheetToken 和 range 读取表格单个范围的值,返回数据限制为10M。 +`, + Request: "GET https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values/:range", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugTMzUjL4EzM14COxMTN", + FuncName: "SpreadSheetsRange", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "读取多个范围", + Description: ` + +该接口用于根据 spreadsheetToken 和 ranges 读取表格多个范围的值,返回数据限制为10M。 +`, + Request: "GET https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_get", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTMzUjL5EzM14SOxMTN", + FuncName: "SpreadSheetsValuesBatchGet", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "向多个范围写入数据", + Description: ` + +该接口用于根据 spreadsheetToken 和 range 向多个范围写入数据,若范围内有数据,将被更新覆盖;单次写入不超过5000行,100列,每个格子大小为0.5M。 +`, + Request: "POST https://open.feishu.cn/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_update", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEjMzUjLxIzM14SMyMTN", + FuncName: "SpreadSheetsValuesBatchUpdate", + AccessToken: "user", + PathParams: []Param{ + {Name: "", Type: "string"}, + }, + }, + { + Name: "获取元数据", + Description: ` + +该接口用于根据 token 获取各类文件的元数据 +`, + Request: "POST https://open.feishu.cn/open-apis/suite/docs-api/meta", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjN3UjLzYzN14yM2cTN", + FuncName: "DocsMeta", + AccessToken: "user", + }, + { + Name: "文档搜索", + Description: ` + +该接口用于根据搜索条件进行文档搜索 +`, + Request: "POST https://open.feishu.cn/open-apis/suite/docs-api/search/object", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDM4UjL4ADO14COwgTN", + FuncName: "SearchObject", + AccessToken: "user", + }, + { + Name: "添加全文评论", + Description: ` + +该接口用于根据 filetoken 给文档添加全文评论 +`, + Request: "POST https://open.feishu.cn/open-apis/comment/add_whole", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucDN4UjL3QDO14yN0gTN", + FuncName: "CommentAddWhole", + AccessToken: "user", + }, + }, + }, + { + Name: `审批`, + Package: `capabilities/approve`, + Apis: []Api{ + + { + Name: "查看审批定义", + Description: ` +根据 Approval Code 获取某个审批定义的详情,用于构造创建审批实例的请求。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/approval/get", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADNyUjLwQjM14CM0ITN", + FuncName: "Get", + }, + { + Name: "批量获取审批实例ID", + Description: ` +根据 approval_code 批量获取审批实例的 instance_code,用于拉取租户下某个审批定义的全部审批实例。 +默认以审批创建时间排序。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/list", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQDOyUjL0gjM14CN4ITN", + FuncName: "InstanceList", + }, + { + Name: "获取单个审批实例详情", + Description: ` +通过审批实例 Instance Code 获取审批实例详情。Instance Code 由 [批量获取审批实例](https://open.feishu.cn/document/ukTMukTMukTM/uQDOyUjL0gjM14CN4ITN) 接口获取。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/get", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEDNyUjLxQjM14SM0ITN", + FuncName: "InstanceGet", + }, + { + Name: "创建审批实例", + Description: ` +创建一个审批实例,调用方需对审批定义的表单有详细了解,将按照定义的表单结构,将表单 Value 通过接口传入。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/create", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIDNyUjLyQjM14iM0ITN", + FuncName: "InstanceCreate", + }, + { + Name: "审批任务同意", + Description: ` +对于单个审批任务进行同意操作。同意后审批流程会流转到下一个审批人。 + + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/approve", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMDNyUjLzQjM14yM0ITN", + FuncName: "Approve", + }, + { + Name: "审批任务拒绝", + Description: ` +对于单个审批任务进行拒绝操作。拒绝后审批流程结束。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/reject", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQDNyUjL0QjM14CN0ITN", + FuncName: "Reject", + }, + { + Name: "审批任务转交", + Description: ` +对于单个审批任务进行转交操作。转交后审批流程流转给被转交人。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/transfer", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUDNyUjL1QjM14SN0ITN", + FuncName: "Transfer", + }, + { + Name: "审批实例撤回", + Description: ` +对于单个审批实例进行撤销操作。撤销后审批流程结束。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/cancel", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYDNyUjL2QjM14iN0ITN", + FuncName: "Cancel", + }, + { + Name: "上传文件", + Description: ` +当审批表单中有图片或附件控件时,开发者需在创建审批实例前通过审批上传文件接口将文件上传到审批系统。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/file/upload", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUDOyUjL1gjM14SN4ITN", + FuncName: "Upload", + }, + { + Name: "三方审批实例同步", + Description: ` +用于第三方审批的实例同步。 +使用前需确保已经在审批后台创建第三方审批。 + +> 审批实例:员工发起审批时产生的对象,详情参见 [开发指南](/ssl:ttdoc/ugTM5UjL4ETO14COxkTN/ukDNyUjL5QjM14SO0ITN) +> +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/external/instance/create", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uczM3UjL3MzN14yNzcTN", + FuncName: "ExternalInstanceCreate", + }, + { + Name: "创建审批定义", + Description: ` +用于通过接口创建简单的审批定义,可以灵活指定定义的基础信息、表单和流程等。不推荐企业自建应用使用,如有需要尽量联系管理员在审批管理后台创建定义。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/approval/create", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUzNyYjL1cjM24SN3IjN", + FuncName: "Create", + }, + { + Name: "审批实例抄送", + Description: ` +通过接口可以将当前审批实例抄送给其他人。 + +**权限说明** :需要获取 访问审批应用 权限。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/instance/cc", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOzYjLwgzM24CM4MjN", + FuncName: "InstanceCc", + }, + { + Name: "订阅审批事件", + Description: ` +订阅 approval_code 后,可以收到该审批定义对应实例的事件通知。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/subscription/subscribe", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucDOyUjL3gjM14yN4ITN", + FuncName: "Subscribe", + }, + { + Name: "取消订阅审批事件", + Description: ` +取消订阅 approval_code 后,无法再收到该审批定义对应实例的事件通知。 +`, + Request: "POST https://www.feishu.cn/approval/openapi/v2/subscription/unsubscribe", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDOyUjL4gjM14CO4ITN", + FuncName: "Unsubscribe", + }, + }, + }, + { + Name: `会议室`, + Package: `capabilities/meeting`, + Apis: []Api{ + + { + Name: "获取建筑物列表", + Description: ` +该接口用于获取本企业下的建筑物(办公大楼)。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/building/list?page_size=1&page_token=0&order_by=name-asc&fields=*", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugzNyUjL4cjM14CO3ITN", + FuncName: "BuildingList", + GetParams: []Param{ + {Name: `page_size`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + {Name: `order_by`, Type: `string`}, + {Name: `fields`, Type: `string`}, + }, + }, + { + Name: "查询建筑物详情", + Description: ` +该接口用于获取指定建筑物的详细信息。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/building/batch_get?building_ids=omb_8ec170b937536a5d87c23b418b83f9bb&building_ids=omb_38570e4f0fd9ecf15030d3cc8b388f3a&fields=*", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukzNyUjL5cjM14SO3ITN", + FuncName: "BuildingBatchGet", + GetParams: []Param{ + {Name: `building_ids`, Type: `string`}, + {Name: `fields`, Type: `string`}, + }, + }, + { + Name: "获取会议室列表", + Description: ` +该接口用于获取指定建筑下的会议室。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/room/list?building_id=omb_8ec170b937536a5d87c23b418b83f9bb&page_size=1&page_token=0&order_by=name-asc&fields=*", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOyUjLwgjM14CM4ITN", + FuncName: "MeetingRoomList", + GetParams: []Param{ + {Name: `building_id`, Type: `string`}, + {Name: `page_size`, Type: `string`}, + {Name: `page_token`, Type: `string`}, + {Name: `order_by`, Type: `string`}, + {Name: `fields`, Type: `string`}, + }, + }, + { + Name: "查询会议室详情", + Description: ` +该接口用于获取指定会议室的详细信息。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/room/batch_get?room_ids=omm_eada1d61a550955240c28757e7dec3af&room_ids=omm_83d09ad4f6896e02029a6a075f71c9d1&fields=*", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEDOyUjLxgjM14SM4ITN", + FuncName: "MeetingRoomBatchGet", + GetParams: []Param{ + {Name: `room_ids`, Type: `string`}, + {Name: `fields`, Type: `string`}, + }, + }, + { + Name: "会议室忙闲查询", + Description: ` +该接口用于获取指定会议室的忙闲日程实例列表。非重复日程只有唯一实例;重复日程可能存在多个实例,依据重复规则和时间范围扩展。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/freebusy/batch_get?room_ids=omm_83d09ad4f6896e02029a6a075f71c9d1&room_ids=omm_eada1d61a550955240c28757e7dec3af&time_min=2019-09-04T08:45:00%2B08:00&time_max=2019-09-04T09:45:00%2B08:00", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIDOyUjLygjM14iM4ITN", + FuncName: "MeetingRoomFreeBusyBatchGet", + GetParams: []Param{ + {Name: `room_ids`, Type: `string`}, + {Name: `time_min`, Type: `string`}, + {Name: `time_max`, Type: `string`}, + }, + }, + { + Name: "回复会议室日程实例", + Description: ` +该接口用于回复会议室日程实例,包括未签到释放和提前结束释放。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/meeting_room/instance/reply", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzN4UjL2cDO14iN3gTN", + FuncName: "InstanceReply", + }, + { + Name: "创建建筑物", + Description: ` +该接口对应管理后台的添加建筑,添加楼层的功能,可用于创建建筑物和建筑物的楼层信息。 + +**权限说明** :需要获取 管理会议室信息 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/meeting_room/building/create", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATNwYjLwUDM24CM1AjN", + FuncName: "BuildingCreate", + }, + { + Name: "更新建筑物", + Description: ` +该接口用于编辑建筑信息,添加楼层,删除楼层,编辑楼层信息。 + +**权限说明** :需要获取 管理会议室信息 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/meeting_room/building/update", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uETNwYjLxUDM24SM1AjN", + FuncName: "BuildingUpdate", + }, + { + Name: "删除建筑物", + Description: ` +该接口用于删除建筑物(办公大楼)。 + +**权限说明** :需要获取 管理会议室信息 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/meeting_room/building/delete", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMzMxYjLzMTM24yMzEjN", + FuncName: "BuildingDelete", + }, + { + Name: "查询建筑物ID", + Description: ` +该接口用于根据租户自定义建筑 ID 查询建筑 ID。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/building/batch_get_id?custom_building_ids=test01&custom_building_ids=test02", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQzMxYjL0MTM24CNzEjN", + FuncName: "BuildingBatchGetById", + GetParams: []Param{ + {Name: `custom_building_ids`, Type: `string`}, + }, + }, + { + Name: "创建会议室", + Description: ` +该接口用于创建会议室。 + +**权限说明** :需要获取 管理会议室信息 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/meeting_room/room/create", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uITNwYjLyUDM24iM1AjN", + FuncName: "MeetingRoomCreate", + }, + { + Name: "更新会议室", + Description: ` +该接口用于更新会议室。 + +**权限说明** :需要获取 管理会议室信息 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/meeting_room/room/update", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMTNwYjLzUDM24yM1AjN", + FuncName: "MeetingRoomUpdate", + }, + { + Name: "删除会议室", + Description: ` +该接口用于删除会议室。 + +**权限说明** :需要获取 管理会议室信息 权限。 +`, + Request: "POST https://open.feishu.cn/open-apis/meeting_room/room/delete", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUzMxYjL1MTM24SNzEjN", + FuncName: "MeetingRoomDelete", + }, + { + Name: "查询会议室ID", + Description: ` +该接口用于根据租户自定义会议室ID查询会议室ID。 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/room/batch_get_id?custom_room_ids=test01&custom_room_ids=test02", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzMxYjL2MTM24iNzEjN", + FuncName: "MeetingRoomBatchGetById", + GetParams: []Param{ + {Name: `custom_room_ids`, Type: `string`}, + }, + }, + { + Name: "获取国家地区列表", + Description: ` +新建建筑时需要标明所处国家/地区,该接口用于获得系统预先提供的可供选择的国家 /地区列表 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/country/list", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTNwYjL0UDM24CN1AjN", + FuncName: "CountryList", + }, + { + Name: "获取城市列表", + Description: ` +新建建筑时需要选择所处国家/地区,该接口用于获得系统预先提供的可供选择的城市列表 + +**权限说明** :需要获取 获取会议室信息 权限。 +`, + Request: "GET https://open.feishu.cn/open-apis/meeting_room/district/list?country_id=1814991", + See: "https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUTNwYjL1UDM24SN1AjN", + FuncName: "DistrictList", + GetParams: []Param{ + {Name: `country_id`, Type: `string`}, + }, + }, + }, + }, +} diff --git a/cmd/build.go b/cmd/build.go new file mode 100644 index 0000000..031f1bf --- /dev/null +++ b/cmd/build.go @@ -0,0 +1,370 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "net/url" + "os" + "path" + "strings" + + "github.com/iancoleman/strcase" +) + +func main() { + var pkgFlag string + flag.StringVar(&pkgFlag, "package", "user", "") + flag.Parse() + for _, group := range apiConfig { + if group.Package == "oauth" { // 单独处理 oauth 模块 + continue + } + + if group.Package == pkgFlag { + build(group) + } + } + + if pkgFlag == "apilist" { + apilist() + } + +} + +func apilist() { + for _, group := range apiConfig2 { + fmt.Printf("- %s(%s)\n", group.Name, group.Package) + for _, api := range group.Apis { + split := strings.Split(api.Request, " ") + parse, _ := url.Parse(split[1]) + + if api.FuncName == "" { + api.FuncName = strcase.ToCamel(path.Base(parse.Path)) + } + + godocLink := fmt.Sprintf("https://pkg.go.dev/github.com/fastwego/feishu/apis/%s?tab=doc#%s", group.Package, api.FuncName) + fmt.Printf("\t- [%s](%s) \n\t\t- [%s (%s)](%s)\n", api.Name, api.See, api.FuncName, parse.Path, godocLink) + } + } +} + +func build(group ApiGroup) { + var funcs []string + var consts []string + var testFuncs []string + var exampleFuncs []string + + for _, api := range group.Apis { + tpl := postFuncTpl + _FUNC_NAME_ := "" + _GET_PARAMS_ := "" + _GET_SUFFIX_PARAMS_ := "" + _PAYLOAD_ := "" + switch { + case strings.Contains(api.Request, "GET http"): + tpl = getFuncTpl + case strings.Contains(api.Request, "DELETE http"): + tpl = deleteFuncTpl + case strings.Contains(api.Request, "POST http"): + tpl = postFuncTpl + } + if len(api.GetParams) > 0 || len(api.PathParams) > 0 { + _GET_PARAMS_ = `, params url.Values` + //if strings.Contains(api.Request, "POST") { + // _GET_PARAMS_ = `, ` + _GET_PARAMS_ + //} + if len(api.GetParams) > 0 { + _GET_SUFFIX_PARAMS_ = `+ "?" + params.Encode()` + } + } + + split := strings.Split(api.Request, " ") + parseUrl, _ := url.Parse(split[1]) + + if api.FuncName == "" { + _FUNC_NAME_ = strcase.ToCamel(path.Base(parseUrl.Path)) + } else { + _FUNC_NAME_ = api.FuncName + } + + _APIVARNAME_ := "api" + _FUNC_NAME_ + _HEADER_ := tenantAccessTokenTpl + _HEADERPARAM_ := "" + if api.AccessToken == "app" { + _HEADER_ = appAccessTokenTpl + } else if api.AccessToken == "user" { + _HEADERPARAM_ = ", header http.Header" + _HEADER_ = userAccessTokenTpl + } + + _PATH_PARAMS_ := "" + if strings.Contains(parseUrl.Path, ":") { + _PATH_PARAMS_ = ` + api := api` + _FUNC_NAME_ + ` + for paramName, paramValues := range params { + api = strings.ReplaceAll(api, ":"+paramName, paramValues[0]) + }` + _APIVARNAME_ = "api" + } + + tpl = strings.ReplaceAll(tpl, "_HEADER_", _HEADER_) + tpl = strings.ReplaceAll(tpl, "_HEADERPARAM_", _HEADERPARAM_) + tpl = strings.ReplaceAll(tpl, "_TITLE_", api.Name) + tpl = strings.ReplaceAll(tpl, "_DESCRIPTION_", api.Description) + tpl = strings.ReplaceAll(tpl, "_REQUEST_", api.Request) + tpl = strings.ReplaceAll(tpl, "_SEE_", api.See) + tpl = strings.ReplaceAll(tpl, "_FUNC_NAME_", _FUNC_NAME_) + tpl = strings.ReplaceAll(tpl, "_GET_PARAMS_", _GET_PARAMS_) + tpl = strings.ReplaceAll(tpl, "_GET_SUFFIX_PARAMS_", _GET_SUFFIX_PARAMS_) + tpl = strings.ReplaceAll(tpl, "_PAYLOAD_", _PAYLOAD_) + tpl = strings.ReplaceAll(tpl, "_PATH_PARAMS_", _PATH_PARAMS_) + tpl = strings.ReplaceAll(tpl, "_APIVARNAME_", _APIVARNAME_) + + funcs = append(funcs, tpl) + + tpl = strings.ReplaceAll(constTpl, "_FUNC_NAME_", _FUNC_NAME_) + tpl = strings.ReplaceAll(tpl, "_API_PATH_", parseUrl.Path) + + consts = append(consts, tpl) + + // TestFunc + _TEST_ARGS_STRUCT_ := "" + _EXAMPLE_HEADER_STMT_ := "" + _HEADERINIT_ := "" + switch { + case strings.Contains(api.Request, "GET http") || strings.Contains(api.Request, "DELETE http"): + _TEST_ARGS_STRUCT_ = `ctx *feishu.App, ` + _GET_PARAMS_ + if api.AccessToken == "user" { + _TEST_ARGS_STRUCT_ += `, header http.Header` + _EXAMPLE_HEADER_STMT_ = `header := http.Header{}` + _HEADERINIT_ = `, header: http.Header{}` + } + case strings.Contains(api.Request, "POST http"): + _TEST_ARGS_STRUCT_ = `ctx *feishu.App, payload []byte` + + if _GET_PARAMS_ != "" { + _TEST_ARGS_STRUCT_ += `,` + _GET_PARAMS_ + } + if api.AccessToken == "user" { + _TEST_ARGS_STRUCT_ += `, header http.Header` + _EXAMPLE_HEADER_STMT_ = `header := http.Header{}` + _HEADERINIT_ = `, header: http.Header{}` + } + } + _TEST_ARGS_STRUCT_ = strings.ReplaceAll(_TEST_ARGS_STRUCT_, ",", "\n") + + _TEST_FUNC_SIGNATURE_ := "" + _EXAMPLE_ARGS_STMT_ := "" + + if strings.TrimSpace(_TEST_ARGS_STRUCT_) != "" { + signatures := strings.Split(_TEST_ARGS_STRUCT_, "\n") + paramNames := []string{} + exampleStmt := []string{} + for _, signature := range signatures { + signature = strings.TrimSpace(signature) + tmp := strings.Split(signature, " ") + //fmt.Println(tmp) + if len(tmp[0]) > 0 { + paramNames = append(paramNames, "tt.args."+tmp[0]) + + switch tmp[1] { + case `[]byte`: + exampleStmt = append(exampleStmt, tmp[0]+" := []byte(\"{}\")") + case `string`: + exampleStmt = append(exampleStmt, tmp[0]+" := \"\"") + case `url.Values`: + exampleStmt = append(exampleStmt, tmp[0]+" := url.Values{}") + } + } + } + _TEST_FUNC_SIGNATURE_ = strings.Join(paramNames, ",") + _EXAMPLE_ARGS_STMT_ = strings.Join(exampleStmt, "\n") + } + + tpl = strings.ReplaceAll(testFuncTpl, "_FUNC_NAME_", _FUNC_NAME_) + tpl = strings.ReplaceAll(tpl, "_TEST_ARGS_STRUCT_", _TEST_ARGS_STRUCT_) + tpl = strings.ReplaceAll(tpl, "_TEST_FUNC_SIGNATURE_", _TEST_FUNC_SIGNATURE_) + tpl = strings.ReplaceAll(tpl, "_HEADERINIT_", _HEADERINIT_) + testFuncs = append(testFuncs, tpl) + + //Example + + tpl = strings.ReplaceAll(exampleFuncTpl, "_FUNC_NAME_", _FUNC_NAME_) + tpl = strings.ReplaceAll(tpl, "_PACKAGE_", path.Base(group.Package)) + tpl = strings.ReplaceAll(tpl, "_TEST_FUNC_SIGNATURE_", strings.ReplaceAll(_TEST_FUNC_SIGNATURE_, "tt.args.", "")) + tpl = strings.ReplaceAll(tpl, "_EXAMPLE_ARGS_STMT_", _EXAMPLE_ARGS_STMT_) + tpl = strings.ReplaceAll(tpl, "_EXAMPLE_HEADER_STMT_", _EXAMPLE_HEADER_STMT_) + exampleFuncs = append(exampleFuncs, tpl) + + } + + fileContent := fmt.Sprintf(fileTpl, path.Base(group.Package), group.Name, path.Base(group.Package), strings.Join(consts, ``), strings.Join(funcs, ``)) + filename := "./../apis/" + group.Package + "/" + path.Base(group.Package) + ".go" + _ = os.MkdirAll(path.Dir(filename), 0644) + ioutil.WriteFile(filename, []byte(fileContent), 0644) + + // output Test + testFileContent := fmt.Sprintf(testFileTpl, path.Base(group.Package), strings.Join(testFuncs, ``)) + //fmt.Println(testFileContent) + ioutil.WriteFile("./../apis/"+group.Package+"/"+path.Base(group.Package)+"_test.go", []byte(testFileContent), 0644) + + // output example + exampleFileContent := fmt.Sprintf(exampleFileTpl, path.Base(group.Package), strings.Join(exampleFuncs, ``)) + //fmt.Println(testFileContent) + ioutil.WriteFile("./../apis/"+group.Package+"/example_"+path.Base(group.Package)+"_test.go", []byte(exampleFileContent), 0644) + +} + +var constTpl = ` + api_FUNC_NAME_ = "_API_PATH_"` +var commentTpl = ` +/* +_TITLE_ + +_DESCRIPTION_ + +See: _SEE_ + +_REQUEST_ +*/` +var postFuncTpl = commentTpl + ` +func _FUNC_NAME_(ctx *feishu.App, payload []byte_GET_PARAMS__HEADERPARAM_) (resp []byte, err error) { + _PATH_PARAMS_ + _HEADER_ + return ctx.Client.HTTPPost(_APIVARNAME__GET_SUFFIX_PARAMS_, bytes.NewReader(payload), header) +} +` +var getFuncTpl = commentTpl + ` +func _FUNC_NAME_(ctx *feishu.App_GET_PARAMS__HEADERPARAM_) (resp []byte, err error) { + _PATH_PARAMS_ + _HEADER_ + + return ctx.Client.HTTPGet(_APIVARNAME__GET_SUFFIX_PARAMS_,header) +} +` +var deleteFuncTpl = commentTpl + ` +func _FUNC_NAME_(ctx *feishu.App_GET_PARAMS__HEADERPARAM_) (resp []byte, err error) { + _PATH_PARAMS_ + _HEADER_ + + return ctx.Client.HTTPDelete(_APIVARNAME__GET_SUFFIX_PARAMS_,header) +} +` +var tenantAccessTokenTpl = ` + accessToken, err := ctx.GetTenantAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") +` + +var appAccessTokenTpl = ` + accessToken, err := ctx.GetAppAccessTokenHandler() + if err != nil { + return + } + header := http.Header{} + header.Set("Authorization", "Bearer "+accessToken) + header.Set("Content-appType", "application/json") +` + +var userAccessTokenTpl = ` + header.Set("Content-appType", "application/json") +` + +var fieldTpl = ` + // field + err = m.WriteField("_FIELD_NAME_", string(payload)) + if err != nil { + return + } +` + +var fileTpl = `// Package %s %s +package %s + +const ( + %s +) +%s` + +var testFileTpl = `package %s + +func TestMain(m *testing.M) { + test.Setup() + os.Exit(m.Run()) +} + +%s +` + +var testFuncTpl = ` +func Test_FUNC_NAME_(t *testing.T) { + mockResp := map[string][]byte{ + "case1": []byte("{\"errcode\":0,\"errmsg\":\"ok\"}"), + } + var resp []byte + test.MockSvrHandler.HandleFunc(api_FUNC_NAME_, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(resp)) + }) + + type args struct { + _TEST_ARGS_STRUCT_ + } + tests := []struct { + name string + args args + wantResp []byte + wantErr bool + }{ + {name: "case1", args: args{ctx: test.MockApp_HEADERINIT_}, wantResp: mockResp["case1"], wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp = mockResp[tt.name] + gotResp, err := _FUNC_NAME_(_TEST_FUNC_SIGNATURE_) + //fmt.Println(string(gotResp), err) + if (err != nil) != tt.wantErr { + t.Errorf("_FUNC_NAME_() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResp, tt.wantResp) { + t.Errorf("_FUNC_NAME_() gotResp = %v, want %v", gotResp, tt.wantResp) + } + }) + } +}` + +var exampleFileTpl = `package %s_test + +%s +` +var exampleFuncTpl = ` +func Example_FUNC_NAME_() { + var ctx *feishu.App + + _EXAMPLE_ARGS_STMT_ + _EXAMPLE_HEADER_STMT_ + resp, err := _PACKAGE_._FUNC_NAME_(_TEST_FUNC_SIGNATURE_) + + fmt.Println(resp, err) +} + +` diff --git a/cmd/data/.gitignore b/cmd/data/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/cmd/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/cmd/parseDocLink.go b/cmd/parseDocLink.go new file mode 100644 index 0000000..b4ad597 --- /dev/null +++ b/cmd/parseDocLink.go @@ -0,0 +1,158 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "regexp" + "strings" + + "github.com/iancoleman/strcase" +) + +const ServerUrl = `https://open.feishu.cn` + +var apiUniqMap = map[string]bool{} + +func run() { + + file, err := ioutil.ReadFile("./data/docs2.html") + if err != nil { + fmt.Println(err) + return + } + matched := regexp.MustCompile(`url="(/document/\S+)"`).FindAllStringSubmatch(string(file), -1) + + for _, match := range matched { + //fmt.Println(match) + + docUrl := match[1] + + split := strings.Split(docUrl, "/") + docId := split[3] + + detailDocUrl := fmt.Sprintf("https://open.feishu.cn/opendoc/page/node/detail?id=%s&tab=1", docId) + + resp, err := http.Get(detailDocUrl) + //fmt.Println(detailDocUrl, "---------------") + body, err := ioutil.ReadAll(resp.Body) + + //fmt.Println(string(body)) + + var data = struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Detail struct { + Content string `json:"content"` + Format int `json:"format"` + HasNewLabel bool `json:"has_new_label"` + Hidden int `json:"hidden"` + ID string `json:"id"` + Name string `json:"name"` + NodeType int `json:"node_type"` + ParentID string `json:"parent_id"` + Seq int `json:"seq"` + Status int `json:"status"` + SubName string `json:"sub_name"` + Tab int `json:"tab"` + } `json:"detail"` + } `json:"data"` + }{} + + err = json.Unmarshal(body, &data) + if err != nil { + continue + } + + line := strings.ReplaceAll(data.Data.Detail.Content, "
", "") + line = strings.Trim(line, "\n\r") + + m2 := regexp.MustCompile(`(?s)#\s+\S+\s(.+).+\*\*请求方式.+(GET|POST|DELETE|PATCH).+请求地址.+(https://open\.feishu\.cn/\S+|https://www\.feishu\.cn/\S+)`).FindStringSubmatch(line) + + //fmt.Println(line, "\n", m2) + + if len(m2) != 4 { + continue + } + + apiurl := m2[3] + _DESCRIPTION_ := m2[1] + method := m2[2] + + _REQUEST_ := method + " " + apiurl + + parse, _ := url.Parse(apiurl) + if _, ok := apiUniqMap[parse.Path]; !ok { + apiUniqMap[parse.Path] = true + } else { + continue + } + + _NAME_ := data.Data.Detail.Name + + _SEE_ := ServerUrl + docUrl + _FUNCNAME_ := strcase.ToCamel(path.Base(parse.Path)) + + //GetParams: []Param{ + // {Name: `appid`, appType: `string`}, + // {Name: `redirect_uri`, appType: `string`}, + //}, + + _GET_PARAMS_ := "" + fields := strings.Fields(_REQUEST_) + parse, _ = url.Parse(fields[1]) + uniqParams := map[string]bool{} + for param_name, _ := range parse.Query() { + if uniqParams[param_name] { + continue + } + _GET_PARAMS_ += "\t\t{Name: `" + param_name + "`, Type: `string`},\n" + + uniqParams[param_name] = true + } + if _GET_PARAMS_ != "" { + _GET_PARAMS_ = `GetParams: []Param{ +` + _GET_PARAMS_ + "\t}," + } + + _DESCRIPTION_ = strings.ReplaceAll(_DESCRIPTION_, "\"", "'") + _DESCRIPTION_ = strings.ReplaceAll(_DESCRIPTION_, "`", " ") + + tpl := strings.ReplaceAll(itemTpl, "_NAME_", _NAME_) + tpl = strings.ReplaceAll(tpl, "_DESCRIPTION_", "`"+_DESCRIPTION_+"`") + tpl = strings.ReplaceAll(tpl, "_REQUEST_", _REQUEST_) + tpl = strings.ReplaceAll(tpl, "_SEE_", _SEE_) + tpl = strings.ReplaceAll(tpl, "_FUNCNAME_", _FUNCNAME_) + tpl = strings.ReplaceAll(tpl, "_GET_PARAMS_", _GET_PARAMS_) + + fmt.Println(tpl) + + } +} + +var itemTpl = `{ + Name: "_NAME_", + Description: _DESCRIPTION_, + Request: "_REQUEST_", + See: "_SEE_", + FuncName: "_FUNCNAME_", + _GET_PARAMS_ +},` diff --git a/cmd/parseEvent.go b/cmd/parseEvent.go new file mode 100644 index 0000000..b7e28aa --- /dev/null +++ b/cmd/parseEvent.go @@ -0,0 +1,147 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/fastwego/feishu/types/event_types" + "github.com/iancoleman/strcase" +) + +func parse() { + + file, err := ioutil.ReadFile("./data/docs3.html") + if err != nil { + fmt.Println(err) + return + } + matched := regexp.MustCompile(`url="(/document/\S+)"`).FindAllStringSubmatch(string(file), -1) + + for _, match := range matched { + //fmt.Println(match) + + docUrl := match[1] + + split := strings.Split(docUrl, "/") + docId := split[3] + + detailDocUrl := fmt.Sprintf("https://open.feishu.cn/opendoc/page/node/detail?id=%s&tab=1", docId) + + resp, err := http.Get(detailDocUrl) + //fmt.Println(detailDocUrl, "---------------") + body, err := ioutil.ReadAll(resp.Body) + + //fmt.Println(string(body)) + + var data = struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Detail struct { + Content string `json:"content"` + Format int `json:"format"` + HasNewLabel bool `json:"has_new_label"` + Hidden int `json:"hidden"` + ID string `json:"id"` + Name string `json:"name"` + NodeType int `json:"node_type"` + ParentID string `json:"parent_id"` + Seq int `json:"seq"` + Status int `json:"status"` + SubName string `json:"sub_name"` + Tab int `json:"tab"` + } `json:"detail"` + } `json:"data"` + }{} + + err = json.Unmarshal(body, &data) + if err != nil { + continue + } + + //fmt.Println(data.Data.Detail.Content) + + line := strings.ReplaceAll(data.Data.Detail.Content, "
", "") + line = strings.Trim(line, "\n\r") + + m2 := regexp.MustCompile(`(?sU)\x60\x60\x60json(.+)\x60\x60\x60`).FindAllStringSubmatch(line, -1) + + //fmt.Println(line, "\n", m2) + + for _, m := range m2 { + if len(m) > 1 { + data := m[1] + noComment := regexp.MustCompile(`(?m)//.+$`).ReplaceAllString(data, "") + //fmt.Println(noComment) + + e := event_types.BaseEvent{} + eventType := "" + err := json.Unmarshal([]byte(noComment), &e) + if err != nil { + + submatch := regexp.MustCompile(`(?sU)"event".+"type":\s*"(.+)"`).FindStringSubmatch(noComment) + if len(submatch) > 1 { + eventType = submatch[1] + } else { + continue + } + + } else { + eventType = e.Event.Type + } + + if eventType == "message" { + msgType := "" + submatch := regexp.MustCompile(`(?sU)"event".+"msg_type":\s*"(.+)"`).FindStringSubmatch(noComment) + if len(submatch) > 1 { + msgType = submatch[1] + } else { + continue + } + + fmt.Println(` +const MsgType` + strcase.ToCamel(msgType) + ` = "` + msgType + `" +/* +` + data + ` +*/ + +type EventMessage` + strcase.ToCamel(msgType) + ` struct { +` + noComment + ` +} +`) + } else { + + fmt.Println(` +const EventType` + strcase.ToCamel(eventType) + ` = "` + eventType + `" +/* +` + data + ` +*/ + +type Event` + strcase.ToCamel(eventType) + ` struct { +` + noComment + ` +} +`) + } + } + } + + } +} diff --git a/doc/apilist.md b/doc/apilist.md new file mode 100644 index 0000000..ab76913 --- /dev/null +++ b/doc/apilist.md @@ -0,0 +1,260 @@ +- 用户管理(contact/user) + - [批量获取用户信息](https://open.feishu.cn/document/ukTMukTMukTM/uIzNz4iM3MjLyczM) + - [BatchGet (/open-apis/contact/v1/user/batch_get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#BatchGet) + - [获取部门用户列表](https://open.feishu.cn/document/ukTMukTMukTM/uEzNz4SM3MjLxczM) + - [DepartmentUserList (/open-apis/contact/v1/department/user/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#DepartmentUserList) + - [获取部门用户详情](https://open.feishu.cn/document/ukTMukTMukTM/uYzN3QjL2czN04iN3cDN) + - [DepartmentUserDetailList (/open-apis/contact/v1/department/user/detail/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#DepartmentUserDetailList) + - [新增用户](https://open.feishu.cn/document/ukTMukTMukTM/uMzNz4yM3MjLzczM) + - [Add (/open-apis/contact/v1/user/add)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#Add) + - [删除用户](https://open.feishu.cn/document/ukTMukTMukTM/uUzNz4SN3MjL1czM) + - [Delete (/open-apis/contact/v1/user/delete)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#Delete) + - [更新用户信息](https://open.feishu.cn/document/ukTMukTMukTM/uQzNz4CN3MjL0czM) + - [Update (/open-apis/contact/v1/user/update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#Update) + - [获取角色列表](https://open.feishu.cn/document/ukTMukTMukTM/uYzMwUjL2MDM14iNzATN) + - [RoleList (/open-apis/contact/v2/role/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#RoleList) + - [获取角色成员列表](https://open.feishu.cn/document/ukTMukTMukTM/uczMwUjL3MDM14yNzATN) + - [RoleMembers (/open-apis/contact/v2/role/members)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#RoleMembers) + - [使用手机号或邮箱获取用户 ID](https://open.feishu.cn/document/ukTMukTMukTM/uUzMyUjL1MjM14SNzITN) + - [BatchGetId (/open-apis/user/v1/batch_get_id)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#BatchGetId) + - [使用统一 ID 获取用户 ID](https://open.feishu.cn/document/ukTMukTMukTM/uUTO5UjL1kTO14SN5kTN) + - [UnionIdBatchGetList (/open-apis/user/v1/union_id/batch_get/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#UnionIdBatchGetList) + - [搜索用户](https://open.feishu.cn/document/ukTMukTMukTM/uMTM4UjLzEDO14yMxgTN) + - [Search (/open-apis/search/v1/user)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/user?tab=doc#Search) +- 部门管理(contact/department) + - [获取部门详情](https://open.feishu.cn/document/ukTMukTMukTM/uAzNz4CM3MjLwczM) + - [DepartmentInfoGet (/open-apis/contact/v1/department/info/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/department?tab=doc#DepartmentInfoGet) + - [获取子部门列表](https://open.feishu.cn/document/ukTMukTMukTM/ugzN3QjL4czN04CO3cDN) + - [DepartmentSimpleList (/open-apis/contact/v1/department/simple/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/department?tab=doc#DepartmentSimpleList) + - [批量获取部门详情](https://open.feishu.cn/document/ukTMukTMukTM/uczN3QjL3czN04yN3cDN) + - [DepartmentDetailBatchGet (/open-apis/contact/v1/department/detail/batch_get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/department?tab=doc#DepartmentDetailBatchGet) + - [新增部门](https://open.feishu.cn/document/ukTMukTMukTM/uYzNz4iN3MjL2czM) + - [DepartmentAdd (/open-apis/contact/v1/department/add)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/department?tab=doc#DepartmentAdd) + - [删除部门](https://open.feishu.cn/document/ukTMukTMukTM/ugzNz4CO3MjL4czM) + - [DepartmentDelete (/open-apis/contact/v1/department/delete)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/department?tab=doc#DepartmentDelete) + - [更新部门信息](https://open.feishu.cn/document/ukTMukTMukTM/uczNz4yN3MjL3czM) + - [DepartmentUpdate (/open-apis/contact/v1/department/update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/department?tab=doc#DepartmentUpdate) +- 异步批量接口(contact/async_batch) + - [批量新增部门](https://open.feishu.cn/document/ukTMukTMukTM/uMDOwUjLzgDM14yM4ATN) + - [DepartmentBatchAdd (/open-apis/contact/v2/department/batch_add)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/async_batch?tab=doc#DepartmentBatchAdd) + - [批量新增用户](https://open.feishu.cn/document/ukTMukTMukTM/uIDOwUjLygDM14iM4ATN) + - [UserBatchAdd (/open-apis/contact/v2/user/batch_add)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/async_batch?tab=doc#UserBatchAdd) + - [查询批量任务执行状态](https://open.feishu.cn/document/ukTMukTMukTM/uUDOwUjL1gDM14SN4ATN) + - [TaskGet (/open-apis/contact/v2/task/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact/async_batch?tab=doc#TaskGet) +- 获取企业自定义用户属性配置(contact) + - [获取企业自定义用户属性配置](https://open.feishu.cn/document/ukTMukTMukTM/ucTN3QjL3UzN04yN1cDN) + - [TenantCustomAttrGet (/open-apis/contact/v1/tenant/custom_attr/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/contact?tab=doc#TenantCustomAttrGet) +- 用户群组(user_group) + - [获取用户所在的群列表](https://open.feishu.cn/document/ukTMukTMukTM/uQzMwUjL0MDM14CNzATN) + - [GroupList (/open-apis/user/v4/group_list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/user_group?tab=doc#GroupList) + - [获取群成员列表](https://open.feishu.cn/document/ukTMukTMukTM/uUzMwUjL1MDM14SNzATN) + - [Members (/open-apis/chat/v4/members)](https://pkg.go.dev/github.com/fastwego/feishu/apis/user_group?tab=doc#Members) + - [搜索用户所在的群列表](https://open.feishu.cn/document/ukTMukTMukTM/uUTOyUjL1kjM14SN5ITN) + - [Search (/open-apis/chat/v4/search)](https://pkg.go.dev/github.com/fastwego/feishu/apis/user_group?tab=doc#Search) +- 应用信息/应用管理(appinfo/app_manage) + - [校验应用管理员](https://open.feishu.cn/document/ukTMukTMukTM/uITN1EjLyUTNx4iM1UTM) + - [IsUserAdmin (/open-apis/application/v3/is_user_admin)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/app_manage?tab=doc#IsUserAdmin) + - [获取应用管理员管理范围](https://open.feishu.cn/document/ukTMukTMukTM/uMzN3QjLzczN04yM3cDN) + - [AdminScopeGet (/open-apis/contact/v1/user/admin_scope/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/app_manage?tab=doc#AdminScopeGet) + - [获取应用在企业内的可用范围](https://open.feishu.cn/document/ukTMukTMukTM/uIjM3UjLyIzN14iMycTN) + - [AppVisibility (/open-apis/application/v2/app/visibility)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/app_manage?tab=doc#AppVisibility) + - [获取用户可用的应用](https://open.feishu.cn/document/ukTMukTMukTM/uMjM3UjLzIzN14yMycTN) + - [VisibleApps (/open-apis/application/v1/user/visible_apps)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/app_manage?tab=doc#VisibleApps) + - [获取企业安装的应用](https://open.feishu.cn/document/ukTMukTMukTM/uYDN3UjL2QzN14iN0cTN) + - [AppList (/open-apis/application/v3/app/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/app_manage?tab=doc#AppList) + - [更新应用可用范围](https://open.feishu.cn/document/ukTMukTMukTM/ucDN3UjL3QzN14yN0cTN) + - [UpdateVisibility (/open-apis/application/v3/app/update_visibility)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/app_manage?tab=doc#UpdateVisibility) + - [查询应用管理员列表](https://open.feishu.cn/document/ukTMukTMukTM/ucDOwYjL3gDM24yN4AjN) + - [AppAdminUserList (/open-apis/user/v4/app_admin_user/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/app_manage?tab=doc#AppAdminUserList) +- 应用信息/应用商店(appinfo/appstore) + - [查询租户购买的付费方案](https://open.feishu.cn/document/ukTMukTMukTM/uETNwUjLxUDM14SM1ATN) + - [OrderList (/open-apis/pay/v1/order/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/appstore?tab=doc#OrderList) + - [查询订单详情](https://open.feishu.cn/document/ukTMukTMukTM/uITNwUjLyUDM14iM1ATN) + - [OrderGet (/open-apis/pay/v1/order/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/appinfo/appstore?tab=doc#OrderGet) +- 机器人/群信息和群管理(bot/group_manage) + - [创建群](https://open.feishu.cn/document/ukTMukTMukTM/ukDO5QjL5gTO04SO4kDN) + - [ChatCreate (/open-apis/chat/v4/create/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/group_manage?tab=doc#ChatCreate) + - [获取群列表](https://open.feishu.cn/document/ukTMukTMukTM/uITO5QjLykTO04iM5kDN) + - [ChatList (/open-apis/chat/v4/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/group_manage?tab=doc#ChatList) + - [获取群信息](https://open.feishu.cn/document/ukTMukTMukTM/uMTO5QjLzkTO04yM5kDN) + - [ChatInfo (/open-apis/chat/v4/info)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/group_manage?tab=doc#ChatInfo) + - [更新群信息](https://open.feishu.cn/document/ukTMukTMukTM/uYTO5QjL2kTO04iN5kDN) + - [ChatUpdate (/open-apis/chat/v4/update/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/group_manage?tab=doc#ChatUpdate) + - [拉用户进群](https://open.feishu.cn/document/ukTMukTMukTM/ucTO5QjL3kTO04yN5kDN) + - [ChatterAdd (/open-apis/chat/v4/chatter/add/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/group_manage?tab=doc#ChatterAdd) + - [移除用户出群](https://open.feishu.cn/document/ukTMukTMukTM/uADMwUjLwADM14CMwATN) + - [ChatterDelete (/open-apis/chat/v4/chatter/delete/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/group_manage?tab=doc#ChatterDelete) + - [解散群](https://open.feishu.cn/document/ukTMukTMukTM/uUDN5QjL1QTO04SN0kDN) + - [Disband (/open-apis/chat/v4/disband)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/group_manage?tab=doc#Disband) +- 机器人/机器人信息和管理(bot/bot_manage) + - [获取机器人信息](https://open.feishu.cn/document/ukTMukTMukTM/uAjMxEjLwITMx4CMyETM) + - [Info (/open-apis/bot/v3/info/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/bot_manage?tab=doc#Info) + - [拉机器人进群](https://open.feishu.cn/document/ukTMukTMukTM/uYDO04iN4QjL2gDN) + - [Add (/open-apis/bot/v4/add)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/bot_manage?tab=doc#Add) + - [将机器人移出群](https://open.feishu.cn/document/ukTMukTMukTM/ucDO04yN4QjL3gDN) + - [Remove (/open-apis/bot/v4/remove)](https://pkg.go.dev/github.com/fastwego/feishu/apis/bot/bot_manage?tab=doc#Remove) +- 消息(message) + - [批量发送消息](https://open.feishu.cn/document/ukTMukTMukTM/ucDO1EjL3gTNx4yN4UTM) + - [BatchSend (/open-apis/message/v4/batch_send/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/message?tab=doc#BatchSend) + - [发送文本消息](https://open.feishu.cn/document/ukTMukTMukTM/uUjNz4SN2MjL1YzM) + - [Send (/open-apis/message/v4/send/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/message?tab=doc#Send) + - [查询消息已读状态](https://open.feishu.cn/document/ukTMukTMukTM/ukTM2UjL5EjN14SOxYTN) + - [ReadInfo (/open-apis/message/v4/read_info/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/message?tab=doc#ReadInfo) + - [上传图片](https://open.feishu.cn/document/ukTMukTMukTM/uEDO04SM4QjLxgDN) + - [ImagePut (/open-apis/image/v4/put/)](https://pkg.go.dev/github.com/fastwego/feishu/apis/message?tab=doc#ImagePut) + - [获取图片](https://open.feishu.cn/document/ukTMukTMukTM/uYzN5QjL2cTO04iN3kDN) + - [ImageGet (/open-apis/image/v4/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/message?tab=doc#ImageGet) + - [获取文件](https://open.feishu.cn/document/ukTMukTMukTM/uMDN4UjLzQDO14yM0gTN) + - [FileGet (/open-apis/open-file/v1/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/message?tab=doc#FileGet) + - [应用发送通知给用户](https://open.feishu.cn/document/ukTMukTMukTM/uATOyUjLwkjM14CM5ITN) + - [AppNotify (/open-apis/notify/v4/appnotify)](https://pkg.go.dev/github.com/fastwego/feishu/apis/message?tab=doc#AppNotify) +- 日历(capabilities/calendar) + - [获取日历](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMDN04yM0QjLzQDN) + - [GetCalendarById (/open-apis/calendar/v3/calendar_list/:calendarId)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#GetCalendarById) + - [获取日历列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMTM14yMxUjLzETN) + - [CalendarList (/open-apis/calendar/v3/calendar_list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#CalendarList) + - [创建日历](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTM14CNxUjL0ETN) + - [CreateCalendars (/open-apis/calendar/v3/calendars)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#CreateCalendars) + - [删除日历](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUTM14SNxUjL1ETN) + - [DeleteCalendarById (/open-apis/calendar/v3/calendars/:calendarId)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#DeleteCalendarById) + - [获取日程](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucTM14yNxUjL3ETN) + - [GetEventById (/open-apis/calendar/v3/calendars/:calendarId/events/:eventId)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#GetEventById) + - [创建日程](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugTM14COxUjL4ETN) + - [CreateEvent (/open-apis/calendar/v3/calendars/:calendarId/events)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#CreateEvent) + - [邀请/移除日程参与者](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIjM14iMyUjLyITN) + - [Attendees (/open-apis/calendar/v3/calendars/:calendarId/events/:eventId/attendees)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#Attendees) + - [获取访问控制列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjM14yMyUjLzITN) + - [Acl (/open-apis/calendar/v3/calendars/:calendarId/acl)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#Acl) + - [删除访问控制](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUjM14SNyUjL1ITN) + - [DeleteAclByRuleId (/open-apis/calendar/v3/calendars/:calendarId/acl/:ruleId)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#DeleteAclByRuleId) + - [查询日历的忙闲状态](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYjM14iNyUjL2ITN) + - [FreeBusyQuery (/open-apis/calendar/v3/freebusy/query)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#FreeBusyQuery) + - [查询公共日历](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukDMwYjL5ADM24SOwAjN) + - [SharedCalendarQuery (/open-apis/calendar/v3/shared_calendar_list/shared_calendar/query)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#SharedCalendarQuery) + - [获取公共日历日程列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIzNwYjLycDM24iM3AjN) + - [SharedCalendarEvents (/open-apis/calendar/v3/shared/calendars/:calendarId/events)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/calendar?tab=doc#SharedCalendarEvents) +- 云文档(capabilities/document) + - [新建文件夹](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTNzUjL5UzM14SO1MTN) + - [CreateFolder (/open-apis/drive/explorer/v2/folder/:folderToken)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#CreateFolder) + - [获取文件夹元信息](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uAjNzUjLwYzM14CM2MTN) + - [RootFolderMeta (/open-apis/drive/explorer/v2/root_folder/meta)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#RootFolderMeta) + - [获取文件夹下的文档清单](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEjNzUjLxYzM14SM2MTN) + - [FolderChildren (/open-apis/drive/explorer/v2/folder/:folderToken/children)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#FolderChildren) + - [新建文档](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTNzUjL0UzM14CN1MTN) + - [CreateFile (/open-apis/drive/explorer/v2/file/:folderToken)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#CreateFile) + - [复制文档](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTNzUjL2UzM14iN1MTN) + - [CopyFile (/open-apis/drive/explorer/v2/file/copy/files/:fileToken)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#CopyFile) + - [删除文档](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATM2UjLwEjN14CMxYTN) + - [DeleteFile (/open-apis/drive/explorer/v2/file/spreadsheets/:spreadsheetToken)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#DeleteFile) + - [增加权限](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMzNzUjLzczM14yM3MTN) + - [PermissionMemberCreate (/open-apis/drive/permission/member/create)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#PermissionMemberCreate) + - [转移拥有者](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQzNzUjL0czM14CN3MTN) + - [PermissionMemberTransfer (/open-apis/drive/permission/member/transfer)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#PermissionMemberTransfer) + - [更新文档公共设置](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTM3UjL5EzN14SOxcTN) + - [PermissionPublicUpdate (/open-apis/drive/permission/public/update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#PermissionPublicUpdate) + - [获取协作者列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATN3UjLwUzN14CM1cTN) + - [PermissionMemberList (/open-apis/drive/permission/member/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#PermissionMemberList) + - [移除协作者权限](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTN3UjL2UzN14iN1cTN) + - [PermissionMemberDelete (/open-apis/drive/permission/member/delete)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#PermissionMemberDelete) + - [更新协作者权限](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucTN3UjL3UzN14yN1cTN) + - [PermissionMemberUpdate (/open-apis/drive/permission/member/update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#PermissionMemberUpdate) + - [判断协作者是否有某权限](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzN3UjL2czN14iN3cTN) + - [PermissionMemberPermitted (/open-apis/drive/permission/member/permitted)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#PermissionMemberPermitted) + - [获取文档文本内容](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukzNzUjL5czM14SO3MTN) + - [RawContent (/open-apis/doc/v2/:docToken/raw_content)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#RawContent) + - [获取sheet@doc的元数据](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOzUjLwgzM14CM4MTN) + - [SheetMeta (/open-apis/doc/v2/:docToken/sheet_meta)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SheetMeta) + - [获取文档元信息](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uczN3UjL3czN14yN3cTN) + - [DocMeta (/open-apis/doc/v2/meta/:docToken)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#DocMeta) + - [获取表格元数据](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uETMzUjLxEzM14SMxMTN) + - [SpreadSheetsMetainfo (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/metainfo)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsMetainfo) + - [操作子表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYTMzUjL2EzM14iNxMTN) + - [SheetsBatchUpdate (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/sheets_batch_update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SheetsBatchUpdate) + - [插入数据](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIjMzUjLyIzM14iMyMTN) + - [SpreadSheetsValuesPrepend (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_prepend)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsValuesPrepend) + - [追加数据](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjMzUjLzIzM14yMyMTN) + - [SpreadSheetsValuesAppend (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_append)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsValuesAppend) + - [插入行列](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQjMzUjL0IzM14CNyMTN) + - [SpreadSheetsInsertDimensionRange (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/insert_dimension_range)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsInsertDimensionRange) + - [增加行列](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUjMzUjL1IzM14SNyMTN) + - [SpreadSheetsDimensionRange (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/dimension_range)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsDimensionRange) + - [增加锁定单元格](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDNzUjL4QzM14CO0MTN) + - [SpreadSheetsProtectedDimension (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/protected_dimension)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsProtectedDimension) + - [合并单元格](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukDNzUjL5QzM14SO0MTN) + - [SpreadSheetsMergeCells (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/merge_cells)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsMergeCells) + - [拆分单元格](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATNzUjLwUzM14CM1MTN) + - [SpreadSheetsUnmergeCells (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/unmerge_cells)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsUnmergeCells) + - [读取单个范围](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugTMzUjL4EzM14COxMTN) + - [SpreadSheetsRange (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values/:range)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsRange) + - [读取多个范围](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukTMzUjL5EzM14SOxMTN) + - [SpreadSheetsValuesBatchGet (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsValuesBatchGet) + - [向多个范围写入数据](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEjMzUjLxIzM14SMyMTN) + - [SpreadSheetsValuesBatchUpdate (/open-apis/sheet/v2/spreadsheets/:spreadsheetToken/values_batch_update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SpreadSheetsValuesBatchUpdate) + - [获取元数据](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMjN3UjLzYzN14yM2cTN) + - [DocsMeta (/open-apis/suite/docs-api/meta)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#DocsMeta) + - [文档搜索](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDM4UjL4ADO14COwgTN) + - [SearchObject (/open-apis/suite/docs-api/search/object)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#SearchObject) + - [添加全文评论](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucDN4UjL3QDO14yN0gTN) + - [CommentAddWhole (/open-apis/comment/add_whole)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/document?tab=doc#CommentAddWhole) +- 审批(capabilities/approve) + - [查看审批定义](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADNyUjLwQjM14CM0ITN) + - [Get (/approval/openapi/v2/approval/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Get) + - [批量获取审批实例ID](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQDOyUjL0gjM14CN4ITN) + - [InstanceList (/approval/openapi/v2/instance/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#InstanceList) + - [获取单个审批实例详情](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEDNyUjLxQjM14SM0ITN) + - [InstanceGet (/approval/openapi/v2/instance/get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#InstanceGet) + - [创建审批实例](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIDNyUjLyQjM14iM0ITN) + - [InstanceCreate (/approval/openapi/v2/instance/create)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#InstanceCreate) + - [审批任务同意](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMDNyUjLzQjM14yM0ITN) + - [Approve (/approval/openapi/v2/instance/approve)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Approve) + - [审批任务拒绝](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQDNyUjL0QjM14CN0ITN) + - [Reject (/approval/openapi/v2/instance/reject)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Reject) + - [审批任务转交](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUDNyUjL1QjM14SN0ITN) + - [Transfer (/approval/openapi/v2/instance/transfer)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Transfer) + - [审批实例撤回](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYDNyUjL2QjM14iN0ITN) + - [Cancel (/approval/openapi/v2/instance/cancel)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Cancel) + - [上传文件](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUDOyUjL1gjM14SN4ITN) + - [Upload (/approval/openapi/v2/file/upload)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Upload) + - [三方审批实例同步](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uczM3UjL3MzN14yNzcTN) + - [ExternalInstanceCreate (/approval/openapi/v2/external/instance/create)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#ExternalInstanceCreate) + - [创建审批定义](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUzNyYjL1cjM24SN3IjN) + - [Create (/approval/openapi/v2/approval/create)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Create) + - [审批实例抄送](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOzYjLwgzM24CM4MjN) + - [InstanceCc (/approval/openapi/v2/instance/cc)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#InstanceCc) + - [订阅审批事件](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ucDOyUjL3gjM14yN4ITN) + - [Subscribe (/approval/openapi/v2/subscription/subscribe)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Subscribe) + - [取消订阅审批事件](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugDOyUjL4gjM14CO4ITN) + - [Unsubscribe (/approval/openapi/v2/subscription/unsubscribe)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/approve?tab=doc#Unsubscribe) +- 会议室(capabilities/meeting) + - [获取建筑物列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ugzNyUjL4cjM14CO3ITN) + - [BuildingList (/open-apis/meeting_room/building/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#BuildingList) + - [查询建筑物详情](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/ukzNyUjL5cjM14SO3ITN) + - [BuildingBatchGet (/open-apis/meeting_room/building/batch_get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#BuildingBatchGet) + - [获取会议室列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uADOyUjLwgjM14CM4ITN) + - [MeetingRoomList (/open-apis/meeting_room/room/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#MeetingRoomList) + - [查询会议室详情](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uEDOyUjLxgjM14SM4ITN) + - [MeetingRoomBatchGet (/open-apis/meeting_room/room/batch_get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#MeetingRoomBatchGet) + - [会议室忙闲查询](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uIDOyUjLygjM14iM4ITN) + - [MeetingRoomFreeBusyBatchGet (/open-apis/meeting_room/freebusy/batch_get)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#MeetingRoomFreeBusyBatchGet) + - [回复会议室日程实例](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzN4UjL2cDO14iN3gTN) + - [InstanceReply (/open-apis/meeting_room/instance/reply)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#InstanceReply) + - [创建建筑物](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uATNwYjLwUDM24CM1AjN) + - [BuildingCreate (/open-apis/meeting_room/building/create)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#BuildingCreate) + - [更新建筑物](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uETNwYjLxUDM24SM1AjN) + - [BuildingUpdate (/open-apis/meeting_room/building/update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#BuildingUpdate) + - [删除建筑物](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMzMxYjLzMTM24yMzEjN) + - [BuildingDelete (/open-apis/meeting_room/building/delete)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#BuildingDelete) + - [查询建筑物ID](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQzMxYjL0MTM24CNzEjN) + - [BuildingBatchGetById (/open-apis/meeting_room/building/batch_get_id)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#BuildingBatchGetById) + - [创建会议室](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uITNwYjLyUDM24iM1AjN) + - [MeetingRoomCreate (/open-apis/meeting_room/room/create)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#MeetingRoomCreate) + - [更新会议室](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uMTNwYjLzUDM24yM1AjN) + - [MeetingRoomUpdate (/open-apis/meeting_room/room/update)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#MeetingRoomUpdate) + - [删除会议室](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUzMxYjL1MTM24SNzEjN) + - [MeetingRoomDelete (/open-apis/meeting_room/room/delete)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#MeetingRoomDelete) + - [查询会议室ID](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uYzMxYjL2MTM24iNzEjN) + - [MeetingRoomBatchGetById (/open-apis/meeting_room/room/batch_get_id)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#MeetingRoomBatchGetById) + - [获取国家地区列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uQTNwYjL0UDM24CN1AjN) + - [CountryList (/open-apis/meeting_room/country/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#CountryList) + - [获取城市列表](https://open.feishu.cn/document/ugTM5UjL4ETO14COxkTN/uUTNwYjL1UDM24SN1AjN) + - [DistrictList (/open-apis/meeting_room/district/list)](https://pkg.go.dev/github.com/fastwego/feishu/apis/capabilities/meeting?tab=doc#DistrictList) diff --git a/doc/img/sdk.gliffy b/doc/img/sdk.gliffy new file mode 100644 index 0000000..7393c04 --- /dev/null +++ b/doc/img/sdk.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":1342.4262295081967,"y":743,"rotation":0,"id":41,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":17,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":-90,"endArrowRotation":-90,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[-530.1967213114754,-235.99892484474185],[-530.1967213114754,-249.66594989649457],[-530.1967213114754,-263.3329749482473],[-530.1967213114754,-277]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"px":0.5,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":180,"px":0.5,"py":1}}},"linkMap":[]},{"x":1244.4262295081967,"y":758,"rotation":0,"id":39,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":16,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":-67.49795355648986,"endArrowRotation":-67.49795352558054,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[-543.6388209411346,-151],[-543.6388209411346,-171],[-431.8997972687886,-171],[-431.8997972687886,-191]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":34,"px":0.7071067811865476,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"px":0.5,"py":1}}},"linkMap":[]},{"x":270.8099428878069,"y":148,"rotation":0,"id":184,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":56,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":185,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

用户群组

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":642.2295081967213,"y":431,"rotation":0,"id":180,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":340,"height":35,"lockAspectRatio":false,"lockShape":false,"order":54,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#dd7e6b","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.1147540983606556,"y":0,"rotation":0,"id":181,"uid":null,"width":337.77049180327845,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

解密

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":760.2295081967213,"y":123,"rotation":0,"id":178,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":53,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":-120.86674286362461,"endArrowRotation":-120.8667430373722,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[122.7583362090071,0],[122.7583362090071,-20],[-58.125859049587916,-20],[-58.125859049587916,-40]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":151,"px":0.7071067811865476,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":75,"px":0.7071067811865476,"py":1}}},"linkMap":[]},{"x":321.2295081967213,"y":470,"rotation":0,"id":176,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":52,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":90,"endArrowRotation":90,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[2.806982278191356,-4],[2.806982278191356,9.670439928729195],[2.806982278191356,23.340879857458333],[2.806982278191356,37.01131978618753]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":81,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"px":0.5,"py":0}}},"linkMap":[]},{"x":324.2295081967213,"y":364,"rotation":0,"id":175,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":51,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":90,"endArrowRotation":90,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[0.7704918032786736,-1],[0.7704918032786736,10.004687640601844],[0.7704918032786736,21.00937528120369],[0.7704918032786736,32.01406292180553]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":17,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":9,"px":0.5,"py":0}}},"linkMap":[]},{"x":301.2295081967213,"y":82,"rotation":0,"id":174,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":50,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":84.54713657621184,"endArrowRotation":84.54713657567382,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[1.307074224200619,0.9999999999999858],[1.307074224200619,21],[23.770491803278674,21],[23.770491803278674,41.000000000000014]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":75,"px":0.29289321881345254,"py":0.9999999999999998}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":17,"px":0.5,"py":0}}},"linkMap":[]},{"x":826.2295081967213,"y":423,"rotation":0,"id":173,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":49,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":-67.34837714696752,"endArrowRotation":-67.34837715279721,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[-14.401541580479943,-27],[-14.401541580479943,-43.5],[56.7583362090071,-43.5],[56.7583362090071,-60]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":7,"px":0.5,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":151,"px":0.7071067811865476,"py":1}}},"linkMap":[]},{"x":479.7068017171987,"y":677,"rotation":0,"id":36,"uid":"com.gliffy.shape.uml.uml_v1.default.actor","width":27.84530386740331,"height":50,"lockAspectRatio":true,"lockShape":false,"order":47,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.actor.uml_v1","strokeWidth":1,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":20.000000000000057,"y":431,"rotation":0,"id":81,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":608.0729809498253,"height":35,"lockAspectRatio":false,"lockShape":false,"order":45,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#a4c2f4","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.9936819047535264,"y":0,"rotation":0,"id":82,"uid":null,"width":604.0856171403182,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

AccessToken

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":20,"y":23,"rotation":0,"id":75,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":964.6402315680552,"height":60,"lockAspectRatio":false,"lockShape":false,"order":43,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#d0e0e3","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":3.162754857600181,"y":0,"rotation":0,"id":76,"uid":null,"width":958.314721852855,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Business Process

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":271.94951128828257,"y":283.15463917525767,"rotation":0,"id":64,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":37,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":65,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

审批

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":271.94951128828257,"y":216.55670103092777,"rotation":0,"id":58,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":31,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":59,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

发送消息

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":39.17609720571888,"y":283.15463917525767,"rotation":0,"id":56,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":29,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":57,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

日历

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":153.74284181072474,"y":283.15463917525767,"rotation":0,"id":54,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":27,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":55,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

文档

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":153.74284181072485,"y":216.55670103092777,"rotation":0,"id":52,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":25,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":53,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

机器人信息/管理

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":39.17609720571888,"y":216.55670103092777,"rotation":0,"id":50,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":23,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":51,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

群信息/群管理

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":803.655737704918,"y":707,"rotation":0,"id":43,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":18,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":95.58192269432621,"endArrowRotation":95.58192269479025,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[-478.655737704918,-140],[-478.655737704918,-120],[-501.6643599836075,-120],[-501.6643599836075,-100]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":34,"px":0.2928932188134524,"py":0}}},"linkMap":[]},{"x":322.2295081967213,"y":819,"rotation":0,"id":38,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":15,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":-0.15393803426732655,"endArrowRotation":-74.69853423275453,"ortho":true,"interpolationType":"quadratic","cornerRadius":null,"controlPath":[[171.39994545417903,-117],[378.5579003703408,-117],[378.5579003703408,-162]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":36,"px":0.5,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":34,"px":0.7071067811865476,"py":1}}},"linkMap":[]},{"x":20.000000000000057,"y":607,"rotation":0,"id":34,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":962.7787862883725,"height":50,"lockAspectRatio":false,"lockShape":false,"order":13,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#b6d7a8","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":3.1566517583225324,"y":0,"rotation":0,"id":35,"uid":null,"width":956.4654827717281,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

飞书

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":643.2295081967213,"y":507,"rotation":0,"id":32,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":338.59384808537357,"height":60,"lockAspectRatio":false,"lockShape":false,"order":11,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#e2e2e2","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.110143764214339,"y":0,"rotation":0,"id":33,"uid":null,"width":336.37356055694494,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Feishu Server

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":153.7428418107249,"y":148,"rotation":0,"id":15,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":9,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":16,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

部门管理

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":39.17609720571888,"y":148,"rotation":0,"id":13,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":7,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":14,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

用户管理

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":20,"y":396,"rotation":0,"id":9,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":608.0729809498253,"height":35,"lockAspectRatio":false,"lockShape":false,"order":5,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#c9daf8","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.9936819047535264,"y":0,"rotation":0,"id":10,"uid":null,"width":604.0856171403182,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

HttpClient

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":641.8279666162414,"y":396,"rotation":0,"id":7,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":340,"height":35,"lockAspectRatio":false,"lockShape":false,"order":3,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#e6b8af","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.1147540983606556,"y":0,"rotation":0,"id":8,"uid":null,"width":337.77049180327845,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

HttpServer

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":20,"y":507,"rotation":0,"id":2,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":610,"height":60,"lockAspectRatio":false,"lockShape":false,"order":1,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#e2e2e2","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.000000000000001,"y":0,"rotation":0,"id":3,"uid":null,"width":606.0000000000001,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Feishu Api Server

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":20,"y":123.00000000000001,"rotation":0,"id":17,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":610,"height":239.99999999999997,"lockAspectRatio":false,"lockShape":false,"order":0,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#d9ead3","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":501.756801717199,"y":283.15463917525767,"rotation":0,"id":68,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":41,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":69,"uid":null,"width":106.71490666564445,"height":21,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

......

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":385.54706581317794,"y":283.15463917525767,"rotation":0,"id":66,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":39,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":67,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

会议室

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":501.75680171719875,"y":216.55670103092777,"rotation":0,"id":62,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":35,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":63,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

身份验证

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":385.54706581317794,"y":216.55670103092777,"rotation":0,"id":60,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":33,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":61,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

发送通知

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":501.75680171719875,"y":148,"rotation":0,"id":48,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":21,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":49,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

应用商店

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":385.54706581317794,"y":148,"rotation":0,"id":44,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":107.41929548851995,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":19,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.3521944114377702,"y":0,"rotation":0,"id":45,"uid":null,"width":106.71490666564445,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

应用管理

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":822.4469733102819,"y":284.13402061855663,"rotation":0,"id":200,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":126.11903325344983,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":70,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.4135050270604911,"y":0,"rotation":0,"id":201,"uid":null,"width":125.29202319932888,"height":21,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

......

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":686.0072444868864,"y":284.13402061855663,"rotation":0,"id":202,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":126.11903325344983,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":68,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.4135050270604911,"y":0,"rotation":0,"id":203,"uid":null,"width":125.29202319932888,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

审批事件

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":822.4469733102817,"y":217.5360824742267,"rotation":0,"id":204,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":126.11903325344983,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":66,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.4135050270604911,"y":0,"rotation":0,"id":205,"uid":null,"width":125.29202319932888,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

群聊事件

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":686.0072444868864,"y":217.5360824742267,"rotation":0,"id":206,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":126.11903325344983,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":64,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.4135050270604911,"y":0,"rotation":0,"id":207,"uid":null,"width":125.29202319932888,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

消息会话事件

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":822.4469733102817,"y":148.97938144329893,"rotation":0,"id":208,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":126.11903325344983,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":62,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.4135050270604911,"y":0,"rotation":0,"id":209,"uid":null,"width":125.29202319932888,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

通讯录事件

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":686.0072444868864,"y":148.97938144329893,"rotation":0,"id":210,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":126.11903325344983,"height":54.84536082474226,"lockAspectRatio":false,"lockShape":false,"order":60,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":0.4135050270604911,"y":0,"rotation":0,"id":211,"uid":null,"width":125.29202319932888,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

应用事件

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":642.2295081967213,"y":123,"rotation":0,"id":151,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":340.48370432115917,"height":240,"lockAspectRatio":false,"lockShape":false,"order":48,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":1,"strokeColor":"#333333","fillColor":"#fce5cd","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]}],"background":"#FFFFFF","width":985,"height":727,"maxWidth":5000,"maxHeight":5000,"nodeIndex":214,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#e6b8af","stroke":"#333333","strokeWidth":1}},"lineStyles":{"global":{"strokeWidth":1,"endArrow":1,"orthoMode":2}},"textStyles":{},"themeData":null}} \ No newline at end of file diff --git a/doc/img/sdk.jpg b/doc/img/sdk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..01382cd209623c3cc7b563fbef3ccd927dd51ca1 GIT binary patch literal 80176 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 0 && cb.Token != s.Ctx.Config.VerificationToken { + err = fmt.Errorf("cb.Token %s != s.Ctx.Config.VerificationToken %s", cb.Token, s.Ctx.Config.VerificationToken) + return + } + + switch cb.Type { + case event_types.CallbackTypeUrlVerification: // 回调 url 校验 + e := event_types.EventChallenge{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.CallbackTypeEventCallback: // 事件 + e := event_types.BaseEvent{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + switch e.Event.Type { + + // 应用事件 + case event_types.EventTypeAppOpen: //首次开通应用 + e := event_types.EventAppOpen{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeAppStatusChange: // 应用停启用 + e := event_types.EventAppStatusChange{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeOrderPaid: // 应用商店应用购买 + e := event_types.EventOrderPaid{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeAppTicket: // app ticket 推送事件 + e := event_types.EventAppTicket{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + + case event_types.EventTypeAppUninstalled: // 应用卸载 + e := event_types.EventAppUninstalled{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + + // 审批事件 + case event_types.EventTypeLeaveApproval: // 请假审批 + e := event_types.EventLeaveApproval{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeLeaveApprovalV2: // 请假审批 + e := event_types.EventLeaveApprovalV2{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeWorkApproval: // 加班审批 + e := event_types.EventWorkApproval{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeShiftApproval: // 换班审批 + e := event_types.EventShiftApproval{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeRemedyApproval: // 补卡审批 + e := event_types.EventRemedyApproval{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeTripApproval: // 出差审批 + e := event_types.EventTripApproval{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + + // 日历事件 + case event_types.EventTypeEventReply: + e := event_types.EventEventReply{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + + // 通讯录事件 + case event_types.EventTypeUserAdd: + e := event_types.EventUserAdd{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeDeptAdd: + e := event_types.EventDeptAdd{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeUserStatusChange: + e := event_types.EventUserStatusChange{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeContactScopeChange: + e := event_types.EventContactScopeChange{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + + // 群聊事件 + case event_types.EventTypeAddUserToChat: + e := event_types.EventAddUserToChat{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeChatDisband: + e := event_types.EventChatDisband{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + + // 机器人和消息会话事件 + case event_types.EventTypeAddBot: // 机器人进群 + e := event_types.EventAddBot{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeRemoveBot: // 机器人被移出群 + e := event_types.EventRemoveBot{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeP2PChatCreate: // 用户和机器人的会话首次被创建 + e := event_types.EventP2PChatCreate{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeMessageRead: // 消息已读 + e := event_types.EventMessageRead{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.EventTypeMessage: // 消息事件 + e := event_types.EventMessage{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + switch e.Event.MsgType { + case event_types.MsgTypeText: // 文本消息 + e := event_types.EventMessageText{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.MsgTypeImage: // 图片消息 + e := event_types.EventMessageImage{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.MsgTypeFile: // 文件消息 + e := event_types.EventMessageFile{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.MsgTypePost: // post x消息 + e := event_types.EventMessagePost{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + case event_types.MsgTypeMergeForward: // 合并转发 + e := event_types.EventMessageMergeForward{} + err = json.Unmarshal(body, &e) + if err != nil { + return + } + return e, nil + } + } + } + + err = errors.New("unknown event") + return +} diff --git a/server_test.go b/server_test.go new file mode 100644 index 0000000..32527cc --- /dev/null +++ b/server_test.go @@ -0,0 +1,94 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package feishu + +import ( + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/fastwego/feishu/types/event_types" +) + +var MockApp *App + +func TestMain(m *testing.M) { + MockApp = NewApp(AppConfig{ + AppId: "AppId", + AppSecret: "AppSecret", + EncryptKey: "kudryavka", + }) + os.Exit(m.Run()) +} + +func TestServer_ParseEvent(t *testing.T) { + + tests := []struct { + name string + wantEvent interface{} + wantErr bool + }{ + {name: "case1", wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body := `{ + "encrypt": "FIAfJPGRmFZWkaxPQ1XrJZVbv2JwdjfLk4jx0k/U1deAqYK3AXOZ5zcHt/cC4ZNTqYwWUW/EoL+b2hW/C4zoAQQ5CeMtbxX2zHjm+E4nX/Aww+FHUL6iuIMaeL2KLxqdtbHRC50vgC2YI7xohnb3KuCNBMUzLiPeNIpVdnYaeteCmSaESb+AZpJB9PExzTpRDzCRv+T6o5vlzaE8UgIneC1sYu85BnPBEMTSuj1ZZzfdQi7ZW992Z4dmJxn9e8FL2VArNm99f5Io3c2O4AcNsQENNKtfAAxVjCqc3mg5jF0nKabA+u/5vrUD76flX1UOF5fzJ0sApG2OEn9wfyPDRBsApn9o+fceF9hNrYBGsdtZrZYyGG387CGOtKsuj8e2E8SNp+Pn4E9oYejOTR+ZNLNi+twxaXVlJhr6l+RXYwEiMGQE9zGFBD6h2dOhKh3W84p1GEYnSRIz1+9/Hp66arjC7RCrhuW5OjCj4QFEQJiwgL45XryxHtiZ7JdAlPmjVsL03CxxFZarzxzffryrWUG3VkRdHRHbTsC34+ScoL5MTDU1QAWdqUC1T7xT0lCvQELaIhBTXAYrznJl6PlA83oqlMxpHh0gZBB1jFbfoUr7OQbBs1xqzpYK6Yjux6diwpQB1zlZErYJUfCqK7G/zI9yK/60b4HW0k3M+AvzMcw=" +}` + /*{ + "uuid": "5226cd85b4d843dccee2e279d93f3ed3", + "event": { + "app_id": "cli_9e28cb7ba56a100e", + "before_status": { + "is_active": true, + "is_frozen": true, + "is_resigned": false + }, + "change_time": "2020-05-20 18:33:25", + "current_status": { + "is_active": true, + "is_frozen": false, + "is_resigned": false + }, + "employee_id": "75ge6c49", + "open_id": "ou_2ef04637d933f798dcb92c99e845ed09", + "tenant_key": "2d520d3b434f175e", + "type": "user_status_change" + }, + "token": "GzhQEyfUcx7eEungQFWtXgCbxSpUOJIb", + "ts": "1589970805.376395", + "type": "event_callback" + }*/ + request := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) + gotEvent, _ := MockApp.Server.ParseEvent(request) + //if (err != nil) != tt.wantErr { + // t.Errorf("ParseEvent() error = %v, wantErr %v", err, tt.wantErr) + // return + //} + event, ok := gotEvent.(event_types.EventUserStatusChange) + if !ok { + t.Errorf("ParseEvent() error = %v", ok) + return + } + + if event.Event.OpenID != "ou_2ef04637d933f798dcb92c99e845ed09" { + t.Errorf(`event.Event.OpenID != "ou_2ef04637d933f798dcb92c99e845ed09"`) + return + } + }) + } +} diff --git a/test/test.go b/test/test.go new file mode 100644 index 0000000..c8c0785 --- /dev/null +++ b/test/test.go @@ -0,0 +1,55 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package test 模拟微信服务器 测试 +package test + +import ( + "net/http" + "net/http/httptest" + "sync" + + "github.com/fastwego/feishu" +) + +var MockApp *feishu.App +var MockSvr *httptest.Server +var MockSvrHandler *http.ServeMux +var onceSetup sync.Once + +// 初始化测试环境 +func Setup() { + onceSetup.Do(func() { + + MockApp = feishu.NewApp(feishu.AppConfig{ + AppId: "APPID", + AppSecret: "SECRET", + //VerificationToken: "TOKEN", + //EncryptKey: "EncryptKey", + }) + + // Mock Server + MockSvrHandler = http.NewServeMux() + MockSvr = httptest.NewServer(MockSvrHandler) + feishu.FeishuServerUrl = MockSvr.URL // 拦截发往微信服务器的请求 + + // Mock access token + MockSvrHandler.HandleFunc("/open-apis/auth/v3/tenant_access_token/internal/", func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"tenant_access_token":"ACCESS_TOKEN","expire":7200}`)) + }) + MockSvrHandler.HandleFunc("/open-apis/auth/v3/app_access_token/internal/", func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"app_access_token":"ACCESS_TOKEN","expire":7200}`)) + }) + }) +} diff --git a/types/event_types/app_event.go b/types/event_types/app_event.go new file mode 100644 index 0000000..09f30b1 --- /dev/null +++ b/types/event_types/app_event.go @@ -0,0 +1,190 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event_types + +const EventTypeAppOpen = "app_open" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_xxx", // 开通的应用ID + "tenant_key":"xxx", // 开通应用的企业唯一标识 + "type":"app_open", // 事件类型 + "applicants": [ // 应用的申请者,可能有多个 + { + "open_id":"xxx" , // 员工对此应用的唯一标识,同一员工对不同应用的open_id不同 + "user_id": "b78cfg77"//安装者的user_id (仅企业自建应用返回 ) + } + ], + "installer": { // 应用的安装者。如果是自动安装,则没有此字段。 + "open_id":"xxx" , // 员工对此应用的唯一标识,同一员工对不同应用的open_id不同 + "user_id": "b78cfg77"//安装者的user_id (仅企业自建应用返回 ) + } + } +} + +*/ + +type EventAppOpen struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + Applicants []struct { + OpenID string `json:"open_id"` + UserID string `json:"user_id"` + } `json:"applicants"` + Installer struct { + OpenID string `json:"open_id"` + UserID string `json:"user_id"` + } `json:"installer"` + } `json:"event"` +} + +const EventTypeAppStatusChange = "app_status_change" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_xxx", // 应用ID + "tenant_key":"xxx", // 企业唯一标识 + "type": "app_status_change", // 事件类型 + "status": "start_by_tenant" //应用状态 start_by_tenant: 租户启用; stop_by_tenant: 租户停用; stop_by_platform: 平台停用 + } +} + +*/ + +type EventAppStatusChange struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + Status string `json:"status"` + } `json:"event"` +} + +const EventTypeOrderPaid = "order_paid" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "type":"order_paid", // 事件类型 + "app_id": "cli_9daeceab98721136", //应用ID + "order_id": "6704894492631105539", // 用户购买付费方案时对订单ID 可作为唯一标识 + "price_plan_id": "price_9d86fa1333b8110c", //付费方案ID + "price_plan_type": "per_seat_per_month", // 用户购买方案类型 + "seats": 20, // 表示购买了多少人份 + "buy_count":1, //套餐购买数量 目前都为1 + "create_time": "1502199207", + "pay_time": "1502199209", + "buy_type": "buy", // 购买类型 buy普通购买 upgrade为升级购买 + "src_order_id": "6704894492631105539", // 当前为升级购买时(buy_type 为upgrade),该字段表示原订单ID,升级后原订单失效,状态变为已升级(业务方需要处理) + "order_pay_price":10000//订单支付价格 单位分, + "tenant_key": "2f98c01bc23f6847"//购买应用的企业标示 + } +} + +*/ + +type EventOrderPaid struct { + BaseEvent + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + OrderID string `json:"order_id"` + PricePlanID string `json:"price_plan_id"` + PricePlanType string `json:"price_plan_type"` + Seats int `json:"seats"` + BuyCount int `json:"buy_count"` + CreateTime string `json:"create_time"` + PayTime string `json:"pay_time"` + BuyType string `json:"buy_type"` + SrcOrderID string `json:"src_order_id"` + OrderPayPrice int `json:"order_pay_price"` + TenantKey string `json:"tenant_key"` + } `json:"event"` +} + +const EventTypeAppTicket = "app_ticket" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_xxx", + "app_ticket":"xxx", + "type":"app_ticket" + } +} + +*/ + +type EventAppTicket struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + AppTicket string `json:"app_ticket"` + Type string `json:"type"` + } `json:"event"` +} + +const EventTypeAppUninstalled = "app_uninstalled" + +/* + +{ + "ts": "1502199207.7171419", //时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", // 校验 Token + "type": "event_callback", // 事件回调此处固定为 event_callback + "event": { + "app_id": "cli_xxx", // 被卸载的应用ID + "tenant_key": "xxx", // 卸载应用的企业ID + "type": "app_uninstalled" // 事件类型 + } +} + +*/ + +type EventAppUninstalled struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + } `json:"event"` +} diff --git a/types/event_types/approve_event.go b/types/event_types/approve_event.go new file mode 100644 index 0000000..23bf190 --- /dev/null +++ b/types/event_types/approve_event.go @@ -0,0 +1,314 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event_types + +const EventTypeLeaveApproval = "leave_approval" + +/* + +{ + "ts": "1502199207.7171419", // 时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", // 校验Toke + "type": "event_callback", // 事件回调此处固定为event_callback + "event": { + "app_id": "cli_xxx", + "tenant_key":"xxx", + "type": "leave_approval", + "instance_code": "xxx", // 审批实例Code + "employee_id": "xxx", // 用户id + "start_time": 1502199207, // 审批发起时间 + "end_time": 1502199307, // 审批结束时间 + "leave_type": "xxx", // 请假类型 + "leave_unit": 1, // 请假最小时长:1:一天,2:半天 + "leave_start_time": "2018-12-01 12:00:00", // 请假开始时间 + "leave_end_time": "2018-12-02 12:00:00", // 请假结束时间 + "leave_interval": 7200, // 请假时长,单位(秒) + "leave_reason": "xxx" // 请假事由 + } +} + +*/ + +type EventLeaveApproval struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + InstanceCode string `json:"instance_code"` + EmployeeID string `json:"employee_id"` + StartTime int `json:"start_time"` + EndTime int `json:"end_time"` + LeaveType string `json:"leave_type"` + LeaveUnit int `json:"leave_unit"` + LeaveStartTime string `json:"leave_start_time"` + LeaveEndTime string `json:"leave_end_time"` + LeaveInterval int `json:"leave_interval"` + LeaveReason string `json:"leave_reason"` + } `json:"event"` +} + +const EventTypeLeaveApprovalV2 = "leave_approvalV2" + +/* + +{ + "ts": "1502199207.7171419", // 时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", // 校验Toke + "type": "event_callback", // 事件回调此处固定为event_callback + "event": { + "app_id": "cli_xxx", + "tenant_key":"xxx", + "type": "leave_approvalV2", + "instance_code": "xxx", // 审批实例Code + "user_id": "xxx", // 用户id + "start_time": 1564590532, // 审批发起时间 + "end_time": 1564590532, // 审批结束时间 + "leave_name": "@i18n@123456", // 假期名称 + "leave_unit": "DAY", // 请假最小时长 + "leave_start_time": "2019-10-01 00:00:00",// 请假开始时间 + "leave_end_time":"2019-10-02 00:00:00",// 请假结束时间 + "leave_detail": [ + ["2019-10-01 00:00:00","2019-10-02 00:00:00"] + ], // 具体请假明细 + "leave_interval": 86400, // 请假时长,单位(秒) + "leave_reason": "abc", // 请假事由 + "i18n_resources":[ { + "locale":"en_us", + "is_default":true, + "texts":{ + "@i18n@123456":"Holiday" + } + } ] // 国际化文案 +} + + +*/ + +type EventLeaveApprovalV2 struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + InstanceCode string `json:"instance_code"` + UserID string `json:"user_id"` + StartTime int `json:"start_time"` + EndTime int `json:"end_time"` + LeaveName string `json:"leave_name"` + LeaveUnit string `json:"leave_unit"` + LeaveStartTime string `json:"leave_start_time"` + LeaveEndTime string `json:"leave_end_time"` + LeaveDetail [][]string `json:"leave_detail"` + LeaveInterval int `json:"leave_interval"` + LeaveReason string `json:"leave_reason"` + I18NResources []struct { + Locale string `json:"locale"` + IsDefault bool `json:"is_default"` + Texts struct { + I18N123456 string `json:"@i18n@123456"` + } `json:"texts"` + } `json:"i18n_resources"` + } `json:"event"` +} + +const EventTypeWorkApproval = "work_approval" + +/* + +{ + "ts": "1502199207.7171419", //时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", //校验Toke + "type": "event_callback", //事件回调此处固定为event_callback + "event": { + "app_id": "cli_xxx", + "tenant_key":"xxx", + "type":"work_approval", + "instance_code": "xxx", // 审批实例Code + "employee_id": "xxx", // 用户id + "start_time": 1502199207, // 审批发起时间 + "end_time": 1502199307, // 审批结束时间 + "work_type": "xxx", // 加班类型 + "work_start_time": "2018-12-01 12:00:00", // 加班开始时间 + "work_end_time": "2018-12-02 12:00:00", // 加班结束时间 + "work_interval": 7200, // 加班时长,单位(秒) + "work_reason": "xxx" // 加班事由 + } +} + +*/ + +type EventWorkApproval struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + InstanceCode string `json:"instance_code"` + EmployeeID string `json:"employee_id"` + StartTime int `json:"start_time"` + EndTime int `json:"end_time"` + WorkType string `json:"work_type"` + WorkStartTime string `json:"work_start_time"` + WorkEndTime string `json:"work_end_time"` + WorkInterval int `json:"work_interval"` + WorkReason string `json:"work_reason"` + } `json:"event"` +} + +const EventTypeShiftApproval = "shift_approval" + +/* + +{ + "ts": "1502199207.7171419", //时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", //校验Toke + "type": "event_callback", //事件回调此处固定为event_callback + "event": { + "app_id": "cli_xxx", + "tenant_key":"xxx", + "type":"shift_approval", + "instance_code": "xxx", // 审批实例Code + "employee_id": "xxx", // 用户id + "start_time": 1502199207, // 审批发起时间 + "end_time": 1502199307, // 审批结束时间 + "shift_time": "2018-12-01 12:00:00", // 换班时间 + "return_time": "2018-12-02 12:00:00", // 还班时间 + "shift_reason": "xxx" // 换班事由 + } +} + +*/ + +type EventShiftApproval struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + InstanceCode string `json:"instance_code"` + EmployeeID string `json:"employee_id"` + StartTime int `json:"start_time"` + EndTime int `json:"end_time"` + ShiftTime string `json:"shift_time"` + ReturnTime string `json:"return_time"` + ShiftReason string `json:"shift_reason"` + } `json:"event"` +} + +const EventTypeRemedyApproval = "remedy_approval" + +/* + +{ + "ts": "1502199207.7171419", //时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", //校验Toke + "type": "event_callback", //事件回调此处固定为event_callback + "event": { + "app_id": "cli_xxx", + "tenant_key":"xxx", + "type":"remedy_approval", + "instance_code": "xxx", // 审批实例Code + "employee_id": "xxx", // 用户id + "start_time": 1502199207, // 审批发起时间 + "end_time": 1502199307, // 审批结束时间 + "remedy_time": "2018-12-01 12:00:00", // 补卡时间 + "remedy_reason": "xxx" // 补卡原因 + } +} + +*/ + +type EventRemedyApproval struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + InstanceCode string `json:"instance_code"` + EmployeeID string `json:"employee_id"` + StartTime int `json:"start_time"` + EndTime int `json:"end_time"` + RemedyTime string `json:"remedy_time"` + RemedyReason string `json:"remedy_reason"` + } `json:"event"` +} + +const EventTypeTripApproval = "trip_approval" + +/* + +{ + "ts": "1502199207.7171419", //时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", //校验Toke + "type": "event_callback", //事件回调此处固定为event_callback + "event": { + "app_id": "cli_xxx", + "tenant_key":"xxx", + "type":"trip_approval", + "instance_code": "xxx", // 审批实例Code + "employee_id": "xxx", // 用户id + "start_time": 1502199207, // 审批发起时间 + "end_time": 1502199307, // 审批结束时间 + "schedules": [{ // Schedule 结构数组 + "trip_start_time": "2018-12-01 12:00:00", // 行程开始时间 + "trip_end_time": "2018-12-01 12:00:00", // 行程结束时间 + "trip_interval": 3600, // 行程时长(秒) + "departure": "xxx", // 出发地 + "destination": "xxx", // 目的地 + "transportation": "xxx", // 目的地 + "trip_type": "单程", // 单程/往返 + "remark": "备注" // 备注 + }, + ] + "trip_interval": 3600, // 行程总时长(秒) + "trip_reason": "xxx" // 出差事由 + "trip_peers": ["xxx", "yyy"], // 同行人 + } +} + +*/ + +type EventTripApproval struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + InstanceCode string `json:"instance_code"` + EmployeeID string `json:"employee_id"` + StartTime int `json:"start_time"` + EndTime int `json:"end_time"` + Schedules []struct { + TripStartTime string `json:"trip_start_time"` + TripEndTime string `json:"trip_end_time"` + TripInterval int `json:"trip_interval"` + Departure string `json:"departure"` + Destination string `json:"destination"` + Transportation string `json:"transportation"` + TripType string `json:"trip_type"` + Remark string `json:"remark"` + } `json:"schedules"` + TripInterval int `json:"trip_interval"` + TripReason string `json:"trip_reason"` + TripPeers []string `json:"trip_peers"` + } `json:"event"` +} diff --git a/types/event_types/bot_message_event.go b/types/event_types/bot_message_event.go new file mode 100644 index 0000000..2d9619d --- /dev/null +++ b/types/event_types/bot_message_event.go @@ -0,0 +1,508 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event_types + +const ( + EventTypeMessage = "message" +) + +type EventMessage struct { + BaseEvent + Event struct { + MsgType string `json:"msg_type"` + } `json:"event"` +} + +const EventTypeAddBot = "add_bot" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_9c8609450f78d102", + "chat_i18n_names": { // 群名称国际化字段 + "en_us": "英文标题", + "zh_cn": "中文标题" + }, + "chat_name": "群名称", + "chat_owner_employee_id": "ca51d83b",// 群主的employee_id(即“用户ID”。如果群主是机器人则没有这个字段,仅企业自建应用返回) + "chat_owner_name": "xxx", // 群主姓名 + "chat_owner_open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb", // 群主的open_id + "open_chat_id": "oc_e520983d3e4f5ec7306069bffe672aa3", // 群聊的id + "operator_employee_id": "ca51d83b", // 操作者的emplolyee_id ,仅企业自建应用返回 + "operator_name": "yyy", // 操作者姓名 + "operator_open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb",//操作者的open_id + "owner_is_bot": false, //群主是否是机器人 + "tenant_key": "736588c9260f175d", // 企业标识 + "type": "add_bot" // 事件类型 + } +} + + +*/ + +type EventAddBot struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + ChatI18NNames struct { + EnUs string `json:"en_us"` + ZhCn string `json:"zh_cn"` + } `json:"chat_i18n_names"` + ChatName string `json:"chat_name"` + ChatOwnerEmployeeID string `json:"chat_owner_employee_id"` + ChatOwnerName string `json:"chat_owner_name"` + ChatOwnerOpenID string `json:"chat_owner_open_id"` + OpenChatID string `json:"open_chat_id"` + OperatorEmployeeID string `json:"operator_employee_id"` + OperatorName string `json:"operator_name"` + OperatorOpenID string `json:"operator_open_id"` + OwnerIsBot bool `json:"owner_is_bot"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + } `json:"event"` +} + +const EventTypeRemoveBot = "remove_bot" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_9c8609450f78d102", + "chat_i18n_names": { // 群名称国际化字段 + "en_us": "英文标题", + "zh_cn": "中文标题" + }, + "chat_name": "群名称", + "chat_owner_employee_id": "ca51d83b",// 群主的employee_id(即“用户ID”。如果群主是机器人则没有这个字段,仅企业自建应用返回) + "chat_owner_name": "xxx", // 群主姓名 + "chat_owner_open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb", // 群主的open_id + "open_chat_id": "oc_e520983d3e4f5ec7306069bffe672aa3", // 群聊的id + "operator_employee_id": "ca51d83b", // 操作者的emplolyee_id ,仅企业自建应用返回 + "operator_name": "yyy", // 操作者姓名 + "operator_open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb",//操作者的open_id + "owner_is_bot": false, //群主是否是机器人 + "tenant_key": "736588c9260f175d", // 企业标识 + "type": "remove_bot" // 移除机器人:remove_bot + + } +} + +*/ + +type EventRemoveBot struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + ChatI18NNames struct { + EnUs string `json:"en_us"` + ZhCn string `json:"zh_cn"` + } `json:"chat_i18n_names"` + ChatName string `json:"chat_name"` + ChatOwnerEmployeeID string `json:"chat_owner_employee_id"` + ChatOwnerName string `json:"chat_owner_name"` + ChatOwnerOpenID string `json:"chat_owner_open_id"` + OpenChatID string `json:"open_chat_id"` + OperatorEmployeeID string `json:"operator_employee_id"` + OperatorName string `json:"operator_name"` + OperatorOpenID string `json:"operator_open_id"` + OwnerIsBot bool `json:"owner_is_bot"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + } `json:"event"` +} + +const EventTypeP2PChatCreate = "p2p_chat_create" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_9c8609450f78d102", + "chat_id": "oc_26b66a5eb603162b849f91bcd8815b20", //机器人和用户的会话id + "operator": { // 会话的发起人。可能是用户,也可能是机器人。 + "open_id": "ou_2d2c0399b53d06fd195bb393cd1e38f2" // 员工对此应用的唯一标识,同一员工对不同应用的open_id不同 + "user_id": "gfa21d92" // 即“用户ID”,仅企业自建应用会返回 + }, + "tenant_key": "736588c9260f175c", // 企业标识 + "type": "p2p_chat_create", // 事件类型 + "user": { // 会话的用户 + "name": "张三", + "open_id": "ou_7dede290d6a27698b969a7fd70ca53da", // 员工对此应用的唯一标识,同一员工对不同应用的open_id不同 + "user_id": "gfa21d92" // 即“用户ID”,仅企业自建应用会返回 + } + } +} + +*/ + +type EventP2PChatCreate struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + ChatID string `json:"chat_id"` + Operator struct { + OpenID string `json:"open_id"` + UserID string `json:"user_id"` + } `json:"operator"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + User struct { + Name string `json:"name"` + OpenID string `json:"open_id"` + UserID string `json:"user_id"` + } `json:"user"` + } `json:"event"` +} + +const MsgTypeText = "text" + +/* + +{ + "uuid": "41b5f371157e3d5341b38b20396e77e3", + "token": "2g7als3DgPW6Xp1xEpmcvgVhQG621bFY",//校验Token + "ts": "1550038209.428520", //时间戳 + "type": "event_callback",//事件回调此处固定为event_callback + "event": { + "type": "message", // 事件类型 + "app_id": "cli_xxx", + "tenant_key": "xxx", //企业标识 + "root_id": "", + "parent_id": "", + "open_chat_id": "oc_5ce6d572455d361153b7cb51da133945", + "chat_type": "private",//私聊private,群聊group + "msg_type": "text", //消息类型 + "open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb", + "open_message_id": "om_36686ee62209da697d8775375d0c8e88", + "is_mention": false, + "text": "@小助手 消息内容 @张三", // 消息文本,可能包含被@的人/机器人。 + "text_without_at_bot":"消息内容 @张三" //消息内容,会过滤掉at你的机器人的内容 + } +} + +*/ + +type EventMessageText struct { + EventMessage + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + RootID string `json:"root_id"` + ParentID string `json:"parent_id"` + OpenChatID string `json:"open_chat_id"` + ChatType string `json:"chat_type"` + MsgType string `json:"msg_type"` + OpenID string `json:"open_id"` + OpenMessageID string `json:"open_message_id"` + IsMention bool `json:"is_mention"` + Text string `json:"text"` + TextWithoutAtBot string `json:"text_without_at_bot"` + } `json:"event"` +} + +const MsgTypePost = "post" + +/* + +{ + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "2g7als3DgPW6Xp1xEpmcvgVhQG621bFY", + "ts": "1550032496.527917", + "type": "event_callback", + "event": { + "type": "message", + "app_id": "cli_xxx", + "tenant_key": "xxx", //企业标识 + "root_id": "", + "parent_id": "", + "open_chat_id": "oc_5ce6d572455d361153b7cb51da133945",//发消息的open_chat_id + "chat_type": "private", + "msg_type": "post",// rich_text和post消息这里统一都是post + "open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb",//发消息的用户open_id + "open_message_id": "om_a81fa00ee467b1084ffd80b197b58f4b",//消息id + "is_mention": false, + "text": "\u003cp\u003e测试1212\u003c/p\u003e\u003cfigure\u003e\u003cimg src=\"http://p4.pstatp.com/origin/daff000d4967d033705b\" origin-width=\"2456\" origin-height=\"2458\"/\u003e\u003c/figure\u003e",//消息内容 + "text_without_at_bot":"消息内容",//消息内容,会过滤掉at你的机器人的内容 + "title": "测试" ,//消息标题 + "image_keys": [ //富文本里面的图片的keys + "1867eac8-8006-40be-8549-b7beae0d3c4a", + "434593af-5269-4db4-8b94-65c6dfd4f35e" + ], + } +} + +*/ + +type EventMessagePost struct { + EventMessage + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + RootID string `json:"root_id"` + ParentID string `json:"parent_id"` + OpenChatID string `json:"open_chat_id"` + ChatType string `json:"chat_type"` + MsgType string `json:"msg_type"` + OpenID string `json:"open_id"` + OpenMessageID string `json:"open_message_id"` + IsMention bool `json:"is_mention"` + Text string `json:"text"` + TextWithoutAtBot string `json:"text_without_at_bot"` + Title string `json:"title"` + ImageKeys []string `json:"image_keys"` + } `json:"event"` +} + +const MsgTypeImage = "image" + +/* + +{ + "uuid": "c58e17e9e84824a48e51a562cf969fb3", + "token": "2g7als3DgPW6Xp1xEpmcvgVhQG621bFY", + "ts": "1550038110.478493", + "type": "event_callback", + "event": { + "type": "message", + "app_id": "cli_xxx", + "tenant_key": "xxx", //企业标识 + "root_id": "", + "parent_id": "", + "open_chat_id": "oc_5ce6d572455d361153b7cb51da133945", + "chat_type": "private", + "msg_type": "image", //图片消息 + "image_height" :"300", + "image_width" :"300", + "open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb", + "open_message_id": "om_340057d660022bf141eb470859c6114c", + "is_mention": false, + "image_key": "cd1ce282-94d1-4154-a326-121b07e4721e", // image_key,获取图片内容请查https://open.feishu.cn/document/ukTMukTMukTM/uYzN5QjL2cTO04iN3kDN + + } +} + +*/ + +type EventMessageImage struct { + EventMessage + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + RootID string `json:"root_id"` + ParentID string `json:"parent_id"` + OpenChatID string `json:"open_chat_id"` + ChatType string `json:"chat_type"` + MsgType string `json:"msg_type"` + ImageHeight string `json:"image_height"` + ImageWidth string `json:"image_width"` + OpenID string `json:"open_id"` + OpenMessageID string `json:"open_message_id"` + IsMention bool `json:"is_mention"` + ImageKey string `json:"image_key"` + } `json:"event"` +} + +const MsgTypeFile = "file" + +/* + +{ + "uuid": "8bbb4b5ba77c316991c41fa9ef0ced8b", + "token": "ZYcIUHYh8lmZen6jStLgvcXAXbqn2tle", + "ts": "1576725930.795192", + "type": "event_callback", + "event": { + "app_id": "cli_xxx", + "tenant_key": "xxx", + "chat_type": "private", + "is_mention": false, + "msg_type": "file", + "open_chat_id": "oc_a1e061372f7745a543dsd5h3d6d2349a", + "open_id": "ou_2b940d169b7a4a0c76633984b08ced73", + "open_message_id": "om_d85c4sd7b209tbb98g693a958bc7185f", + "parent_id": "", + "root_id": "", + "type": "message", + "file_key": "file_b4do9r9b-3526-4bc4-a568-65fe3695b05g" + } +} + +*/ + +type EventMessageFile struct { + EventMessage + Event struct { + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + ChatType string `json:"chat_type"` + IsMention bool `json:"is_mention"` + MsgType string `json:"msg_type"` + OpenChatID string `json:"open_chat_id"` + OpenID string `json:"open_id"` + OpenMessageID string `json:"open_message_id"` + ParentID string `json:"parent_id"` + RootID string `json:"root_id"` + Type string `json:"type"` + FileKey string `json:"file_key"` + } `json:"event"` +} + +const MsgTypeMergeForward = "merge_forward" + +/* + +{ + "uuid": "d465df13905458e361ff39af85d96d09", + "token": "2g7als3DgPW6Xp1xEpmcvgVhQG621bFY", + "ts": "1550111453.764646", + "type": "event_callback", + "event": { + "type": "message", + "app_id": "cli_xxx", + "tenant_key": "xxx", //企业标识 + "root_id": "", + "parent_id": "", + "open_chat_id": "oc_5ce6d572455d361153b7cb51da133945", + "msg_type": "merge_forward", + "open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb", + "open_message_id": "om_b3961b120d67259e7495d8eb69488189", + "is_mention": false, + "chat_type": "private", + "chat_id": "6642174187597201422", + "user": "6610187460791558158", + "msg_list": [ + { + "root_id": "", + "parent_id": "", + "open_chat_id": "oc_b74c59c68d0f2d0ac65846272499d651", + "msg_type": "image", + "open_id": "", + "open_message_id": "be1000265b014075a869134b20c87633", + "is_mention": false, + "image_key": "99295878-5e85-41a3-bb00-0ad63b5b156d", + "image_url": "https://oapi-staging.zjurl.cn/open-apis/api/v2/file/f/99295878-5e85-41a3-bb00-0ad63b5b156d", + "create_time": 1550044148 + }, + { + "root_id": "", + "parent_id": "", + "open_chat_id": "oc_b74c59c68d0f2d0ac65846272499d651", + "msg_type": "text", + "open_id": "", + "open_message_id": "om_a96c620f2aa036e3c08abebaec7f09d1", + "is_mention": false, + "text": "文本1", + "create_time": 1550044749 + }, + { + "root_id": "", + "parent_id": "", + "open_chat_id": "oc_b74c59c68d0f2d0ac65846272499d651", + "msg_type": "post", + "open_id": "", + "open_message_id": "om_5d5b1732aa9b997dff23d63146427bb2", + "is_mention": false, + "text": "\u003cp\u003e富文本内容\u003c/p\u003e\u003cfigure\u003e\u003cimg src=\"http://p2.pstatp.com/origin/dad90010d9c8fc72f0b0\" origin-width=\"888\" origin-height=\"888\"/\u003e\u003c/figure\u003e", + "title": "富文本", + "create_time": 1550044772 + } + ] + } +} + +*/ + +type EventMessageMergeForward struct { + EventMessage + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + RootID string `json:"root_id"` + ParentID string `json:"parent_id"` + OpenChatID string `json:"open_chat_id"` + MsgType string `json:"msg_type"` + OpenID string `json:"open_id"` + OpenMessageID string `json:"open_message_id"` + IsMention bool `json:"is_mention"` + ChatType string `json:"chat_type"` + ChatID string `json:"chat_id"` + User string `json:"user"` + MsgList []struct { + RootID string `json:"root_id"` + ParentID string `json:"parent_id"` + OpenChatID string `json:"open_chat_id"` + MsgType string `json:"msg_type"` + OpenID string `json:"open_id"` + OpenMessageID string `json:"open_message_id"` + IsMention bool `json:"is_mention"` + ImageKey string `json:"image_key,omitempty"` + ImageURL string `json:"image_url,omitempty"` + CreateTime int `json:"create_time"` + Text string `json:"text,omitempty"` + Title string `json:"title,omitempty"` + } `json:"msg_list"` + } `json:"event"` +} + +const EventTypeMessageRead = "message_read" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_9c8609450f78d102", + "open_chat_id": "oc_e520983d3e4f5ec7306069bffe672aa3", + "open_id": "ou_2d2c0399b53d06fd195bb393cd1e38f2", + "open_message_ids": ["om_dc13264520392913993dd051dba21dcf"], // 已读消息列表 + "tenant_key": "xxx", + "type": "message_read" + } +} + +*/ + +type EventMessageRead struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + OpenChatID string `json:"open_chat_id"` + OpenID string `json:"open_id"` + OpenMessageIds []string `json:"open_message_ids"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + } `json:"event"` +} diff --git a/types/event_types/calendar_event.go b/types/event_types/calendar_event.go new file mode 100644 index 0000000..19ad858 --- /dev/null +++ b/types/event_types/calendar_event.go @@ -0,0 +1,60 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event_types + +const ( + EventTypeEventReply = "event_reply" +) + +/* +{ + "ts": "1502199207.7171419", //时间戳 + "uuid": "bc447199585340d1f3728d26b1c0297a", + "token": "41a9425ea7df4536a7623e38fa321bae", // 校验 Token + "type": "event_callback", // 事件回调此处固定为 event_callback + "event": { + "app_id" : "cli_xxxxxx", + "type" : "event_reply", // 事件类型 + "tenant_key" : "xxxxx", + "event_id": "xxxx", //日程id + "attendee" : { + "open_id":"xxx", // 回复此日程的参与人的open_id + "employee_id":"yyy", // 回复此日程的参与人的employee_id,仅自建应用才会返回 + "union_id": "zzz" // 用户在ISV下的唯一标识,申请了"获取用户统一ID"权限后返回 + }, + "status": "accept", // 对日程的回复:accept 接受, tentative 待定, decline 拒绝 + "display_name":"xxx", + "reply_timestmap":"1589097034" // 回复时间 + } +} + +*/ +type EventEventReply struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + Type string `json:"type"` + TenantKey string `json:"tenant_key"` + EventID string `json:"event_id"` + Attendee struct { + OpenID string `json:"open_id"` + EmployeeID string `json:"employee_id"` + UnionID string `json:"union_id"` + } `json:"attendee"` + Status string `json:"status"` + DisplayName string `json:"display_name"` + ReplyTimestmap string `json:"reply_timestmap"` + } `json:"event"` +} diff --git a/types/event_types/contact_event.go b/types/event_types/contact_event.go new file mode 100644 index 0000000..39f50fb --- /dev/null +++ b/types/event_types/contact_event.go @@ -0,0 +1,159 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event_types + +const EventTypeUserAdd = "user_add" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "type": "user_add", // 事件类型,包括user_add, user_update, user_leave + "app_id": "cli_xxx", // 应用ID + "tenant_key": "xxx", // 企业标识 + "open_id":"xxx" , // 员工对此应用的唯一标识,同一员工对不同应用的open_id不同 + "employee_id":"xxx", // 即“用户ID”,仅企业自建应用会返回 + "union_id": "xxx" // 用户在ISV下的唯一标识,申请了"获取用户统一ID"权限后返回 + } +} + +*/ + +type EventUserAdd struct { + BaseEvent + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + OpenID string `json:"open_id"` + EmployeeID string `json:"employee_id"` + UnionID string `json:"union_id"` + } `json:"event"` +} + +const EventTypeDeptAdd = "dept_add" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "type": "dept_add", // 事件类型,包括 dept_add, dept_update, dept_delete + "app_id": "cli_xxx", // 应用ID + "tenant_key": "xxx", // 企业标识 + "open_department_id":"od-xxx" // 部门的Id + } +} + +*/ + +type EventDeptAdd struct { + BaseEvent + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + OpenDepartmentID string `json:"open_department_id"` + } `json:"event"` +} + +const EventTypeUserStatusChange = "user_status_change" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "type": "user_status_change", // 事件类型 + "app_id": "cli_xxx", // 应用ID + "tenant_key": "xxx", // 企业标识 + "open_id":"xxx" , // 员工对此应用的唯一标识,同一员工对不同应用的open_id不同 + "employee_id":"xxx", // 即“用户ID”,仅企业自建应用会返回 + "union_id": "xxx", //用户统一ID,申请了"获取用户统一ID"权限后返回 + "before_status": { // 变化前的状态 + "is_active": false, // 账号是否已激活 + "is_frozen": false, // 账号是否冻结 + "is_resigned": false // 是否离职 + }, + "change_time": "2020-02-21 16:28:48", // 状态更新的时间 + "current_status": { // 变化后的状态 + "is_active": true, + "is_frozen": false, + "is_resigned": false + } + } +} + +*/ + +type EventUserStatusChange struct { + BaseEvent + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + OpenID string `json:"open_id"` + EmployeeID string `json:"employee_id"` + UnionID string `json:"union_id"` + BeforeStatus struct { + IsActive bool `json:"is_active"` + IsFrozen bool `json:"is_frozen"` + IsResigned bool `json:"is_resigned"` + } `json:"before_status"` + ChangeTime string `json:"change_time"` + CurrentStatus struct { + IsActive bool `json:"is_active"` + IsFrozen bool `json:"is_frozen"` + IsResigned bool `json:"is_resigned"` + } `json:"current_status"` + } `json:"event"` +} + +const EventTypeContactScopeChange = "contact_scope_change" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "type": "contact_scope_change", // 事件类型 + "app_id": "cli_xxx", // 应用ID + "tenant_key": "xxx", //企业标识 + } +} + +*/ + +type EventContactScopeChange struct { + BaseEvent + Event struct { + Type string `json:"type"` + AppID string `json:"app_id"` + TenantKey string `json:"tenant_key"` + } `json:"event"` +} diff --git a/types/event_types/event.go b/types/event_types/event.go new file mode 100644 index 0000000..d6ef5b3 --- /dev/null +++ b/types/event_types/event.go @@ -0,0 +1,39 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event_types + +const ( + CallbackTypeUrlVerification = "url_verification" + CallbackTypeEventCallback = "event_callback" +) + +type Callback struct { + Type string `json:"type"` + Token string `json:"token"` +} + +type EventChallenge struct { + Callback + Challenge string `json:"challenge"` +} + +type BaseEvent struct { + Callback + Ts string `json:"ts"` + UUID string `json:"uuid"` + Event struct { + Type string `json:"type"` + } `json:"event"` +} diff --git a/types/event_types/group_chat_event.go b/types/event_types/group_chat_event.go new file mode 100644 index 0000000..1cf4bf7 --- /dev/null +++ b/types/event_types/group_chat_event.go @@ -0,0 +1,107 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event_types + +const EventTypeAddUserToChat = "add_user_to_chat" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_9c8609450f78d102", + "chat_id": "oc_9e9619b938c9571c1c3165681cdaead5", // 群聊的id + "operator": { + // 用户进出群的操作人。用户主动退群的话,operator 就是user自己 + "open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb", // 员工对此应用的唯一标识,同一员工对不同应用的open_id不同 + "user_id": "ca51d83b" // 即“用户ID”,仅企业自建应用会返回 + }, + "tenant_key": "736588c9260f175d", + "type": "add_user_to_chat", // 事件类型,add_user_to_chat/remove_user_from_chat/revoke_add_user_from_chat + "users": [{ + "name": "James", + "open_id": "ou_706adeb944ab1473b9fb3e7da2a40b68", + "user_id": "51g97a4g" + }, + { + "name": "张三", + "open_id": "ou_7885357f9922aaa34001b190109e0b48", + "user_id": "6e125386" + } + ] + } +} + +*/ + +type EventAddUserToChat struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + ChatID string `json:"chat_id"` + Operator struct { + OpenID string `json:"open_id"` + UserID string `json:"user_id"` + } `json:"operator"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + Users []struct { + Name string `json:"name"` + OpenID string `json:"open_id"` + UserID string `json:"user_id"` + } `json:"users"` + } `json:"event"` +} + +const EventTypeChatDisband = "chat_disband" + +/* + +{ + "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 + "uuid": "bc447199585340d1f3728d26b1c0297a", // 事件的唯一标识 + "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token + "type": "event_callback", // 此事件此处始终为event_callback + "event": { + "app_id": "cli_9c8609450f78d102", + "chat_id": "oc_9f2df3c095c9395334bb6e70ced0fa83", + "operator": { + "open_id": "ou_18eac85d35a26f989317ad4f02e8bbbb", + "user_id": "ca51d83b" + }, + "tenant_key": "736588c9260f175d", + "type": "chat_disband" + } +} + + +*/ + +type EventChatDisband struct { + BaseEvent + Event struct { + AppID string `json:"app_id"` + ChatID string `json:"chat_id"` + Operator struct { + OpenID string `json:"open_id"` + UserID string `json:"user_id"` + } `json:"operator"` + TenantKey string `json:"tenant_key"` + Type string `json:"type"` + } `json:"event"` +} diff --git a/util/aes_crypto.go b/util/aes_crypto.go new file mode 100644 index 0000000..d0d6a18 --- /dev/null +++ b/util/aes_crypto.go @@ -0,0 +1,65 @@ +// Copyright 2014 chanxuehong(chanxuehong@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "errors" +) + +func AESDecrypt(base64CipherText string, key []byte) (unpadDecrypted []byte, err error) { + sum := sha256.Sum256(key) + + key = []byte(``) + for _, b := range sum { + key = append(key, b) + } + + ciphertext, err := base64.StdEncoding.DecodeString(base64CipherText) + if err != nil { + return + } + + block, err := aes.NewCipher(key) + if err != nil { + return + } + if len(ciphertext) < aes.BlockSize { + err = errors.New("len(crypt) < aes.BlockSize") + return + } + cbc := cipher.NewCBCDecrypter(block, ciphertext[:aes.BlockSize]) + ciphertext = ciphertext[aes.BlockSize:] + decrypted := make([]byte, len(ciphertext)) + cbc.CryptBlocks(decrypted, ciphertext) + + unpadDecrypted = PKCS5Trimming(decrypted) + return +} + +func PKCS5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +func PKCS5Trimming(encrypt []byte) []byte { + padding := encrypt[len(encrypt)-1] + return encrypt[:len(encrypt)-int(padding)] +} diff --git a/util/aes_crypto_test.go b/util/aes_crypto_test.go new file mode 100644 index 0000000..9f84872 --- /dev/null +++ b/util/aes_crypto_test.go @@ -0,0 +1,74 @@ +// Copyright 2014 chanxuehong(chanxuehong@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "reflect" + "testing" +) + +func TestAESDecrypt(t *testing.T) { + type args struct { + base64CipherText string + key []byte + } + + tests := []struct { + name string + args args + wantUnpadDecrypted []byte + wantErr bool + }{ + { + name: "case1", + args: args{ + base64CipherText: "E/SxIO6PaRHpTXfcS+/PTKQcQhjJ8oFhBIHfrvuOi40lry+db6WAI9BGZFfPsLyRPYffrnUHJWvLcuPavtpiAgtB3j9AX3GBWD/n4twxP7JsqgZQ+0xWk0BOdJ7MSzt3rUmzdOQEgYQZyI5EKlDpU2yRWqLG0uk+i+aquPLzQpRvMlKWfhLXhBdNgqbztE4M036Jci/wByhAbLfO2WsLpPMRvdSdAkYK22Hr2rKkQnqfvb4oZNZmqpwUxPMJGWr/e7krKWwPc7PBAyMunl00XQogOqkTX5uBs6i1HQvUNPwa6rG3ABk14o2j4Qig0kBS3foUWwAEAd6DpsW7kmH+SFjJFsCY7pawVEYf3NMY+Gb7zLfTtuydCUG4ZUtqTPF1SrZIBtHarzy6FlT458lZdmN6BlbyDM1M9NrjLKwmLJqhBGT6JAk4/IVdGaypdFWkGIbjWXskBN4Il7+E1KwTAV9mpNvOAHyooiYrJp4qNLB5ts9uPs0JT0U23Em0Ra/GTrlaftwB9Cu7e4ePm6s5Ok0bdu/u51j+FX1UocduArfS2TIfwQ8hJ7iiqhg3Zlce0SKOwS+OfUkydxLRW637bninfb7oOuGJmaojFihz1moguVtRulOxL/1jSjbHV8bdFma9dIbVSJTNoy2cTqZowPf6Cv2WkMuE3ewdaq2SZAyOVt4tWoBBMk+fyDiPbKPoCzsdSRvmn1FQHU0HX/xPdnAYB9TnpuVpyXC5Kp/ZTekExkb+zaAuW5AI1EztzayN4PNEXNg64mz7tstHEcEti63dVULgwsDml7RNy1YDKsJm3r05XmJYTmnheXpAyPMgYt0mmxjBxlDsoOwISQ0vAEGrU1tnzIMhl/3ZEy40g/zcLzLc8cbu9KuZ2109jyiV8z9YqrI5QCe6rYcuE850HMNP00OWXkxu16jG39yWSQ0GlSr1Q562uKG+nLzvXASIFJ0huVCTrEzogxJgWEGBY3cp6t4GK7VXJdZZSlTqC0wgRNPcDxMRPMhzNGXL5bHnENrZI8ycfta+McvKBUadVkVBAvSFXmcPxhXXdGTMAYpcg8gt3j4S4gpp+2T693QRp68ks7HvJnXAMBUhX+tP5dTZ4HRNwupaZhuzf/e0Sxdi8UkcFCXIqySgwBCnmzoF", + key: []byte("lkNa48m2mvOPeMXCQU201UfoDnd3WtrR"), + }, + wantErr: false, + wantUnpadDecrypted: []byte(`{"uuid":"e5b933050c4045fce8f09a5f612c53a0","event":{"app_id":"cli_9fbc93aab56f500c","chat_type":"private","is_mention":false,"lark_version":"lark/3.29.4","message_id":"","msg_type":"text","open_chat_id":"oc_a9c45bf55957776216b5e3a998ea8a96","open_id":"ou_3eb7fcb13e6d756f46e4d910df760e85","open_message_id":"om_ea53bac0b8eaf73933fc4a640906e38c","parent_id":"","root_id":"","tenant_key":"2d54f4e2f8cf575d","text":"哈哈","text_without_at_bot":"哈哈","type":"message","user_agent":"Mozilla/5.0 (Linux; Android 10; LIO-AN00 Build/HUAWEILIO-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3770.156 Mobile Safari/537.36 Lark/3.29.4 LarkLocale/zh_CN SDK-Version/3.29.13","user_open_id":"ou_3eb7fcb13e6d756f46e4d910df760e85"},"token":"jz51vCdQz04lxVsvA7I1zbF15fexduh2","ts":"1597670902.943681","type":"event_callback"}`), + }, + { + name: "case2", + args: args{ + base64CipherText: "P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk=", + key: []byte("test key"), + }, + wantErr: false, + wantUnpadDecrypted: []byte(`hello world`), + }, + { + name: "case3", + args: args{ + base64CipherText: "FIAfJPGRmFZWkaxPQ1XrJZVbv2JwdjfLk4jx0k/U1deAqYK3AXOZ5zcHt/cC4ZNTqYwWUW/EoL+b2hW/C4zoAQQ5CeMtbxX2zHjm+E4nX/Aww+FHUL6iuIMaeL2KLxqdtbHRC50vgC2YI7xohnb3KuCNBMUzLiPeNIpVdnYaeteCmSaESb+AZpJB9PExzTpRDzCRv+T6o5vlzaE8UgIneC1sYu85BnPBEMTSuj1ZZzfdQi7ZW992Z4dmJxn9e8FL2VArNm99f5Io3c2O4AcNsQENNKtfAAxVjCqc3mg5jF0nKabA+u/5vrUD76flX1UOF5fzJ0sApG2OEn9wfyPDRBsApn9o+fceF9hNrYBGsdtZrZYyGG387CGOtKsuj8e2E8SNp+Pn4E9oYejOTR+ZNLNi+twxaXVlJhr6l+RXYwEiMGQE9zGFBD6h2dOhKh3W84p1GEYnSRIz1+9/Hp66arjC7RCrhuW5OjCj4QFEQJiwgL45XryxHtiZ7JdAlPmjVsL03CxxFZarzxzffryrWUG3VkRdHRHbTsC34+ScoL5MTDU1QAWdqUC1T7xT0lCvQELaIhBTXAYrznJl6PlA83oqlMxpHh0gZBB1jFbfoUr7OQbBs1xqzpYK6Yjux6diwpQB1zlZErYJUfCqK7G/zI9yK/60b4HW0k3M+AvzMcw=", + key: []byte("kudryavka"), + }, + wantErr: false, + wantUnpadDecrypted: []byte(`{"uuid":"5226cd85b4d843dccee2e279d93f3ed3","event":{"app_id":"cli_9e28cb7ba56a100e","before_status":{"is_active":true,"is_frozen":true,"is_resigned":false},"change_time":"2020-05-20 18:33:25","current_status":{"is_active":true,"is_frozen":false,"is_resigned":false},"employee_id":"75ge6c49","open_id":"ou_2ef04637d933f798dcb92c99e845ed09","tenant_key":"2d520d3b434f175e","type":"user_status_change"},"token":"GzhQEyfUcx7eEungQFWtXgCbxSpUOJIb","ts":"1589970805.376395","type":"event_callback"}`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotUnpadDecrypted, err := AESDecrypt(tt.args.base64CipherText, tt.args.key) + if (err != nil) != tt.wantErr { + t.Errorf("AESDecrypt() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotUnpadDecrypted, tt.wantUnpadDecrypted) { + t.Errorf("AESDecrypt() gotUnpadDecrypted = %v, \n want %v", string(gotUnpadDecrypted), tt.wantUnpadDecrypted) + } + }) + } +} diff --git a/util/randstring.go b/util/randstring.go new file mode 100644 index 0000000..3538c4d --- /dev/null +++ b/util/randstring.go @@ -0,0 +1,38 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "math/rand" + "time" +) + +const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +// GetRandStringWithCharset 获取指定字符集 下 指定长度的随机字符串 +func GetRandStringWithCharset(length int, charset string) string { + seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) + + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +// GetRandString 获取指定长度的随机字符串 +func GetRandString(length int) string { + return GetRandStringWithCharset(length, charset) +} diff --git a/util/randstring_test.go b/util/randstring_test.go new file mode 100644 index 0000000..ab6137f --- /dev/null +++ b/util/randstring_test.go @@ -0,0 +1,68 @@ +// Copyright 2020 FastWeGo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "fmt" + "testing" +) + +func TestGetRandString(t *testing.T) { + type args struct { + length int + } + tests := []struct { + name string + args args + want string + wantLen int + }{ + {name: "case1", args: args{length: 6}, wantLen: 6}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetRandString(tt.args.length) + if len(got) != tt.wantLen { + t.Errorf("GetRandString() = %v, want %v", got, tt.want) + } + + fmt.Println(got) + }) + } +} + +func TestGetRandStringWithCharset(t *testing.T) { + type args struct { + length int + charset string + } + tests := []struct { + name string + args args + want string + wantLen int + }{ + {name: "case1", args: args{length: 6, charset: "0x0x0x"}, wantLen: 6}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetRandStringWithCharset(tt.args.length, tt.args.charset) + if len(got) != tt.wantLen { + t.Errorf("GetRandStringWithCharset() = %v, want %v", got, tt.want) + } + fmt.Println(got) + }) + } +}