lansenger-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lansenger_cli-0.1.0/LICENSE +21 -0
- lansenger_cli-0.1.0/PKG-INFO +22 -0
- lansenger_cli-0.1.0/README.md +288 -0
- lansenger_cli-0.1.0/pyproject.toml +40 -0
- lansenger_cli-0.1.0/setup.cfg +4 -0
- lansenger_cli-0.1.0/src/lansenger_cli/__init__.py +0 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/__init__.py +0 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/calendar.py +154 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/callback.py +64 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/config.py +54 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/department.py +59 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/group.py +117 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/health.py +17 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/media.py +42 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/message.py +162 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/oauth.py +73 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/staff.py +102 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/streaming.py +27 -0
- lansenger_cli-0.1.0/src/lansenger_cli/commands/todo.py +220 -0
- lansenger_cli-0.1.0/src/lansenger_cli/main.py +45 -0
- lansenger_cli-0.1.0/src/lansenger_cli/utils.py +79 -0
- lansenger_cli-0.1.0/src/lansenger_cli.egg-info/PKG-INFO +22 -0
- lansenger_cli-0.1.0/src/lansenger_cli.egg-info/SOURCES.txt +25 -0
- lansenger_cli-0.1.0/src/lansenger_cli.egg-info/dependency_links.txt +1 -0
- lansenger_cli-0.1.0/src/lansenger_cli.egg-info/entry_points.txt +2 -0
- lansenger_cli-0.1.0/src/lansenger_cli.egg-info/requires.txt +6 -0
- lansenger_cli-0.1.0/src/lansenger_cli.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lansenger PM Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lansenger-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for Lansenger (蓝信) — send messages, manage groups, staff, departments, calendars, todos, and more
|
|
5
|
+
Author: Lansenger PM Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: lansenger,蓝信,cli,chatbot,messaging,enterprise-chat
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Communications :: Chat
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: lansenger-sdk>=1.0.1
|
|
18
|
+
Requires-Dist: typer>=0.9
|
|
19
|
+
Requires-Dist: rich>=13
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Lansenger CLI
|
|
2
|
+
|
|
3
|
+
蓝信(Lansenger)命令行工具 — 在终端直接调用蓝信开放平台 API,发送消息、管理群组、查询人员/部门、操作日程与待办等。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install lansenger-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
或从源码安装:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -e .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
需要 Python ≥ 3.10。
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
### 1. 配置凭证
|
|
22
|
+
|
|
23
|
+
通过 `config set` 命令保存 app 凭证(存储在 `~/.lansenger/sdk_state.json`,密钥脱敏显示):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
lansenger config set app_id YOUR_APP_ID
|
|
27
|
+
lansenger config set app_secret YOUR_APP_SECRET
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
也可以通过环境变量配置(适合 CI/CD 或临时使用):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export LANSENGER_APP_ID=YOUR_APP_ID
|
|
34
|
+
export LANSENGER_APP_SECRET=YOUR_APP_SECRET
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
可选:设置私有部署网关地址和 OAuth2 passport 地址:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
lansenger config set api_gateway_url https://your-gateway.example.com/open/apigw
|
|
41
|
+
lansenger config set passport_url https://your-passport.example.com
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. 查看配置
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
lansenger config show
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. 健康检查
|
|
51
|
+
|
|
52
|
+
验证凭证是否正确、能否成功获取 app token:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
lansenger health check
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 命令总览
|
|
59
|
+
|
|
60
|
+
| 命令组 | 说明 | 子命令 |
|
|
61
|
+
|--------|------|--------|
|
|
62
|
+
| `config` | 管理凭证配置 | `set`, `show`, `clear` |
|
|
63
|
+
| `message` | 发送与管理消息 | `send-text`, `send-markdown`, `send-file`, `send-image-url`, `send-link-card`, `send-app-articles`, `send-app-card`, `update-dynamic-card`, `revoke`, `query-groups` |
|
|
64
|
+
| `group` | 管理群组 | `create`, `info`, `members`, `list`, `check`, `update`, `update-members` |
|
|
65
|
+
| `staff` | 查询人员信息 | `basic-info`, `detail`, `ancestors`, `id-mapping`, `org-extra-fields`, `search`, `org-info` |
|
|
66
|
+
| `department` | 查询部门信息 | `detail`, `children`, `staffs` |
|
|
67
|
+
| `calendar` | 日程操作 | `primary`, `create-schedule`, `fetch-schedule`, `delete-schedule`, `list-schedules`, `attendees`, `add-attendees`, `delete-attendees` |
|
|
68
|
+
| `todo` | 待办任务管理 | `create`, `update`, `update-status`, `delete`, `list`, `fetch-by-source`, `fetch-by-id`, `status-counts`, `executor-status`, `add-executors`, `delete-executors`, `executor-list` |
|
|
69
|
+
| `oauth` | OAuth2 用户认证 | `authorize-url`, `exchange-code`, `refresh-token`, `user-info`, `parse-callback`, `validate-state` |
|
|
70
|
+
| `callback` | 回调事件解析 | `parse-payload`, `verify-signature`, `event-types` |
|
|
71
|
+
| `media` | 媒体文件操作 | `upload`, `download`, `download-to-file` |
|
|
72
|
+
| `streaming` | 流式消息(AI 场景) | `create`, `fetch` |
|
|
73
|
+
| `health` | 连接健康检查 | `check` |
|
|
74
|
+
|
|
75
|
+
## 常用示例
|
|
76
|
+
|
|
77
|
+
### 发送消息
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 发送文本消息
|
|
81
|
+
lansenger message send-text chat123 "Hello World"
|
|
82
|
+
|
|
83
|
+
# 发送 Markdown 消息
|
|
84
|
+
lansenger message send-markdown chat123 "**Bold** text"
|
|
85
|
+
|
|
86
|
+
# 发送文件
|
|
87
|
+
lansenger message send-file chat123 /path/to/file.pdf
|
|
88
|
+
|
|
89
|
+
# 发送带图片 URL 的消息
|
|
90
|
+
lansenger message send-image-url chat123 https://example.com/photo.jpg
|
|
91
|
+
|
|
92
|
+
# 发送链接卡片
|
|
93
|
+
lansenger message send-link-card chat123 "公告标题" https://example.com --desc "点击查看详情"
|
|
94
|
+
|
|
95
|
+
# 发送应用卡片
|
|
96
|
+
lansenger message send-app-card chat123 "卡片标题" --content "正文内容" --card-link https://example.com
|
|
97
|
+
|
|
98
|
+
# 发送多条图文(appArticles)
|
|
99
|
+
lansenger message send-app-articles chat123 '{"title":"文章1","url":"https://a.com"}' '{"title":"文章2","url":"https://b.com"}'
|
|
100
|
+
|
|
101
|
+
# 群内发送并 @all
|
|
102
|
+
lansenger message send-text group123 "全员通知" --group --mention-all
|
|
103
|
+
|
|
104
|
+
# 群内 @指定人
|
|
105
|
+
lansenger message send-text group123 "请查看" --group --mention staff001 --mention staff002
|
|
106
|
+
|
|
107
|
+
# 撤回消息
|
|
108
|
+
lansenger message revoke msg001 msg002
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 群组管理
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# 创建群组
|
|
115
|
+
lansenger group create "项目群" org001 --staff staff001 --staff staff002
|
|
116
|
+
|
|
117
|
+
# 查看群信息
|
|
118
|
+
lansenger group info group123
|
|
119
|
+
|
|
120
|
+
# 查看群成员
|
|
121
|
+
lansenger group members group123
|
|
122
|
+
|
|
123
|
+
# 查看群列表
|
|
124
|
+
lansenger group list --user-token YOUR_USER_TOKEN
|
|
125
|
+
|
|
126
|
+
# 检查用户是否在群内
|
|
127
|
+
lansenger group check group123 --staff-id staff001
|
|
128
|
+
|
|
129
|
+
# 更新群信息
|
|
130
|
+
lansenger group update group123 --name "新名称" --desc "新描述"
|
|
131
|
+
|
|
132
|
+
# 添加/移除成员
|
|
133
|
+
lansenger group update-members group123 --add staff003 --remove staff001
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 人员查询
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# 查看人员基本信息
|
|
140
|
+
lansenger staff basic-info staff001
|
|
141
|
+
|
|
142
|
+
# 查看人员详细信息
|
|
143
|
+
lansenger staff detail staff001
|
|
144
|
+
|
|
145
|
+
# 搜索人员
|
|
146
|
+
lansenger staff search 张三
|
|
147
|
+
|
|
148
|
+
# 手机号/邮箱映射 staff ID
|
|
149
|
+
lansenger staff id-mapping org001 phone 13800138000
|
|
150
|
+
|
|
151
|
+
# 查看组织信息
|
|
152
|
+
lansenger staff org-info org001
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 部门查询
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 查看部门详情
|
|
159
|
+
lansenger department detail dept001
|
|
160
|
+
|
|
161
|
+
# 查看子部门
|
|
162
|
+
lansenger department children dept001
|
|
163
|
+
|
|
164
|
+
# 查看部门内人员
|
|
165
|
+
lansenger department staffs dept001
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 日程操作
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 获取主日历
|
|
172
|
+
lansenger calendar primary --user-token YOUR_USER_TOKEN
|
|
173
|
+
|
|
174
|
+
# 创建日程
|
|
175
|
+
lansenger calendar create-schedule cal001 "周会" \
|
|
176
|
+
'{"dateTime":"2026-01-01T09:00:00","timeZone":"Asia/Shanghai"}' \
|
|
177
|
+
'{"dateTime":"2026-01-01T10:00:00","timeZone":"Asia/Shanghai"}' \
|
|
178
|
+
'[{"staffId":"staff001"}]' \
|
|
179
|
+
--desc "每周例会"
|
|
180
|
+
|
|
181
|
+
# 查看日程列表
|
|
182
|
+
lansenger calendar list-schedules cal001 1735689600 1735776000 --user-token YOUR_TOKEN
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 待办任务
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# 创建待办
|
|
189
|
+
lansenger todo create "审批文档" https://app.com/doc https://app.com/doc \
|
|
190
|
+
"staff001,staff002" org001 --desc "请审批" --type 2
|
|
191
|
+
|
|
192
|
+
# 更新待办状态(11=待阅, 12=已阅, 21=待办, 22=已办)
|
|
193
|
+
lansenger todo update-status task001 22 org001
|
|
194
|
+
|
|
195
|
+
# 查看待办列表
|
|
196
|
+
lansenger todo list org001 --status 21,22
|
|
197
|
+
|
|
198
|
+
# 删除待办
|
|
199
|
+
lansenger todo delete task001 org001
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### OAuth2 用户认证
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# 生成授权 URL
|
|
206
|
+
lansenger oauth authorize-url https://yourapp.com/callback --scope basic_userinfor
|
|
207
|
+
|
|
208
|
+
# 交换 code 获取 user token
|
|
209
|
+
lansenger oauth exchange-code AUTH_CODE --redirect-uri https://yourapp.com/callback
|
|
210
|
+
|
|
211
|
+
# 刷新 user token
|
|
212
|
+
lansenger oauth refresh-token YOUR_REFRESH_TOKEN
|
|
213
|
+
|
|
214
|
+
# 获取用户信息
|
|
215
|
+
lansenger oauth user-info YOUR_USER_TOKEN
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 回调事件
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# 查看所有回调事件类型
|
|
222
|
+
lansenger callback event-types
|
|
223
|
+
|
|
224
|
+
# 解析回调数据
|
|
225
|
+
lansenger callback parse-payload ENCRYPTED_DATA --encoding-key YOUR_KEY
|
|
226
|
+
|
|
227
|
+
# 验证签名
|
|
228
|
+
lansenger callback verify-signature TIMESTAMP NONCE SIGNATURE ENCODING_KEY
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 媒体文件
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# 上传文件
|
|
235
|
+
lansenger media upload /path/to/file.pdf --media-type 3
|
|
236
|
+
|
|
237
|
+
# 下载媒体文件到本地
|
|
238
|
+
lansenger media download-to-file MEDIA_ID --output /path/to/save.pdf
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 流式消息
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
# 创建流式消息(用于 AI agent 渐进式输出)
|
|
245
|
+
lansenger streaming create user123 single stream-session-001
|
|
246
|
+
|
|
247
|
+
# 获取流式消息状态
|
|
248
|
+
lansenger streaming fetch MSG_ID
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 全局选项
|
|
252
|
+
|
|
253
|
+
| 选项 | 说明 |
|
|
254
|
+
|------|------|
|
|
255
|
+
| `--json` / `-j` | 输出原始 JSON 格式而非 rich 表格 |
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# JSON 格式输出(便于脚本处理)
|
|
259
|
+
lansenger -j staff basic-info staff001
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Shell 自动补全
|
|
263
|
+
|
|
264
|
+
typer 内置补全支持:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# 安装补全
|
|
268
|
+
lansenger --install-completion
|
|
269
|
+
|
|
270
|
+
# 查看补全脚本
|
|
271
|
+
lansenger --show-completion
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
支持 bash、zsh、fish 等主流 shell。
|
|
275
|
+
|
|
276
|
+
## 凭证安全
|
|
277
|
+
|
|
278
|
+
- 凭证存储在 `~/.lansenger/sdk_state.json`,文件权限 0600
|
|
279
|
+
- `config show` 命令对 `app_id` 和 `app_secret` 脱敏显示(`***`)
|
|
280
|
+
- 也支持环境变量 `LANSENGER_APP_ID` / `LANSENGER_APP_SECRET`,适合 CI/CD 场景
|
|
281
|
+
|
|
282
|
+
## 与 SDK 的关系
|
|
283
|
+
|
|
284
|
+
本 CLI 基于 [lansenger-sdk](https://github.com/lansenger-pm/lansenger-skills-official) 的 `LansengerSyncClient` 实现,覆盖 SDK 全部同步 API,不修改 SDK 代码。
|
|
285
|
+
|
|
286
|
+
## 许可证
|
|
287
|
+
|
|
288
|
+
MIT License
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lansenger-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI for Lansenger (蓝信) — send messages, manage groups, staff, departments, calendars, todos, and more"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Lansenger PM Team" },
|
|
13
|
+
]
|
|
14
|
+
keywords = ["lansenger", "蓝信", "cli", "chatbot", "messaging", "enterprise-chat"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Communications :: Chat",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
dependencies = [
|
|
26
|
+
"lansenger-sdk>=1.0.1",
|
|
27
|
+
"typer>=0.9",
|
|
28
|
+
"rich>=13",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=7",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
lansenger = "lansenger_cli.main:app"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import json
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from lansenger_cli.utils import get_client, output_result, output_list
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Calendar and schedule operations")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command("primary")
|
|
11
|
+
def fetch_primary_calendar(
|
|
12
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
13
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
14
|
+
):
|
|
15
|
+
client = get_client()
|
|
16
|
+
result = client.fetch_primary_calendar(user_token=user_token, user_id=user_id)
|
|
17
|
+
output_result(result, fields=[
|
|
18
|
+
"calendar_id", "summary", "description", "permissions", "role",
|
|
19
|
+
], title="Primary Calendar")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command("create-schedule")
|
|
23
|
+
def create_schedule(
|
|
24
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
25
|
+
summary: str = typer.Argument(help="Schedule summary/title"),
|
|
26
|
+
start_time: str = typer.Argument(help="Start time as JSON: '{\"dateTime\":\"2026-01-01T09:00:00\",\"timeZone\":\"Asia/Shanghai\"}'"),
|
|
27
|
+
end_time: str = typer.Argument(help="End time as JSON"),
|
|
28
|
+
attendees: str = typer.Argument(help="Attendees as JSON list: '[{\"staffId\":\"xxx\"}]'"),
|
|
29
|
+
description: str = typer.Option("", "--desc", "-d", help="Schedule description"),
|
|
30
|
+
all_day: str = typer.Option("no", "--all-day", help="yes or no"),
|
|
31
|
+
repeat_type: str = typer.Option("no", "--repeat", help="Repeat type: no, daily, weekly, monthly, yearly"),
|
|
32
|
+
reminder_type: str = typer.Option("yes", "--reminder", help="Reminder type: yes or no"),
|
|
33
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
34
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
35
|
+
):
|
|
36
|
+
client = get_client()
|
|
37
|
+
start_time_dict = json.loads(start_time)
|
|
38
|
+
end_time_dict = json.loads(end_time)
|
|
39
|
+
attendees_list = json.loads(attendees)
|
|
40
|
+
result = client.create_schedule(
|
|
41
|
+
calendar_id=calendar_id, summary=summary,
|
|
42
|
+
start_time=start_time_dict, end_time=end_time_dict,
|
|
43
|
+
attendees=attendees_list, description=description,
|
|
44
|
+
all_day=all_day, repeat_type=repeat_type,
|
|
45
|
+
reminder_type=reminder_type,
|
|
46
|
+
user_token=user_token, user_id=user_id,
|
|
47
|
+
)
|
|
48
|
+
output_result(result, fields=["schedule_id"], title="Create Schedule Result")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("fetch-schedule")
|
|
52
|
+
def fetch_schedule(
|
|
53
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
54
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
55
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
56
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
57
|
+
):
|
|
58
|
+
client = get_client()
|
|
59
|
+
result = client.fetch_schedule(
|
|
60
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
61
|
+
user_token=user_token, user_id=user_id,
|
|
62
|
+
)
|
|
63
|
+
output_result(result, fields=[
|
|
64
|
+
"schedule_id", "summary", "description", "all_day",
|
|
65
|
+
"start_time", "end_time", "creator", "rsvp_status",
|
|
66
|
+
], title="Schedule Info")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command("delete-schedule")
|
|
70
|
+
def delete_schedule(
|
|
71
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
72
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
73
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
74
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
75
|
+
):
|
|
76
|
+
client = get_client()
|
|
77
|
+
result = client.delete_schedule(
|
|
78
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
79
|
+
user_token=user_token, user_id=user_id,
|
|
80
|
+
)
|
|
81
|
+
output_result(result, fields=["schedule_id"], title="Delete Schedule Result")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command("list-schedules")
|
|
85
|
+
def fetch_schedule_list(
|
|
86
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
87
|
+
start_time: int = typer.Argument(help="Start time (unix timestamp)"),
|
|
88
|
+
end_time: int = typer.Argument(help="End time (unix timestamp)"),
|
|
89
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
90
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
91
|
+
):
|
|
92
|
+
client = get_client()
|
|
93
|
+
result = client.fetch_schedule_list(
|
|
94
|
+
calendar_id=calendar_id, start_time=start_time, end_time=end_time,
|
|
95
|
+
user_token=user_token, user_id=user_id,
|
|
96
|
+
)
|
|
97
|
+
if result.success and result.schedule_list:
|
|
98
|
+
output_list(result.schedule_list, columns=["Schedule ID", "Summary"], row_mapper=lambda s: [
|
|
99
|
+
getattr(s, "schedule_id", ""), getattr(s, "summary", ""),
|
|
100
|
+
])
|
|
101
|
+
else:
|
|
102
|
+
output_result(result, title="Schedule List")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.command("attendees")
|
|
106
|
+
def fetch_schedule_attendees(
|
|
107
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
108
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
109
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
110
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
111
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
112
|
+
page_size: int = typer.Option(500, "--size", "-s", help="Page size"),
|
|
113
|
+
):
|
|
114
|
+
client = get_client()
|
|
115
|
+
result = client.fetch_schedule_attendees(
|
|
116
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
117
|
+
user_token=user_token, user_id=user_id,
|
|
118
|
+
page=page, page_size=page_size,
|
|
119
|
+
)
|
|
120
|
+
output_result(result, fields=["total"], title="Schedule Attendees")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command("add-attendees")
|
|
124
|
+
def add_schedule_attendees(
|
|
125
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
126
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
127
|
+
attendees: str = typer.Argument(help="Attendee staff IDs as JSON list: '[\"id1\",\"id2\"]'"),
|
|
128
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
129
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
130
|
+
):
|
|
131
|
+
client = get_client()
|
|
132
|
+
attendees_list = json.loads(attendees)
|
|
133
|
+
result = client.add_schedule_attendees(
|
|
134
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
135
|
+
attendees=attendees_list, user_token=user_token, user_id=user_id,
|
|
136
|
+
)
|
|
137
|
+
output_result(result, fields=["schedule_id"], title="Add Attendees Result")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command("delete-attendees")
|
|
141
|
+
def delete_schedule_attendees(
|
|
142
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
143
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
144
|
+
attendees: str = typer.Argument(help="Attendee staff IDs as JSON list"),
|
|
145
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
146
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
147
|
+
):
|
|
148
|
+
client = get_client()
|
|
149
|
+
attendees_list = json.loads(attendees)
|
|
150
|
+
result = client.delete_schedule_attendees(
|
|
151
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
152
|
+
attendees=attendees_list, user_token=user_token, user_id=user_id,
|
|
153
|
+
)
|
|
154
|
+
output_result(result, fields=["schedule_id"], title="Delete Attendees Result")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import json
|
|
3
|
+
from rich import print as rprint
|
|
4
|
+
|
|
5
|
+
from lansenger_cli.utils import is_json_output, console
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Parse and verify callback events")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command("parse-payload")
|
|
11
|
+
def parse_callback_payload(
|
|
12
|
+
encrypted_data: str = typer.Argument(help="Encrypted callback data"),
|
|
13
|
+
encoding_key: str = typer.Option("", "--encoding-key", help="Encoding key for decryption"),
|
|
14
|
+
verify_signature: bool = typer.Option(False, "--verify-sig", help="Verify signature"),
|
|
15
|
+
timestamp: str = typer.Option("", "--timestamp", help="Timestamp for signature verification"),
|
|
16
|
+
nonce: str = typer.Option("", "--nonce", help="Nonce for signature verification"),
|
|
17
|
+
signature: str = typer.Option("", "--signature", help="Signature to verify"),
|
|
18
|
+
):
|
|
19
|
+
from lansenger_sdk import parse_callback_payload
|
|
20
|
+
events = parse_callback_payload(
|
|
21
|
+
encrypted_data,
|
|
22
|
+
encoding_key=encoding_key,
|
|
23
|
+
verify_signature=verify_signature,
|
|
24
|
+
timestamp=timestamp,
|
|
25
|
+
nonce=nonce,
|
|
26
|
+
signature=signature,
|
|
27
|
+
)
|
|
28
|
+
if is_json_output():
|
|
29
|
+
rprint(json.dumps([e.to_dict() if hasattr(e, "to_dict") else str(e) for e in events], indent=2, ensure_ascii=False))
|
|
30
|
+
return
|
|
31
|
+
for event in events:
|
|
32
|
+
rprint(f"[bold cyan]Event #{event.event_id}[/bold cyan] — {event.event_type} ({event.category})")
|
|
33
|
+
if hasattr(event, "data") and hasattr(event.data, "to_dict"):
|
|
34
|
+
rprint(json.dumps(event.data.to_dict(), indent=2, ensure_ascii=False))
|
|
35
|
+
else:
|
|
36
|
+
rprint(event.data)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command("verify-signature")
|
|
40
|
+
def verify_callback_signature(
|
|
41
|
+
timestamp: str = typer.Argument(help="Timestamp"),
|
|
42
|
+
nonce: str = typer.Argument(help="Nonce"),
|
|
43
|
+
signature: str = typer.Argument(help="Signature"),
|
|
44
|
+
encoding_key: str = typer.Argument(help="Encoding key"),
|
|
45
|
+
):
|
|
46
|
+
from lansenger_sdk import verify_callback_signature
|
|
47
|
+
valid = verify_callback_signature(timestamp, nonce, signature, encoding_key)
|
|
48
|
+
rprint(f"Signature valid: {valid}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("event-types")
|
|
52
|
+
def get_callback_event_types():
|
|
53
|
+
from lansenger_sdk import get_callback_event_types
|
|
54
|
+
types = get_callback_event_types()
|
|
55
|
+
if is_json_output():
|
|
56
|
+
rprint(json.dumps(types, indent=2, ensure_ascii=False))
|
|
57
|
+
return
|
|
58
|
+
from rich.table import Table
|
|
59
|
+
table = Table(title="Callback Event Types", show_header=True, header_style="bold cyan")
|
|
60
|
+
table.add_column("Event Type")
|
|
61
|
+
table.add_column("Category")
|
|
62
|
+
for event_type, category in types.items():
|
|
63
|
+
table.add_row(event_type, category)
|
|
64
|
+
console.print(table)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print as rprint
|
|
3
|
+
|
|
4
|
+
from lansenger_sdk import CredentialStore
|
|
5
|
+
|
|
6
|
+
from lansenger_cli.utils import get_store, output_result, is_json_output
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="Manage CLI configuration (credentials, tokens)")
|
|
9
|
+
|
|
10
|
+
VALID_KEYS = ["app_id", "app_secret", "api_gateway_url", "passport_url"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command("set")
|
|
14
|
+
def config_set(
|
|
15
|
+
key: str = typer.Argument(help=f"Config key: {', '.join(VALID_KEYS)}"),
|
|
16
|
+
value: str = typer.Argument(help="Config value"),
|
|
17
|
+
):
|
|
18
|
+
if key not in VALID_KEYS:
|
|
19
|
+
rprint(f"[red]Error:[/red] Invalid key '{key}'. Valid keys: {', '.join(VALID_KEYS)}")
|
|
20
|
+
raise typer.Exit(1)
|
|
21
|
+
store = get_store()
|
|
22
|
+
creds = store.load_credentials()
|
|
23
|
+
creds[key] = value
|
|
24
|
+
store.save_credentials(
|
|
25
|
+
app_id=creds.get("app_id", ""),
|
|
26
|
+
app_secret=creds.get("app_secret", ""),
|
|
27
|
+
api_gateway_url=creds.get("api_gateway_url", ""),
|
|
28
|
+
passport_url=creds.get("passport_url", ""),
|
|
29
|
+
)
|
|
30
|
+
rprint(f"[green]Set[/green] {key} = {value}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command("show")
|
|
34
|
+
def config_show():
|
|
35
|
+
store = get_store()
|
|
36
|
+
if is_json_output():
|
|
37
|
+
rprint(store.load())
|
|
38
|
+
return
|
|
39
|
+
creds = store.load_credentials()
|
|
40
|
+
has = store.has_credentials()
|
|
41
|
+
full = store.has_full_config()
|
|
42
|
+
rprint(f"Credentials configured: {has}")
|
|
43
|
+
rprint(f"Full config available: {full}")
|
|
44
|
+
rprint(f"Store path: {store.path}")
|
|
45
|
+
for k, v in creds.items():
|
|
46
|
+
display = v if k in ("api_gateway_url", "passport_url") else ("***" if v else "(empty)")
|
|
47
|
+
rprint(f" {k}: {display}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command("clear")
|
|
51
|
+
def config_clear():
|
|
52
|
+
store = get_store()
|
|
53
|
+
store.clear()
|
|
54
|
+
rprint("[green]Cleared[/green] all stored configuration.")
|