openim-sdk-core 0.1.3__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.
- openim_sdk_core-0.1.3/MANIFEST.in +11 -0
- openim_sdk_core-0.1.3/PKG-INFO +297 -0
- openim_sdk_core-0.1.3/README.md +275 -0
- openim_sdk_core-0.1.3/openim_sdk/__init__.py +57 -0
- openim_sdk_core-0.1.3/openim_sdk/client.py +381 -0
- openim_sdk_core-0.1.3/openim_sdk/example.py +35 -0
- openim_sdk_core-0.1.3/openim_sdk/example_ws.py +53 -0
- openim_sdk_core-0.1.3/openim_sdk/gob_codec.py +243 -0
- openim_sdk_core-0.1.3/openim_sdk/http_api.py +48 -0
- openim_sdk_core-0.1.3/openim_sdk/models.py +583 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/__init__.py +21 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/conversation/__init__.py +1 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/conversation/conversation_pb2.py +145 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/msg/__init__.py +1 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/msg/msg_pb2.py +235 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/sdkws/__init__.py +1 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/sdkws/sdkws_pb2.py +214 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/wrapperspb/__init__.py +1 -0
- openim_sdk_core-0.1.3/openim_sdk/proto/wrapperspb/wrapperspb_pb2.py +45 -0
- openim_sdk_core-0.1.3/openim_sdk/self_user_info_api.py +48 -0
- openim_sdk_core-0.1.3/openim_sdk/storage.py +178 -0
- openim_sdk_core-0.1.3/openim_sdk/ws_client.py +830 -0
- openim_sdk_core-0.1.3/openim_sdk_core.egg-info/PKG-INFO +297 -0
- openim_sdk_core-0.1.3/openim_sdk_core.egg-info/SOURCES.txt +27 -0
- openim_sdk_core-0.1.3/openim_sdk_core.egg-info/dependency_links.txt +1 -0
- openim_sdk_core-0.1.3/openim_sdk_core.egg-info/requires.txt +6 -0
- openim_sdk_core-0.1.3/openim_sdk_core.egg-info/top_level.txt +1 -0
- openim_sdk_core-0.1.3/pyproject.toml +41 -0
- openim_sdk_core-0.1.3/setup.cfg +4 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openim-sdk-core
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Python SDK for OpenIM-style login, messaging, and websocket sync.
|
|
5
|
+
Author: OpenIM Contributors
|
|
6
|
+
License-Expression: AGPL-3.0-or-later
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: protobuf<7,>=6.32.0
|
|
18
|
+
Requires-Dist: websocket-client<2,>=1.8.0
|
|
19
|
+
Provides-Extra: release
|
|
20
|
+
Requires-Dist: build>=1.2.2; extra == "release"
|
|
21
|
+
Requires-Dist: twine>=6.0.1; extra == "release"
|
|
22
|
+
|
|
23
|
+
# openim_sdk
|
|
24
|
+
|
|
25
|
+
Python SDK for OpenIM-style login, send message, and receive message.
|
|
26
|
+
|
|
27
|
+
## Modes
|
|
28
|
+
|
|
29
|
+
- `OpenIMWSSDK` (recommended): websocket direct connection (`gob + gzip + protobuf`) with heartbeat and auto reconnect.
|
|
30
|
+
- `OpenIMSDK`: HTTP polling fallback.
|
|
31
|
+
|
|
32
|
+
## WebSocket quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from openim_sdk import OpenIMWSSDK, WSConfig
|
|
36
|
+
|
|
37
|
+
sdk = OpenIMWSSDK(
|
|
38
|
+
WSConfig(ws_addr="ws://127.0.0.1:10001/msg_gateway", data_dir="./openim_py_data")
|
|
39
|
+
)
|
|
40
|
+
sdk.login(user_id="u1", token="YOUR_TOKEN")
|
|
41
|
+
sdk.start()
|
|
42
|
+
sdk.send_text("hello", recv_id="u2")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
See `openim_sdk/example_ws.py` for a runnable demo script.
|
|
46
|
+
|
|
47
|
+
## WebSocket detailed usage
|
|
48
|
+
|
|
49
|
+
### 1. Build config
|
|
50
|
+
|
|
51
|
+
`WSConfig` parameters:
|
|
52
|
+
|
|
53
|
+
- `ws_addr` (required): websocket gateway address, e.g. `ws://127.0.0.1:10001/msg_gateway`
|
|
54
|
+
- `api_addr`: HTTP API address for profile fetch (used to cache self info for send), e.g. `http://127.0.0.1:10002`; when empty, sdk falls back to env `OPENIM_API_ADDR`
|
|
55
|
+
- `data_dir`: local sqlite directory, default `./data`
|
|
56
|
+
- `platform_id`: platform ID sent to server, default `1`
|
|
57
|
+
- `timeout_seconds`: request/response wait timeout, default `1000`
|
|
58
|
+
- `pull_batch_size`: sync page size, default `100`
|
|
59
|
+
- `sdk_version`: value sent in ws query, default `python-ws-0.1.0`
|
|
60
|
+
- `auto_sync_on_connect`: run `sync_once()` immediately after first connect, default `False`
|
|
61
|
+
- `auto_sync_on_reconnect`: auto sync after reconnect, default `True`
|
|
62
|
+
- `auto_reconnect`: reconnect automatically on disconnect, default `True`
|
|
63
|
+
- `max_reconnect_attempts`: reconnect attempt cap, default `300`
|
|
64
|
+
- `reconnect_backoff_seconds`: reconnect interval sequence, default `(1, 2, 4, 8, 16)`
|
|
65
|
+
- `enable_heartbeat`: send ws ping periodically, default `True`
|
|
66
|
+
- `heartbeat_interval_seconds`: ping interval, default `24`
|
|
67
|
+
- `heartbeat_pong_timeout_seconds`: pong timeout, default `30`
|
|
68
|
+
- `self_user_info_cache_ttl_seconds`: in-memory cache ttl for self profile (`senderNickname/senderFaceURL`), default `600` seconds
|
|
69
|
+
|
|
70
|
+
### 2. Register callbacks
|
|
71
|
+
|
|
72
|
+
Available callbacks in `OpenIMWSSDK(...)`:
|
|
73
|
+
|
|
74
|
+
- `on_recv_new_message(msg: WSMessage)`: called for online real-time messages
|
|
75
|
+
- `on_recv_offline_new_message(msg: WSMessage)`: called for offline/sync messages
|
|
76
|
+
- `on_connecting()`: called before connect/reconnect attempts
|
|
77
|
+
- `on_connect_success()`: called after successful connect/reconnect
|
|
78
|
+
- `on_connect_failed(exc)`: called when one connect attempt fails
|
|
79
|
+
- `on_kicked_offline()`: called when server returns kick event
|
|
80
|
+
- `on_error(exc)`: called on internal errors (decode/send/read/reconnect, etc.)
|
|
81
|
+
- `logger(text)`: sdk log output callback
|
|
82
|
+
|
|
83
|
+
### 2.1 `WSMessage` fields
|
|
84
|
+
|
|
85
|
+
Raw payload keys:
|
|
86
|
+
|
|
87
|
+
- `conversationID`: conversation id (sdk adds this key)
|
|
88
|
+
- `sendID`, `recvID`, `groupID`
|
|
89
|
+
- `clientMsgID`, `serverMsgID`
|
|
90
|
+
- `senderPlatformID`, `senderNickname`, `senderFaceURL`
|
|
91
|
+
- `sessionType` (`1` single, `2` group, `3` super group)
|
|
92
|
+
- `msgFrom`, `contentType`, `content`
|
|
93
|
+
- `seq`, `sendTime`, `createTime`
|
|
94
|
+
- `status`, `isRead`
|
|
95
|
+
- `options` (`dict[str, bool]`)
|
|
96
|
+
- `offlinePushInfo` (`dict`)
|
|
97
|
+
- `atUserIDList` (`list[str]`)
|
|
98
|
+
- `attachedInfo`, `ex`
|
|
99
|
+
|
|
100
|
+
Python field names in `WSMessage`:
|
|
101
|
+
|
|
102
|
+
- `conversation_id`, `send_id`, `recv_id`, `group_id`
|
|
103
|
+
- `client_msg_id`, `server_msg_id`
|
|
104
|
+
- `sender_platform_id`, `sender_nickname`, `sender_face_url`
|
|
105
|
+
- `session_type`, `msg_from`, `content_type`, `content`
|
|
106
|
+
- `seq`, `send_time`, `create_time`, `status`, `is_read`
|
|
107
|
+
- `options`, `offline_push_info`, `at_user_id_list`, `attached_info`, `ex`
|
|
108
|
+
- `raw`: original dictionary payload
|
|
109
|
+
- `content_json`: parsed JSON from `content` (or `None` when not valid JSON)
|
|
110
|
+
- `content_obj`: typed content object parsed by `content_type`
|
|
111
|
+
|
|
112
|
+
`content_type -> content_obj` mapping (same as `sdk_struct.go`):
|
|
113
|
+
|
|
114
|
+
- `101` -> `TextElem`
|
|
115
|
+
- `102` -> `PictureElem`
|
|
116
|
+
- `103` -> `SoundElem`
|
|
117
|
+
- `104` -> `VideoElem`
|
|
118
|
+
- `105` -> `FileElem`
|
|
119
|
+
- `106` -> `AtTextElem`
|
|
120
|
+
- `107` -> `MergeElem`
|
|
121
|
+
- `108` -> `CardElem`
|
|
122
|
+
- `109` -> `LocationElem`
|
|
123
|
+
- `110/119/120` -> `CustomElem`
|
|
124
|
+
- `113` -> `TypingElem`
|
|
125
|
+
- `114` -> `QuoteElem`
|
|
126
|
+
- `115` -> `FaceElem`
|
|
127
|
+
- `117` -> `AdvancedTextElem`
|
|
128
|
+
- `118` -> `MarkdownTextElem`
|
|
129
|
+
- other content types -> `NotificationElem`
|
|
130
|
+
|
|
131
|
+
Minimal callback example:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from openim_sdk import OpenIMWSSDK, WSConfig, WSMessage, TextElem, PictureElem
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def on_new_model(msg: WSMessage) -> None:
|
|
138
|
+
if isinstance(msg.content_obj, TextElem):
|
|
139
|
+
print("[text]", msg.content_obj.content)
|
|
140
|
+
elif isinstance(msg.content_obj, PictureElem):
|
|
141
|
+
print("[picture]", msg.content_obj.source_picture)
|
|
142
|
+
else:
|
|
143
|
+
print("[other]", msg.content_type, msg.content_obj)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
sdk = OpenIMWSSDK(
|
|
147
|
+
WSConfig(ws_addr="ws://127.0.0.1:10001/msg_gateway"),
|
|
148
|
+
on_recv_new_message=on_new_model,
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 3. Full example (recommended pattern)
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
import time
|
|
156
|
+
|
|
157
|
+
from openim_sdk import OpenIMWSSDK, TextElem, WSConfig, WSSDKError, WSMessage
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def on_new(msg: WSMessage) -> None:
|
|
161
|
+
if isinstance(msg.content_obj, TextElem):
|
|
162
|
+
print("[new text]", msg.send_id, "->", msg.recv_id, msg.content_obj.content)
|
|
163
|
+
else:
|
|
164
|
+
print("[new]", msg.send_id, "->", msg.recv_id, msg.content_type, msg.content_obj)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def on_offline(msg: WSMessage) -> None:
|
|
168
|
+
print("[offline]", msg.client_msg_id, msg.seq)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def on_err(exc: Exception) -> None:
|
|
172
|
+
print("[error]", exc)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
sdk = OpenIMWSSDK(
|
|
176
|
+
WSConfig(
|
|
177
|
+
ws_addr="ws://127.0.0.1:10001/msg_gateway",
|
|
178
|
+
api_addr="http://127.0.0.1:10002",
|
|
179
|
+
data_dir="./openim_py_data",
|
|
180
|
+
auto_sync_on_connect=True,
|
|
181
|
+
auto_sync_on_reconnect=True,
|
|
182
|
+
),
|
|
183
|
+
on_recv_new_message=on_new,
|
|
184
|
+
on_recv_offline_new_message=on_offline,
|
|
185
|
+
on_connecting=lambda: print("[ws] connecting"),
|
|
186
|
+
on_connect_success=lambda: print("[ws] connected"),
|
|
187
|
+
on_connect_failed=lambda e: print("[ws] connect failed:", e),
|
|
188
|
+
on_kicked_offline=lambda: print("[ws] kicked offline"),
|
|
189
|
+
on_error=on_err,
|
|
190
|
+
logger=lambda s: print("[sdk]", s),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
# 1) login first
|
|
195
|
+
sdk.login(user_id="u1", token="YOUR_TOKEN")
|
|
196
|
+
# 2) then start websocket threads
|
|
197
|
+
sdk.start()
|
|
198
|
+
|
|
199
|
+
# send peer message
|
|
200
|
+
ack = sdk.send_text("hello from ws sdk", recv_id="u2")
|
|
201
|
+
print("send ack:", ack) # {"serverMsgID": "...", "clientMsgID": "...", "sendTime": ...}
|
|
202
|
+
|
|
203
|
+
# keep process alive
|
|
204
|
+
while True:
|
|
205
|
+
time.sleep(1)
|
|
206
|
+
except WSSDKError as e:
|
|
207
|
+
print("business error:", e.err_code, e.err_msg)
|
|
208
|
+
except KeyboardInterrupt:
|
|
209
|
+
pass
|
|
210
|
+
finally:
|
|
211
|
+
# stop background threads and close db
|
|
212
|
+
sdk.logout()
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 4. Sending messages
|
|
216
|
+
|
|
217
|
+
- Single chat:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
sdk.send_text("hello", recv_id="u2")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- Group chat:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
sdk.send_text("hello group", group_id="g123", session_type=2)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`send_text(...)` notes:
|
|
230
|
+
|
|
231
|
+
- must pass one of `recv_id` or `group_id`
|
|
232
|
+
- `session_type` defaults to single chat (`1`) if `recv_id` is set, otherwise group (`2`)
|
|
233
|
+
- if `WSConfig.api_addr` is set, sdk fetches self profile (`/user/get_users_info`) and caches it in memory (ttl controlled by `self_user_info_cache_ttl_seconds`, default 10 minutes), then auto-fills `senderNickname` and `senderFaceURL` on send
|
|
234
|
+
- supported session values:
|
|
235
|
+
- `1`: single chat
|
|
236
|
+
- `2`: group chat
|
|
237
|
+
- `3`: super group chat
|
|
238
|
+
|
|
239
|
+
### 5. Manual sync
|
|
240
|
+
|
|
241
|
+
If you want explicit control (instead of `auto_sync_on_connect/reconnect`), call:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
sdk.sync_once()
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
This pulls missing seq ranges and dispatches them through `on_recv_offline_new_message`.
|
|
248
|
+
|
|
249
|
+
### 6. Lifecycle and best practices
|
|
250
|
+
|
|
251
|
+
- call order should be: `login(...) -> start() -> send/recv -> logout()`
|
|
252
|
+
- `stop()` only stops ws threads; `logout()` stops threads and closes local storage
|
|
253
|
+
- keep your process alive after `start()`, otherwise daemon threads exit with process
|
|
254
|
+
- for production:
|
|
255
|
+
- keep `auto_reconnect=True`
|
|
256
|
+
- keep heartbeat enabled
|
|
257
|
+
- handle `on_error` and `on_connect_failed` for observability/retry alerts
|
|
258
|
+
- persist your own app-level offsets/state if required
|
|
259
|
+
|
|
260
|
+
## HTTP polling quick start
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from openim_sdk import OpenIMSDK, SDKConfig
|
|
264
|
+
|
|
265
|
+
sdk = OpenIMSDK(
|
|
266
|
+
SDKConfig(api_addr="http://127.0.0.1:10002", data_dir="./openim_py_data")
|
|
267
|
+
)
|
|
268
|
+
sdk.login(user_id="u1", token="YOUR_TOKEN")
|
|
269
|
+
sdk.start_receiving()
|
|
270
|
+
sdk.send_text("hello", recv_id="u2")
|
|
271
|
+
me = sdk.get_self_user_info()
|
|
272
|
+
print(me.user_id, me.nickname, me.face_url)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## WebSocket features
|
|
276
|
+
|
|
277
|
+
- Login with `user_id + token`
|
|
278
|
+
- Send text message with protobuf payload (`MsgData`)
|
|
279
|
+
- Receive `PushMessages` from ws binary frames
|
|
280
|
+
- Gob encode/decode for `GeneralWsReq/GeneralWsResp`
|
|
281
|
+
- Gzip frame compression/decompression
|
|
282
|
+
- Heartbeat (`ping/pong`)
|
|
283
|
+
- Auto reconnect with backoff (`1,2,4,8,16` seconds loop by default)
|
|
284
|
+
- Auto sync missed messages on connect/reconnect
|
|
285
|
+
- Local SQLite persistence:
|
|
286
|
+
- message rows
|
|
287
|
+
- per-conversation latest synced seq
|
|
288
|
+
|
|
289
|
+
## Local database
|
|
290
|
+
|
|
291
|
+
- WS mode: `<data_dir>/OpenIM_pyws_<user_id>.db`
|
|
292
|
+
- HTTP mode: `<data_dir>/OpenIM_py_<user_id>.db`
|
|
293
|
+
|
|
294
|
+
Tables:
|
|
295
|
+
|
|
296
|
+
- `conversation_seq(conversation_id, last_seq)`
|
|
297
|
+
- `messages(...)`
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# openim_sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for OpenIM-style login, send message, and receive message.
|
|
4
|
+
|
|
5
|
+
## Modes
|
|
6
|
+
|
|
7
|
+
- `OpenIMWSSDK` (recommended): websocket direct connection (`gob + gzip + protobuf`) with heartbeat and auto reconnect.
|
|
8
|
+
- `OpenIMSDK`: HTTP polling fallback.
|
|
9
|
+
|
|
10
|
+
## WebSocket quick start
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from openim_sdk import OpenIMWSSDK, WSConfig
|
|
14
|
+
|
|
15
|
+
sdk = OpenIMWSSDK(
|
|
16
|
+
WSConfig(ws_addr="ws://127.0.0.1:10001/msg_gateway", data_dir="./openim_py_data")
|
|
17
|
+
)
|
|
18
|
+
sdk.login(user_id="u1", token="YOUR_TOKEN")
|
|
19
|
+
sdk.start()
|
|
20
|
+
sdk.send_text("hello", recv_id="u2")
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
See `openim_sdk/example_ws.py` for a runnable demo script.
|
|
24
|
+
|
|
25
|
+
## WebSocket detailed usage
|
|
26
|
+
|
|
27
|
+
### 1. Build config
|
|
28
|
+
|
|
29
|
+
`WSConfig` parameters:
|
|
30
|
+
|
|
31
|
+
- `ws_addr` (required): websocket gateway address, e.g. `ws://127.0.0.1:10001/msg_gateway`
|
|
32
|
+
- `api_addr`: HTTP API address for profile fetch (used to cache self info for send), e.g. `http://127.0.0.1:10002`; when empty, sdk falls back to env `OPENIM_API_ADDR`
|
|
33
|
+
- `data_dir`: local sqlite directory, default `./data`
|
|
34
|
+
- `platform_id`: platform ID sent to server, default `1`
|
|
35
|
+
- `timeout_seconds`: request/response wait timeout, default `1000`
|
|
36
|
+
- `pull_batch_size`: sync page size, default `100`
|
|
37
|
+
- `sdk_version`: value sent in ws query, default `python-ws-0.1.0`
|
|
38
|
+
- `auto_sync_on_connect`: run `sync_once()` immediately after first connect, default `False`
|
|
39
|
+
- `auto_sync_on_reconnect`: auto sync after reconnect, default `True`
|
|
40
|
+
- `auto_reconnect`: reconnect automatically on disconnect, default `True`
|
|
41
|
+
- `max_reconnect_attempts`: reconnect attempt cap, default `300`
|
|
42
|
+
- `reconnect_backoff_seconds`: reconnect interval sequence, default `(1, 2, 4, 8, 16)`
|
|
43
|
+
- `enable_heartbeat`: send ws ping periodically, default `True`
|
|
44
|
+
- `heartbeat_interval_seconds`: ping interval, default `24`
|
|
45
|
+
- `heartbeat_pong_timeout_seconds`: pong timeout, default `30`
|
|
46
|
+
- `self_user_info_cache_ttl_seconds`: in-memory cache ttl for self profile (`senderNickname/senderFaceURL`), default `600` seconds
|
|
47
|
+
|
|
48
|
+
### 2. Register callbacks
|
|
49
|
+
|
|
50
|
+
Available callbacks in `OpenIMWSSDK(...)`:
|
|
51
|
+
|
|
52
|
+
- `on_recv_new_message(msg: WSMessage)`: called for online real-time messages
|
|
53
|
+
- `on_recv_offline_new_message(msg: WSMessage)`: called for offline/sync messages
|
|
54
|
+
- `on_connecting()`: called before connect/reconnect attempts
|
|
55
|
+
- `on_connect_success()`: called after successful connect/reconnect
|
|
56
|
+
- `on_connect_failed(exc)`: called when one connect attempt fails
|
|
57
|
+
- `on_kicked_offline()`: called when server returns kick event
|
|
58
|
+
- `on_error(exc)`: called on internal errors (decode/send/read/reconnect, etc.)
|
|
59
|
+
- `logger(text)`: sdk log output callback
|
|
60
|
+
|
|
61
|
+
### 2.1 `WSMessage` fields
|
|
62
|
+
|
|
63
|
+
Raw payload keys:
|
|
64
|
+
|
|
65
|
+
- `conversationID`: conversation id (sdk adds this key)
|
|
66
|
+
- `sendID`, `recvID`, `groupID`
|
|
67
|
+
- `clientMsgID`, `serverMsgID`
|
|
68
|
+
- `senderPlatformID`, `senderNickname`, `senderFaceURL`
|
|
69
|
+
- `sessionType` (`1` single, `2` group, `3` super group)
|
|
70
|
+
- `msgFrom`, `contentType`, `content`
|
|
71
|
+
- `seq`, `sendTime`, `createTime`
|
|
72
|
+
- `status`, `isRead`
|
|
73
|
+
- `options` (`dict[str, bool]`)
|
|
74
|
+
- `offlinePushInfo` (`dict`)
|
|
75
|
+
- `atUserIDList` (`list[str]`)
|
|
76
|
+
- `attachedInfo`, `ex`
|
|
77
|
+
|
|
78
|
+
Python field names in `WSMessage`:
|
|
79
|
+
|
|
80
|
+
- `conversation_id`, `send_id`, `recv_id`, `group_id`
|
|
81
|
+
- `client_msg_id`, `server_msg_id`
|
|
82
|
+
- `sender_platform_id`, `sender_nickname`, `sender_face_url`
|
|
83
|
+
- `session_type`, `msg_from`, `content_type`, `content`
|
|
84
|
+
- `seq`, `send_time`, `create_time`, `status`, `is_read`
|
|
85
|
+
- `options`, `offline_push_info`, `at_user_id_list`, `attached_info`, `ex`
|
|
86
|
+
- `raw`: original dictionary payload
|
|
87
|
+
- `content_json`: parsed JSON from `content` (or `None` when not valid JSON)
|
|
88
|
+
- `content_obj`: typed content object parsed by `content_type`
|
|
89
|
+
|
|
90
|
+
`content_type -> content_obj` mapping (same as `sdk_struct.go`):
|
|
91
|
+
|
|
92
|
+
- `101` -> `TextElem`
|
|
93
|
+
- `102` -> `PictureElem`
|
|
94
|
+
- `103` -> `SoundElem`
|
|
95
|
+
- `104` -> `VideoElem`
|
|
96
|
+
- `105` -> `FileElem`
|
|
97
|
+
- `106` -> `AtTextElem`
|
|
98
|
+
- `107` -> `MergeElem`
|
|
99
|
+
- `108` -> `CardElem`
|
|
100
|
+
- `109` -> `LocationElem`
|
|
101
|
+
- `110/119/120` -> `CustomElem`
|
|
102
|
+
- `113` -> `TypingElem`
|
|
103
|
+
- `114` -> `QuoteElem`
|
|
104
|
+
- `115` -> `FaceElem`
|
|
105
|
+
- `117` -> `AdvancedTextElem`
|
|
106
|
+
- `118` -> `MarkdownTextElem`
|
|
107
|
+
- other content types -> `NotificationElem`
|
|
108
|
+
|
|
109
|
+
Minimal callback example:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from openim_sdk import OpenIMWSSDK, WSConfig, WSMessage, TextElem, PictureElem
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def on_new_model(msg: WSMessage) -> None:
|
|
116
|
+
if isinstance(msg.content_obj, TextElem):
|
|
117
|
+
print("[text]", msg.content_obj.content)
|
|
118
|
+
elif isinstance(msg.content_obj, PictureElem):
|
|
119
|
+
print("[picture]", msg.content_obj.source_picture)
|
|
120
|
+
else:
|
|
121
|
+
print("[other]", msg.content_type, msg.content_obj)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
sdk = OpenIMWSSDK(
|
|
125
|
+
WSConfig(ws_addr="ws://127.0.0.1:10001/msg_gateway"),
|
|
126
|
+
on_recv_new_message=on_new_model,
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 3. Full example (recommended pattern)
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import time
|
|
134
|
+
|
|
135
|
+
from openim_sdk import OpenIMWSSDK, TextElem, WSConfig, WSSDKError, WSMessage
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def on_new(msg: WSMessage) -> None:
|
|
139
|
+
if isinstance(msg.content_obj, TextElem):
|
|
140
|
+
print("[new text]", msg.send_id, "->", msg.recv_id, msg.content_obj.content)
|
|
141
|
+
else:
|
|
142
|
+
print("[new]", msg.send_id, "->", msg.recv_id, msg.content_type, msg.content_obj)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def on_offline(msg: WSMessage) -> None:
|
|
146
|
+
print("[offline]", msg.client_msg_id, msg.seq)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def on_err(exc: Exception) -> None:
|
|
150
|
+
print("[error]", exc)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
sdk = OpenIMWSSDK(
|
|
154
|
+
WSConfig(
|
|
155
|
+
ws_addr="ws://127.0.0.1:10001/msg_gateway",
|
|
156
|
+
api_addr="http://127.0.0.1:10002",
|
|
157
|
+
data_dir="./openim_py_data",
|
|
158
|
+
auto_sync_on_connect=True,
|
|
159
|
+
auto_sync_on_reconnect=True,
|
|
160
|
+
),
|
|
161
|
+
on_recv_new_message=on_new,
|
|
162
|
+
on_recv_offline_new_message=on_offline,
|
|
163
|
+
on_connecting=lambda: print("[ws] connecting"),
|
|
164
|
+
on_connect_success=lambda: print("[ws] connected"),
|
|
165
|
+
on_connect_failed=lambda e: print("[ws] connect failed:", e),
|
|
166
|
+
on_kicked_offline=lambda: print("[ws] kicked offline"),
|
|
167
|
+
on_error=on_err,
|
|
168
|
+
logger=lambda s: print("[sdk]", s),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# 1) login first
|
|
173
|
+
sdk.login(user_id="u1", token="YOUR_TOKEN")
|
|
174
|
+
# 2) then start websocket threads
|
|
175
|
+
sdk.start()
|
|
176
|
+
|
|
177
|
+
# send peer message
|
|
178
|
+
ack = sdk.send_text("hello from ws sdk", recv_id="u2")
|
|
179
|
+
print("send ack:", ack) # {"serverMsgID": "...", "clientMsgID": "...", "sendTime": ...}
|
|
180
|
+
|
|
181
|
+
# keep process alive
|
|
182
|
+
while True:
|
|
183
|
+
time.sleep(1)
|
|
184
|
+
except WSSDKError as e:
|
|
185
|
+
print("business error:", e.err_code, e.err_msg)
|
|
186
|
+
except KeyboardInterrupt:
|
|
187
|
+
pass
|
|
188
|
+
finally:
|
|
189
|
+
# stop background threads and close db
|
|
190
|
+
sdk.logout()
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 4. Sending messages
|
|
194
|
+
|
|
195
|
+
- Single chat:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
sdk.send_text("hello", recv_id="u2")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- Group chat:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
sdk.send_text("hello group", group_id="g123", session_type=2)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`send_text(...)` notes:
|
|
208
|
+
|
|
209
|
+
- must pass one of `recv_id` or `group_id`
|
|
210
|
+
- `session_type` defaults to single chat (`1`) if `recv_id` is set, otherwise group (`2`)
|
|
211
|
+
- if `WSConfig.api_addr` is set, sdk fetches self profile (`/user/get_users_info`) and caches it in memory (ttl controlled by `self_user_info_cache_ttl_seconds`, default 10 minutes), then auto-fills `senderNickname` and `senderFaceURL` on send
|
|
212
|
+
- supported session values:
|
|
213
|
+
- `1`: single chat
|
|
214
|
+
- `2`: group chat
|
|
215
|
+
- `3`: super group chat
|
|
216
|
+
|
|
217
|
+
### 5. Manual sync
|
|
218
|
+
|
|
219
|
+
If you want explicit control (instead of `auto_sync_on_connect/reconnect`), call:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
sdk.sync_once()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
This pulls missing seq ranges and dispatches them through `on_recv_offline_new_message`.
|
|
226
|
+
|
|
227
|
+
### 6. Lifecycle and best practices
|
|
228
|
+
|
|
229
|
+
- call order should be: `login(...) -> start() -> send/recv -> logout()`
|
|
230
|
+
- `stop()` only stops ws threads; `logout()` stops threads and closes local storage
|
|
231
|
+
- keep your process alive after `start()`, otherwise daemon threads exit with process
|
|
232
|
+
- for production:
|
|
233
|
+
- keep `auto_reconnect=True`
|
|
234
|
+
- keep heartbeat enabled
|
|
235
|
+
- handle `on_error` and `on_connect_failed` for observability/retry alerts
|
|
236
|
+
- persist your own app-level offsets/state if required
|
|
237
|
+
|
|
238
|
+
## HTTP polling quick start
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from openim_sdk import OpenIMSDK, SDKConfig
|
|
242
|
+
|
|
243
|
+
sdk = OpenIMSDK(
|
|
244
|
+
SDKConfig(api_addr="http://127.0.0.1:10002", data_dir="./openim_py_data")
|
|
245
|
+
)
|
|
246
|
+
sdk.login(user_id="u1", token="YOUR_TOKEN")
|
|
247
|
+
sdk.start_receiving()
|
|
248
|
+
sdk.send_text("hello", recv_id="u2")
|
|
249
|
+
me = sdk.get_self_user_info()
|
|
250
|
+
print(me.user_id, me.nickname, me.face_url)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## WebSocket features
|
|
254
|
+
|
|
255
|
+
- Login with `user_id + token`
|
|
256
|
+
- Send text message with protobuf payload (`MsgData`)
|
|
257
|
+
- Receive `PushMessages` from ws binary frames
|
|
258
|
+
- Gob encode/decode for `GeneralWsReq/GeneralWsResp`
|
|
259
|
+
- Gzip frame compression/decompression
|
|
260
|
+
- Heartbeat (`ping/pong`)
|
|
261
|
+
- Auto reconnect with backoff (`1,2,4,8,16` seconds loop by default)
|
|
262
|
+
- Auto sync missed messages on connect/reconnect
|
|
263
|
+
- Local SQLite persistence:
|
|
264
|
+
- message rows
|
|
265
|
+
- per-conversation latest synced seq
|
|
266
|
+
|
|
267
|
+
## Local database
|
|
268
|
+
|
|
269
|
+
- WS mode: `<data_dir>/OpenIM_pyws_<user_id>.db`
|
|
270
|
+
- HTTP mode: `<data_dir>/OpenIM_py_<user_id>.db`
|
|
271
|
+
|
|
272
|
+
Tables:
|
|
273
|
+
|
|
274
|
+
- `conversation_seq(conversation_id, last_seq)`
|
|
275
|
+
- `messages(...)`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
__version__ = "0.1.3"
|
|
2
|
+
|
|
3
|
+
from .client import OpenIMSDK, SDKConfig, SDKError, SDKHTTPError
|
|
4
|
+
from .models import (
|
|
5
|
+
CONTENT_TYPE_NAMES,
|
|
6
|
+
AdvancedTextElem,
|
|
7
|
+
AtTextElem,
|
|
8
|
+
CardElem,
|
|
9
|
+
ContentType,
|
|
10
|
+
CustomElem,
|
|
11
|
+
FaceElem,
|
|
12
|
+
FileElem,
|
|
13
|
+
LocationElem,
|
|
14
|
+
MarkdownTextElem,
|
|
15
|
+
MergeElem,
|
|
16
|
+
NotificationElem,
|
|
17
|
+
PictureElem,
|
|
18
|
+
QuoteElem,
|
|
19
|
+
SelfUserInfo,
|
|
20
|
+
SoundElem,
|
|
21
|
+
TextElem,
|
|
22
|
+
TypingElem,
|
|
23
|
+
VideoElem,
|
|
24
|
+
WSMessage,
|
|
25
|
+
)
|
|
26
|
+
from .ws_client import OpenIMWSSDK, WSConfig, SDKError as WSSDKError
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"__version__",
|
|
30
|
+
"OpenIMSDK",
|
|
31
|
+
"SDKConfig",
|
|
32
|
+
"SDKError",
|
|
33
|
+
"SDKHTTPError",
|
|
34
|
+
"WSSDKError",
|
|
35
|
+
"WSMessage",
|
|
36
|
+
"SelfUserInfo",
|
|
37
|
+
"ContentType",
|
|
38
|
+
"CONTENT_TYPE_NAMES",
|
|
39
|
+
"TextElem",
|
|
40
|
+
"PictureElem",
|
|
41
|
+
"SoundElem",
|
|
42
|
+
"VideoElem",
|
|
43
|
+
"FileElem",
|
|
44
|
+
"AtTextElem",
|
|
45
|
+
"MergeElem",
|
|
46
|
+
"CardElem",
|
|
47
|
+
"LocationElem",
|
|
48
|
+
"CustomElem",
|
|
49
|
+
"QuoteElem",
|
|
50
|
+
"FaceElem",
|
|
51
|
+
"AdvancedTextElem",
|
|
52
|
+
"TypingElem",
|
|
53
|
+
"MarkdownTextElem",
|
|
54
|
+
"NotificationElem",
|
|
55
|
+
"OpenIMWSSDK",
|
|
56
|
+
"WSConfig",
|
|
57
|
+
]
|