lansenger-sdk 1.0.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_sdk-1.0.0/PKG-INFO +458 -0
- lansenger_sdk-1.0.0/README.md +435 -0
- lansenger_sdk-1.0.0/pyproject.toml +44 -0
- lansenger_sdk-1.0.0/setup.cfg +4 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/__init__.py +340 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/account_messages.py +121 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/auth.py +100 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/calendars.py +477 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/callbacks.py +500 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/client.py +2586 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/config.py +78 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/constants.py +105 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/contacts.py +455 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/departments.py +209 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/exceptions.py +45 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/group_messages.py +126 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/groups.py +529 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/media.py +161 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/models.py +878 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/oauth.py +312 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/persistence.py +149 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/py.typed +0 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/streaming.py +146 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/sync_client.py +1348 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/todos.py +577 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/user_messages.py +116 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk/users.py +97 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk.egg-info/PKG-INFO +458 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk.egg-info/SOURCES.txt +48 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk.egg-info/dependency_links.txt +1 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk.egg-info/requires.txt +5 -0
- lansenger_sdk-1.0.0/src/lansenger_sdk.egg-info/top_level.txt +1 -0
- lansenger_sdk-1.0.0/tests/test_account_messages.py +154 -0
- lansenger_sdk-1.0.0/tests/test_bot_message.py +41 -0
- lansenger_sdk-1.0.0/tests/test_calendars.py +374 -0
- lansenger_sdk-1.0.0/tests/test_callbacks.py +439 -0
- lansenger_sdk-1.0.0/tests/test_client.py +282 -0
- lansenger_sdk-1.0.0/tests/test_config.py +66 -0
- lansenger_sdk-1.0.0/tests/test_constants.py +93 -0
- lansenger_sdk-1.0.0/tests/test_contacts.py +316 -0
- lansenger_sdk-1.0.0/tests/test_departments.py +255 -0
- lansenger_sdk-1.0.0/tests/test_exceptions.py +57 -0
- lansenger_sdk-1.0.0/tests/test_group_messages.py +233 -0
- lansenger_sdk-1.0.0/tests/test_groups.py +403 -0
- lansenger_sdk-1.0.0/tests/test_models.py +179 -0
- lansenger_sdk-1.0.0/tests/test_oauth.py +328 -0
- lansenger_sdk-1.0.0/tests/test_persistence.py +146 -0
- lansenger_sdk-1.0.0/tests/test_streaming.py +227 -0
- lansenger_sdk-1.0.0/tests/test_todos.py +340 -0
- lansenger_sdk-1.0.0/tests/test_user_messages.py +183 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lansenger-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Framework-independent Python SDK for Lansenger (蓝信) Smart Bot API — send messages, files, images, cards, manage messages
|
|
5
|
+
Author: Lansenger PM Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lansenger-pm/lansenger-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/lansenger-pm/lansenger-sdk
|
|
9
|
+
Keywords: lansenger,蓝信,chatbot,messaging,sdk,agent,enterprise-chat
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Communications :: Chat
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: httpx>=0.24
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
23
|
+
|
|
24
|
+
[English](README.md) | [简体中文](README.zhHans.md) | [繁体中文](README.zhHant.md) | [繁体中文香港](README.zhHantHK.md) | [Français](README.fr.md)
|
|
25
|
+
|
|
26
|
+
# lansenger-sdk
|
|
27
|
+
|
|
28
|
+
Framework-independent Python SDK for the Lansenger (蓝信) platform — supports Lansenger apps, organization bots, and personal bots.
|
|
29
|
+
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
[](https://www.python.org/)
|
|
32
|
+
[](https://github.com/lansenger-pm/lansenger-skills-official)
|
|
33
|
+
|
|
34
|
+
> 💠 Zero framework dependencies — only `httpx`. Works with any async or sync Python codebase.
|
|
35
|
+
|
|
36
|
+
## Supported Bot Types
|
|
37
|
+
|
|
38
|
+
| Bot Type | Auth | WebSocket Inbound | All APIs |
|
|
39
|
+
|----------|------|-------------------|----------|
|
|
40
|
+
| **Lansenger App** | appToken + userToken | ✗ (uses webhook) | ✓ |
|
|
41
|
+
| **Organization Bot** | appToken + userToken | ✗ (uses webhook) | ✓ |
|
|
42
|
+
| **Personal Bot** | appToken | ✓ (WebSocket) | ✓ (limited for non-bot APIs) |
|
|
43
|
+
|
|
44
|
+
All three bot types use the same auth mechanism: `appToken` is required for every API call; `userToken` is only needed for specific user-level operations (user info, staff search, calendar, etc.).
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **Async & Sync clients** — `LansengerClient` (async) + `LansengerSyncClient` (blocking)
|
|
49
|
+
- **Credential & token persistence** — `CredentialStore` saves app_id, app_secret, URLs, appToken, userToken to file (survives restarts)
|
|
50
|
+
- **OAuth2 user authentication** — authorize URL, code exchange, token refresh
|
|
51
|
+
- **Organization & departments** — org info, department detail/children/staff
|
|
52
|
+
- **Staff & contacts** — basic/detailed info, ID mapping, department ancestors, search
|
|
53
|
+
- **Messaging** — 3 private chat channels (bot, official account, user impersonate) + group chat, all message types, @mention, human/bot sender identity
|
|
54
|
+
- **Rich cards** — appCard (with dynamic status updates), oacard, linkCard, verifyCard, appArticles
|
|
55
|
+
- **Streaming messages** — SSE-based real-time delivery for AI agents
|
|
56
|
+
- **Media upload/download** — files, images, videos with auto type detection
|
|
57
|
+
- **Message management** — revoke, dynamic card update
|
|
58
|
+
- **Groups** — create, info, members, list, membership check, update settings & members
|
|
59
|
+
- **Calendar & schedule** — primary calendar, schedule CRUD, attendee management
|
|
60
|
+
- **Unified todo** — create, update, delete, query, executor management, status counts
|
|
61
|
+
- **Callback events** — 25 event types, structured data parsing, signature verification
|
|
62
|
+
|
|
63
|
+
## Quick Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install lansenger-sdk
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For development:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install -e ".[dev]"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 1. Authentication
|
|
76
|
+
|
|
77
|
+
### appToken — Required for all API calls
|
|
78
|
+
|
|
79
|
+
Every SDK method requires `appToken`. The client automatically obtains and refreshes it using your `app_id` + `app_secret`. You never need to manage appToken manually — the `TokenManager` handles the lifecycle:
|
|
80
|
+
|
|
81
|
+
1. **First call** → `GET /v1/apptoken/create` with app_id + app_secret → returns `appToken` (valid 2 hours)
|
|
82
|
+
2. **Subsequent calls** → reuse cached appToken until expiry
|
|
83
|
+
3. **Token expired** → automatically refresh via the same endpoint
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# appToken is managed automatically — just configure app_id + app_secret
|
|
87
|
+
client = LansengerClient(app_id="your-appid", app_secret="your-secret")
|
|
88
|
+
|
|
89
|
+
# You can also get/invalidate token manually
|
|
90
|
+
token = await client.get_token()
|
|
91
|
+
client.invalidate_token() # force refresh on next call
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### userToken — Only needed for specific endpoints
|
|
95
|
+
|
|
96
|
+
`userToken` represents a specific Lansenger user's authorization (obtained via OAuth2). It's only required for:
|
|
97
|
+
- User-level information (fetch_user_info, fetch_staff_detail, search_staff)
|
|
98
|
+
- Calendar & schedule operations (fetch_primary_calendar, create_schedule, etc.)
|
|
99
|
+
- Group operations as a human sender
|
|
100
|
+
|
|
101
|
+
### Getting Credentials
|
|
102
|
+
|
|
103
|
+
| Bot Type | How to get app_id + app_secret |
|
|
104
|
+
|----------|--------------------------------|
|
|
105
|
+
| **Personal Bot** | Lansenger desktop → Contacts → Smart Bots → Personal Bots → click ℹ️ icon (mobile client does NOT show credentials) |
|
|
106
|
+
| **Lansenger App** | Create at [Lansenger Developer Center](https://dev.lanxin.cn) — may require organization admin approval |
|
|
107
|
+
| **Organization Bot** | Create at [Lansenger Developer Center](https://dev.lanxin.cn) — may require organization admin approval |
|
|
108
|
+
|
|
109
|
+
### OAuth2 user-level auth
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# Build authorize URL — redirect user to Lansenger passport
|
|
113
|
+
url = client.build_authorize_url(redirect_uri="https://myapp.com/callback")
|
|
114
|
+
|
|
115
|
+
# After user authorizes, exchange code for userToken + refreshToken
|
|
116
|
+
token_result = await client.exchange_code(code="auth_code_from_callback")
|
|
117
|
+
|
|
118
|
+
# Refresh expired userToken
|
|
119
|
+
new_token = await client.refresh_user_token(refresh_token=token_result.refresh_token)
|
|
120
|
+
|
|
121
|
+
# Fetch user profile
|
|
122
|
+
user_info = await client.fetch_user_info(user_token=token_result.user_token)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 2. Organization & Departments
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
# Organization info
|
|
129
|
+
org = await client.fetch_org_info(org_id="orgId")
|
|
130
|
+
|
|
131
|
+
# Department hierarchy
|
|
132
|
+
detail = await client.fetch_department_detail(department_id="deptId")
|
|
133
|
+
children = await client.fetch_department_children(department_id="deptId")
|
|
134
|
+
staffs = await client.fetch_department_staffs(department_id="deptId")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 3. Staff & Contacts
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# Basic staff info
|
|
141
|
+
staff = await client.fetch_staff_basic_info(staff_id="staffOpenId")
|
|
142
|
+
|
|
143
|
+
# Detailed profile (userToken recommended)
|
|
144
|
+
detail = await client.fetch_staff_detail(staff_id="staffOpenId", user_token="ut")
|
|
145
|
+
|
|
146
|
+
# Map phone → staffId
|
|
147
|
+
mapping = await client.fetch_staff_id_mapping(
|
|
148
|
+
org_id="orgId", id_type="mobile", id_value="13800138000"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Department ancestors for a staff member
|
|
152
|
+
ancestors = await client.fetch_department_ancestors(staff_id="staffOpenId")
|
|
153
|
+
|
|
154
|
+
# Search staff (requires userToken or userId)
|
|
155
|
+
results = await client.search_staff(keyword="Zhang San", user_token="ut")
|
|
156
|
+
|
|
157
|
+
# Org extra field IDs
|
|
158
|
+
fields = await client.fetch_org_extra_field_ids(org_id="orgId")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 4. Messaging & Media
|
|
162
|
+
|
|
163
|
+
#### Bot private chat — most common
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
result = await client.send_text(chat_id="staff123", content="Hello!")
|
|
167
|
+
result = await client.send_markdown(chat_id="staff123", content="**Bold**")
|
|
168
|
+
result = await client.send_file(chat_id="staff123", file_path="/path/to/report.pdf")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Public account channel
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
result = await client.send_account_message(
|
|
175
|
+
msg_type="text", msg_data={"text": {"content": "System notice"}},
|
|
176
|
+
chat_ids=["staff1", "staff2"], account_id="524288-xxxx",
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### User impersonate channel (requires userToken)
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
result = await client.send_user_message(
|
|
184
|
+
receiver_id="staff456", msg_type="text",
|
|
185
|
+
msg_data={"text": {"content": "Hello"}},
|
|
186
|
+
user_token="ut", # required
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Group chat
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# Bot → group
|
|
194
|
+
result = await client.send_text(chat_id="group123", content="Notice", is_group=True)
|
|
195
|
+
|
|
196
|
+
# Human → group (with userToken)
|
|
197
|
+
result = await client.send_group_message(
|
|
198
|
+
group_id="group123", msg_type="text",
|
|
199
|
+
msg_data={"text": {"content": "I'll handle it"}},
|
|
200
|
+
user_token="ut",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Group chat supports ALL message types (text, formatText, oacard, appCard, linkCard, etc.)
|
|
204
|
+
result = await client.send_group_message(
|
|
205
|
+
group_id="group123", msg_type="appCard",
|
|
206
|
+
msg_data={"appCard": {"bodyTitle": "Approval", "isDynamic": True}},
|
|
207
|
+
user_token="ut",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# @mention in group
|
|
211
|
+
result = await client.send_text(
|
|
212
|
+
chat_id="group123", content="Important!", is_group=True, reminder_all=True,
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Rich cards
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
result = await client.send_app_card(chat_id="staff123", body_title="Approval", is_dynamic=True)
|
|
220
|
+
result = await client.send_link_card(chat_id="staff123", title="Article", link="https://...")
|
|
221
|
+
result = await client.send_app_articles(chat_id="staff123", articles=[...])
|
|
222
|
+
|
|
223
|
+
# Update dynamic card status
|
|
224
|
+
result = await client.update_dynamic_card(msg_id="msg123", is_last_update=True)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### Streaming messages (for AI agents)
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
result = await client.create_stream_message(receiver_id="staff1", receiver_type="staff", stream_id="s1")
|
|
231
|
+
result = await client.fetch_stream_message(msg_id="msg123")
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### Media
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# Upload
|
|
238
|
+
upload = await client.upload_media(file_path="/path/to/file.pdf")
|
|
239
|
+
|
|
240
|
+
# Download
|
|
241
|
+
download = await client.download_media(media_id="media123")
|
|
242
|
+
|
|
243
|
+
# Revoke messages
|
|
244
|
+
result = await client.revoke_message(message_ids=["msg1", "msg2"])
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## 5. Groups
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
# Create group
|
|
251
|
+
group = await client.create_group(name="Project Chat", org_id="orgId", staff_id_list=["s1","s2","s3"])
|
|
252
|
+
|
|
253
|
+
# Fetch info & members
|
|
254
|
+
info = await client.fetch_group_info(group_id="groupOpenId")
|
|
255
|
+
members = await client.fetch_group_members(group_id="groupOpenId")
|
|
256
|
+
groups = await client.fetch_group_list()
|
|
257
|
+
|
|
258
|
+
# Check membership
|
|
259
|
+
result = await client.check_is_in_group(group_id="groupOpenId", staff_id="staff1")
|
|
260
|
+
|
|
261
|
+
# Update settings
|
|
262
|
+
await client.update_group_info(group_id="groupId", name="New Name", manage_mode=1)
|
|
263
|
+
|
|
264
|
+
# Add/remove members
|
|
265
|
+
await client.update_group_members(
|
|
266
|
+
group_id="groupId", add_user_list=["staff4"], del_user_list=["staff3"],
|
|
267
|
+
)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## 6. Calendar & Schedule
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
# Get primary calendar (requires userToken or userId)
|
|
274
|
+
cal = await client.fetch_primary_calendar(user_token="ut")
|
|
275
|
+
|
|
276
|
+
# Create schedule
|
|
277
|
+
schedule = await client.create_schedule(
|
|
278
|
+
calendar_id=cal.calendar_id, summary="Team Meeting",
|
|
279
|
+
start_time={"date": "2024-01-15", "time": "10:00", "timeZone": "Asia/Shanghai"},
|
|
280
|
+
end_time={"date": "2024-01-15", "time": "11:00", "timeZone": "Asia/Shanghai"},
|
|
281
|
+
attendees=[{"staffId": "staff1", "attendeeFlag": "required"}],
|
|
282
|
+
user_token="ut",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Fetch/delete schedule
|
|
286
|
+
info = await client.fetch_schedule(calendar_id="cal1", schedule_id="sch1", user_token="ut")
|
|
287
|
+
await client.delete_schedule(calendar_id="cal1", schedule_id="sch1", user_token="ut")
|
|
288
|
+
|
|
289
|
+
# Schedule list in time range (max 42 days)
|
|
290
|
+
schedules = await client.fetch_schedule_list(
|
|
291
|
+
calendar_id="cal1", start_time=1705276800000, end_time=1707940800000, user_token="ut",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Attendee management
|
|
295
|
+
attendees = await client.fetch_schedule_attendees(calendar_id="cal1", schedule_id="sch1", user_token="ut")
|
|
296
|
+
await client.add_schedule_attendees(calendar_id="cal1", schedule_id="sch1", attendees=["staff2"], user_token="ut")
|
|
297
|
+
await client.delete_schedule_attendees(calendar_id="cal1", schedule_id="sch1", attendees=["staff2"], user_token="ut")
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## 7. Unified Todo
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from lansenger_sdk import TODO_TYPE_APPROVAL, TODO_TODO_STATUS_DONE
|
|
304
|
+
|
|
305
|
+
# Create todo task
|
|
306
|
+
todo = await client.create_todo_task(
|
|
307
|
+
title="Approval Request", link="https://app.com/a/1", pc_link="https://pc.app.com/a/1",
|
|
308
|
+
executor_ids=["staff1"], org_id="org1", type=TODO_TYPE_APPROVAL,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Update status (11=pending-read, 12=read, 21=pending-do, 22=done)
|
|
312
|
+
await client.update_todo_task_status(todotask_id="taskId", status=TODO_TODO_STATUS_DONE, org_id="org1")
|
|
313
|
+
|
|
314
|
+
# Update content
|
|
315
|
+
await client.update_todo_task(todotask_id="taskId", title="Updated", link="l", pc_link="p", org_id="org1")
|
|
316
|
+
|
|
317
|
+
# Delete (sender only)
|
|
318
|
+
await client.delete_todo_task(todotask_id="taskId", org_id="org1")
|
|
319
|
+
|
|
320
|
+
# Query
|
|
321
|
+
list_result = await client.fetch_todo_task_list(org_id="org1")
|
|
322
|
+
task = await client.fetch_todo_task_by_id(todotask_id="taskId", org_id="org1")
|
|
323
|
+
task = await client.fetch_todo_task_by_source_id(source_id="src1", org_id="org1")
|
|
324
|
+
counts = await client.fetch_todo_task_status_counts(staff_id="staff1", org_id="org1")
|
|
325
|
+
|
|
326
|
+
# Executor management
|
|
327
|
+
await client.add_executors(executor_ids=["staff2"], org_id="org1", todotask_id="taskId")
|
|
328
|
+
await client.delete_executors(executor_ids=["staff2"], org_id="org1", todotask_id="taskId")
|
|
329
|
+
executors = await client.fetch_executor_list(todotask_id="taskId", org_id="org1")
|
|
330
|
+
await client.update_executor_status(
|
|
331
|
+
executor_status_list=[{"executorId": "staff1", "todotaskId": "taskId", "status": "22"}],
|
|
332
|
+
org_id="org1",
|
|
333
|
+
)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## 8. Callback Events
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
from lansenger_sdk import parse_callback_payload, verify_callback_signature
|
|
340
|
+
|
|
341
|
+
# Parse webhook payload
|
|
342
|
+
events = parse_callback_payload(encrypted_data, encoding_key="your_key")
|
|
343
|
+
|
|
344
|
+
# Verify signature
|
|
345
|
+
is_valid = verify_callback_signature(timestamp, nonce, signature, encoding_key)
|
|
346
|
+
|
|
347
|
+
# Available event types
|
|
348
|
+
types = client.get_callback_event_types() # 26 event types across 14 categories
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Message Type Capability Matrix
|
|
352
|
+
|
|
353
|
+
| msgType | Markdown | @mention | Attachments | Private Channels | Group Chat | Notes |
|
|
354
|
+
|---------|----------|----------|-------------|------------------|------------|-------|
|
|
355
|
+
| `text` | ✗ | ✓ (group) | ✓ | Bot, Official Account, User Impersonate | ✓ | Up to 6000 bytes |
|
|
356
|
+
| `formatText` | ✓ | ✗ | ✗ | User Impersonate only | ✓ | Markdown via formatType=1 |
|
|
357
|
+
| `oacard` | ✗ | ✗ | ✗ | Bot, Official Account, User Impersonate | ✓ | Simple card with fields |
|
|
358
|
+
| `appCard` | ✓ (div tags) | ✗ | ✗ | Bot, Official Account, User Impersonate | ✓ | Rich card, dynamic updates |
|
|
359
|
+
| `linkCard` | ✗ | ✗ | ✗ | Bot, Official Account | ✓ | Link preview card |
|
|
360
|
+
| `appArticles` | ✗ | ✗ | ✗ | Bot private only | ✓ | Article list (1+ articles) |
|
|
361
|
+
| `verifyCard` | ✗ | ✗ | ✗ | Bot, Official Account | ✓ | Verification card with buttons |
|
|
362
|
+
| `i18nAppCard` | ✓ (div tags) | ✗ | ✗ | Bot, Official Account, User Impersonate | ✓ | Multilingual appCard (zhHans/zhHant/zhHantHK/en/fr) |
|
|
363
|
+
| `i18nSystemAction` | ✗ | ✗ | ✗ | Platform internal | ✓ | Multilingual systemAction |
|
|
364
|
+
| `i18nSystem` | ✗ | ✗ | ✗ | Platform internal | ✓ | Multilingual system message |
|
|
365
|
+
|
|
366
|
+
**Group chat** supports all message types. Only group chat supports @mention.
|
|
367
|
+
|
|
368
|
+
## Configuration
|
|
369
|
+
|
|
370
|
+
### Environment Variables
|
|
371
|
+
|
|
372
|
+
| Variable | Required | Description | Default |
|
|
373
|
+
|----------|----------|-------------|---------|
|
|
374
|
+
| `LANSENGER_APP_ID` | ✓ | App/Bot ID | — |
|
|
375
|
+
| `LANSENGER_APP_SECRET` | ✓ | App/Bot Secret | — |
|
|
376
|
+
| `LANSENGER_API_GATEWAY_URL` | ✗ | API Gateway URL | `https://open.e.lanxin.cn/open/apigw` |
|
|
377
|
+
| `LANSENGER_PASSPORT_URL` | ✗ | Passport URL (for OAuth2) | — |
|
|
378
|
+
|
|
379
|
+
### Credential & Token Persistence
|
|
380
|
+
|
|
381
|
+
By default, credentials and tokens stay in memory only (lost on process exit). Enable file persistence with `store_path`:
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
from lansenger_sdk import LansengerClient, CredentialStore
|
|
385
|
+
|
|
386
|
+
# Auto-persist to ~/.lansenger/sdk_state.json (0600 permissions)
|
|
387
|
+
client = LansengerClient(app_id="...", app_secret="...", store_path="~/.lansenger/sdk_state.json")
|
|
388
|
+
|
|
389
|
+
# Or from env with persistence
|
|
390
|
+
client = LansengerClient.from_env(store_path="~/.lansenger/sdk_state.json")
|
|
391
|
+
|
|
392
|
+
# Manual store operations
|
|
393
|
+
store = CredentialStore(path="~/.lansenger/sdk_state.json")
|
|
394
|
+
store.save_credentials("app_id", "app_secret", api_gateway_url="...", passport_url="...")
|
|
395
|
+
store.save_user_token("user_token", refresh_token="refresh_token")
|
|
396
|
+
token = store.load_app_token() # None if expired
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
When persistence is enabled:
|
|
400
|
+
- **appToken** is saved after each API fetch and restored on restart (skips redundant API calls)
|
|
401
|
+
- **userToken + refreshToken** are saved after OAuth2 exchange
|
|
402
|
+
- **Credentials + URLs** are saved together for full config recovery
|
|
403
|
+
|
|
404
|
+
### Sync Client
|
|
405
|
+
|
|
406
|
+
All methods available on `LansengerSyncClient` with identical signatures (blocking):
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
from lansenger_sdk import LansengerSyncClient
|
|
410
|
+
|
|
411
|
+
client = LansengerSyncClient.from_env()
|
|
412
|
+
result = client.send_text(chat_id="staff123", content="Hello!")
|
|
413
|
+
org = client.fetch_org_info(org_id="orgId")
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Project Structure
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
lansenger-skills-official/
|
|
420
|
+
├── src/lansenger_sdk/
|
|
421
|
+
│ ├── __init__.py # All exports
|
|
422
|
+
│ ├── client.py # LansengerClient (async)
|
|
423
|
+
│ ├── sync_client.py # LansengerSyncClient (sync)
|
|
424
|
+
│ ├── config.py # LansengerConfig
|
|
425
|
+
│ ├── auth.py # TokenManager — appToken lifecycle
|
|
426
|
+
│ ├── oauth.py # OAuth2 helpers
|
|
427
|
+
│ ├── constants.py # API endpoints, media types, OAuth scopes
|
|
428
|
+
│ ├── exceptions.py # LansengerError hierarchy
|
|
429
|
+
│ ├── models.py # 35+ dataclass result types
|
|
430
|
+
│ ├── contacts.py # Staff & org info APIs
|
|
431
|
+
│ ├── departments.py # Department APIs
|
|
432
|
+
│ ├── account_messages.py # Public account channel
|
|
433
|
+
│ ├── user_messages.py # User impersonate channel
|
|
434
|
+
│ ├── group_messages.py # Group chat channel
|
|
435
|
+
│ ├── media.py # Upload/download
|
|
436
|
+
│ ├── streaming.py # SSE streaming
|
|
437
|
+
│ ├── persistence.py # CredentialStore — file-based token & credential persistence
|
|
438
|
+
│ ├── callbacks.py # Callback events
|
|
439
|
+
│ ├── groups.py # Group APIs
|
|
440
|
+
│ ├── todos.py # Unified Todo
|
|
441
|
+
│ ├── calendars.py # Calendar & Schedule
|
|
442
|
+
│ └── users.py # User info
|
|
443
|
+
├── tests/ # 296 tests, all passing
|
|
444
|
+
├── skills/ # 9 skill docs + manifest
|
|
445
|
+
├── pyproject.toml
|
|
446
|
+
└── README*.md # 5-language READMEs
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Development
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
pip install -e ".[dev]"
|
|
453
|
+
pytest tests/ -v
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## License
|
|
457
|
+
|
|
458
|
+
MIT — see [LICENSE](LICENSE).
|