imbot-sdk-python 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,463 @@
1
+ """Concrete message content models.
2
+
3
+ Each class serializes to a stable JSON shape, so messages sent from this SDK
4
+ are interchangeable with the other JuggleIM client SDKs.
5
+ """
6
+
7
+ import json
8
+ from typing import Dict, List, Optional
9
+
10
+ from ..models import (
11
+ Conversation,
12
+ UserInfo,
13
+ DEFAULT_MESSAGE_FLAGS,
14
+ MESSAGE_CONTENT_TYPE_UNKNOWN,
15
+ )
16
+ from .base import (
17
+ MessageContent,
18
+ _json_bytes,
19
+ MESSAGE_CONTENT_TYPE_TEXT,
20
+ MESSAGE_CONTENT_TYPE_IMAGE,
21
+ MESSAGE_CONTENT_TYPE_FILE,
22
+ MESSAGE_CONTENT_TYPE_VIDEO,
23
+ MESSAGE_CONTENT_TYPE_VOICE,
24
+ MESSAGE_CONTENT_TYPE_STREAM_TEXT,
25
+ MESSAGE_CONTENT_TYPE_RECALL_INFO,
26
+ MESSAGE_CONTENT_TYPE_MERGE,
27
+ MESSAGE_CONTENT_TYPE_THUMBNAIL_PACKED_IMAGE,
28
+ MESSAGE_CONTENT_TYPE_SNAPSHOT_PACKED_VIDEO,
29
+ )
30
+
31
+
32
+ class TextMessage(MessageContent):
33
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_TEXT
34
+
35
+ def __init__(self, content: str = "", extra: str = "") -> None:
36
+ self.content = content
37
+ self.extra = extra
38
+
39
+ def encode(self) -> bytes:
40
+ obj = {}
41
+ if self.content:
42
+ obj["content"] = self.content
43
+ if self.extra:
44
+ obj["extra"] = self.extra
45
+ return _json_bytes(obj)
46
+
47
+ def decode(self, data: bytes) -> None:
48
+ obj = json.loads(data or b"{}")
49
+ self.content = obj.get("content", "")
50
+ self.extra = obj.get("extra", "")
51
+
52
+ def conversation_digest(self) -> str:
53
+ return self.content
54
+
55
+ def get_search_content(self) -> str:
56
+ return self.content
57
+
58
+
59
+ class ImageMessage(MessageContent):
60
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_IMAGE
61
+
62
+ def __init__(self) -> None:
63
+ self.url = ""
64
+ self.local_path = ""
65
+ self.thumbnail_url = ""
66
+ self.thumbnail_local_path = ""
67
+ self.height = 0
68
+ self.width = 0
69
+ self.extra = ""
70
+ self.size = 0
71
+
72
+ def encode(self) -> bytes:
73
+ obj = {"height": self.height, "width": self.width, "size": self.size}
74
+ if self.url:
75
+ obj["url"] = self.url
76
+ if self.local_path:
77
+ obj["local"] = self.local_path
78
+ if self.thumbnail_url:
79
+ obj["thumbnail"] = self.thumbnail_url
80
+ if self.thumbnail_local_path:
81
+ obj["thumbnailLocalPath"] = self.thumbnail_local_path
82
+ if self.extra:
83
+ obj["extra"] = self.extra
84
+ return _json_bytes(obj)
85
+
86
+ def decode(self, data: bytes) -> None:
87
+ obj = json.loads(data or b"{}")
88
+ self.url = obj.get("url", "")
89
+ self.local_path = obj.get("local", "")
90
+ self.thumbnail_url = obj.get("thumbnail", "")
91
+ self.thumbnail_local_path = obj.get("thumbnailLocalPath", "")
92
+ self.height = obj.get("height", 0)
93
+ self.width = obj.get("width", 0)
94
+ self.extra = obj.get("extra", "")
95
+ self.size = obj.get("size", 0)
96
+
97
+ def conversation_digest(self) -> str:
98
+ return "[Image]"
99
+
100
+
101
+ class FileMessage(MessageContent):
102
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_FILE
103
+
104
+ def __init__(self) -> None:
105
+ self.name = ""
106
+ self.url = ""
107
+ self.local_path = ""
108
+ self.size = 0
109
+ self.type = ""
110
+ self.extra = ""
111
+
112
+ def encode(self) -> bytes:
113
+ obj = {"size": self.size}
114
+ if self.name:
115
+ obj["name"] = self.name
116
+ if self.url:
117
+ obj["url"] = self.url
118
+ if self.local_path:
119
+ obj["local"] = self.local_path
120
+ if self.type:
121
+ obj["type"] = self.type
122
+ if self.extra:
123
+ obj["extra"] = self.extra
124
+ return _json_bytes(obj)
125
+
126
+ def decode(self, data: bytes) -> None:
127
+ obj = json.loads(data or b"{}")
128
+ self.name = obj.get("name", "")
129
+ self.url = obj.get("url", "")
130
+ self.local_path = obj.get("local", "")
131
+ self.size = obj.get("size", 0)
132
+ self.type = obj.get("type", "")
133
+ self.extra = obj.get("extra", "")
134
+
135
+ def conversation_digest(self) -> str:
136
+ return "[File]"
137
+
138
+ def get_search_content(self) -> str:
139
+ return self.name
140
+
141
+
142
+ class VideoMessage(MessageContent):
143
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_VIDEO
144
+
145
+ def __init__(self) -> None:
146
+ self.url = ""
147
+ self.local_path = ""
148
+ self.snapshot_url = ""
149
+ self.snapshot_local_path = ""
150
+ self.height = 0
151
+ self.width = 0
152
+ self.size = 0
153
+ self.duration = 0
154
+ self.extra = ""
155
+
156
+ def encode(self) -> bytes:
157
+ obj = {
158
+ "height": self.height,
159
+ "width": self.width,
160
+ "size": self.size,
161
+ "duration": self.duration,
162
+ }
163
+ if self.url:
164
+ obj["url"] = self.url
165
+ if self.local_path:
166
+ obj["local"] = self.local_path
167
+ if self.snapshot_url:
168
+ obj["poster"] = self.snapshot_url
169
+ if self.snapshot_local_path:
170
+ obj["snapshotLocalPath"] = self.snapshot_local_path
171
+ if self.extra:
172
+ obj["extra"] = self.extra
173
+ return _json_bytes(obj)
174
+
175
+ def decode(self, data: bytes) -> None:
176
+ obj = json.loads(data or b"{}")
177
+ self.url = obj.get("url", "")
178
+ self.local_path = obj.get("local", "")
179
+ self.snapshot_url = obj.get("poster", "")
180
+ self.snapshot_local_path = obj.get("snapshotLocalPath", "")
181
+ self.height = obj.get("height", 0)
182
+ self.width = obj.get("width", 0)
183
+ self.size = obj.get("size", 0)
184
+ self.duration = obj.get("duration", 0)
185
+ self.extra = obj.get("extra", "")
186
+
187
+ def conversation_digest(self) -> str:
188
+ return "[Video]"
189
+
190
+
191
+ class VoiceMessage(MessageContent):
192
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_VOICE
193
+
194
+ def __init__(self) -> None:
195
+ self.url = ""
196
+ self.local_path = ""
197
+ self.duration = 0
198
+ self.extra = ""
199
+
200
+ def encode(self) -> bytes:
201
+ obj = {"duration": self.duration}
202
+ if self.url:
203
+ obj["url"] = self.url
204
+ if self.local_path:
205
+ obj["local"] = self.local_path
206
+ if self.extra:
207
+ obj["extra"] = self.extra
208
+ return _json_bytes(obj)
209
+
210
+ def decode(self, data: bytes) -> None:
211
+ obj = json.loads(data or b"{}")
212
+ self.url = obj.get("url", "")
213
+ self.local_path = obj.get("local", "")
214
+ self.duration = obj.get("duration", 0)
215
+ self.extra = obj.get("extra", "")
216
+
217
+ def conversation_digest(self) -> str:
218
+ return "[Voice]"
219
+
220
+
221
+ class StreamTextMessage(MessageContent):
222
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_STREAM_TEXT
223
+
224
+ def __init__(self) -> None:
225
+ self.content = ""
226
+ self.is_finished = False
227
+ self.seq = 0
228
+
229
+ def encode(self) -> bytes:
230
+ obj = {"is_finished": self.is_finished, "seq": self.seq}
231
+ if self.content:
232
+ obj["content"] = self.content
233
+ return _json_bytes(obj)
234
+
235
+ def decode(self, data: bytes) -> None:
236
+ obj = json.loads(data or b"{}")
237
+ self.content = obj.get("content", "")
238
+ self.is_finished = obj.get("is_finished", False)
239
+ self.seq = obj.get("seq", 0)
240
+
241
+ def conversation_digest(self) -> str:
242
+ return self.content
243
+
244
+ def get_search_content(self) -> str:
245
+ return self.content
246
+
247
+
248
+ class RecallInfoMessage(MessageContent):
249
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_RECALL_INFO
250
+
251
+ def __init__(self) -> None:
252
+ self.extra: Dict[str, str] = {}
253
+
254
+ def encode(self) -> bytes:
255
+ obj = {}
256
+ if self.extra:
257
+ obj["exts"] = self.extra
258
+ return _json_bytes(obj)
259
+
260
+ def decode(self, data: bytes) -> None:
261
+ obj = json.loads(data or b"{}")
262
+ self.extra = obj.get("exts", {}) or {}
263
+
264
+
265
+ class MergeMessagePreviewUnit:
266
+ def __init__(self, preview_content: str = "", sender: Optional[UserInfo] = None) -> None:
267
+ self.preview_content = preview_content
268
+ self.sender = sender
269
+
270
+
271
+ class MergeMessage(MessageContent):
272
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_MERGE
273
+
274
+ def __init__(
275
+ self,
276
+ title: str = "",
277
+ conversation: Optional[Conversation] = None,
278
+ message_id_list: Optional[List[str]] = None,
279
+ preview_list: Optional[List[MergeMessagePreviewUnit]] = None,
280
+ ) -> None:
281
+ message_id_list = list(message_id_list or [])
282
+ preview_list = list(preview_list or [])
283
+ if len(message_id_list) > 100:
284
+ message_id_list = message_id_list[:100]
285
+ if len(preview_list) > 10:
286
+ preview_list = preview_list[:10]
287
+ self.title = title
288
+ self.container_msg_id = ""
289
+ self.conversation = conversation
290
+ self.message_id_list = message_id_list
291
+ self.preview_list = preview_list
292
+ self.extra = ""
293
+
294
+ def encode(self) -> bytes:
295
+ dto: Dict[str, object] = {}
296
+ if self.title:
297
+ dto["title"] = self.title
298
+ if self.container_msg_id:
299
+ dto["containerMsgId"] = self.container_msg_id
300
+ if self.conversation is not None:
301
+ if self.conversation.conversation:
302
+ dto["conversationId"] = self.conversation.conversation
303
+ if self.conversation.conversation_type:
304
+ dto["conversationType"] = int(self.conversation.conversation_type)
305
+ if self.conversation.sub_channel:
306
+ dto["sub_channel"] = self.conversation.sub_channel
307
+ if self.message_id_list:
308
+ dto["messageIdList"] = self.message_id_list
309
+ previews = []
310
+ for preview in self.preview_list:
311
+ unit: Dict[str, str] = {}
312
+ if preview.preview_content:
313
+ unit["content"] = preview.preview_content
314
+ if preview.sender is not None:
315
+ if preview.sender.user_id:
316
+ unit["userId"] = preview.sender.user_id
317
+ if preview.sender.user_name:
318
+ unit["userName"] = preview.sender.user_name
319
+ if preview.sender.user_portrait:
320
+ unit["portrait"] = preview.sender.user_portrait
321
+ previews.append(unit)
322
+ if previews:
323
+ dto["previewList"] = previews
324
+ if self.extra:
325
+ dto["extra"] = self.extra
326
+ return _json_bytes(dto)
327
+
328
+ def decode(self, data: bytes) -> None:
329
+ dto = json.loads(data or b"{}")
330
+ self.title = dto.get("title", "")
331
+ self.container_msg_id = dto.get("containerMsgId", "")
332
+ self.message_id_list = dto.get("messageIdList", []) or []
333
+ self.extra = dto.get("extra", "")
334
+ conv_id = dto.get("conversationId", "")
335
+ if conv_id:
336
+ self.conversation = Conversation(
337
+ conversation=conv_id,
338
+ conversation_type=dto.get("conversationType", 0),
339
+ sub_channel=dto.get("sub_channel", ""),
340
+ )
341
+ self.preview_list = []
342
+ for unit in dto.get("previewList", []) or []:
343
+ self.preview_list.append(
344
+ MergeMessagePreviewUnit(
345
+ preview_content=unit.get("content", ""),
346
+ sender=UserInfo(
347
+ user_id=unit.get("userId", ""),
348
+ user_name=unit.get("userName", ""),
349
+ user_portrait=unit.get("portrait", ""),
350
+ ),
351
+ )
352
+ )
353
+
354
+ def conversation_digest(self) -> str:
355
+ return "[Merge]"
356
+
357
+
358
+ class ThumbnailPackedImageMessage(MessageContent):
359
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_THUMBNAIL_PACKED_IMAGE
360
+
361
+ def __init__(self) -> None:
362
+ self.image_uri = ""
363
+ self.local_path = ""
364
+ self.content = ""
365
+ self.height = 0
366
+ self.width = 0
367
+ self.extra = ""
368
+ self.size = 0
369
+
370
+ def encode(self) -> bytes:
371
+ obj = {"height": self.height, "width": self.width, "size": self.size}
372
+ if self.image_uri:
373
+ obj["imageUri"] = self.image_uri
374
+ if self.local_path:
375
+ obj["local"] = self.local_path
376
+ if self.content:
377
+ obj["content"] = self.content
378
+ if self.extra:
379
+ obj["extra"] = self.extra
380
+ return _json_bytes(obj)
381
+
382
+ def decode(self, data: bytes) -> None:
383
+ obj = json.loads(data or b"{}")
384
+ self.image_uri = obj.get("imageUri", "")
385
+ self.local_path = obj.get("local", "")
386
+ self.content = obj.get("content", "")
387
+ self.height = obj.get("height", 0)
388
+ self.width = obj.get("width", 0)
389
+ self.extra = obj.get("extra", "")
390
+ self.size = obj.get("size", 0)
391
+
392
+ def conversation_digest(self) -> str:
393
+ return "[Image]"
394
+
395
+
396
+ class SnapshotPackedVideoMessage(MessageContent):
397
+ CONTENT_TYPE = MESSAGE_CONTENT_TYPE_SNAPSHOT_PACKED_VIDEO
398
+
399
+ def __init__(self) -> None:
400
+ self.sight_url = ""
401
+ self.local_path = ""
402
+ self.content = ""
403
+ self.height = 0
404
+ self.width = 0
405
+ self.size = 0
406
+ self.duration = 0
407
+ self.name = ""
408
+ self.extra = ""
409
+
410
+ def encode(self) -> bytes:
411
+ obj = {
412
+ "height": self.height,
413
+ "width": self.width,
414
+ "size": self.size,
415
+ "duration": self.duration,
416
+ }
417
+ if self.sight_url:
418
+ obj["sightUrl"] = self.sight_url
419
+ if self.local_path:
420
+ obj["local"] = self.local_path
421
+ if self.content:
422
+ obj["content"] = self.content
423
+ if self.name:
424
+ obj["name"] = self.name
425
+ if self.extra:
426
+ obj["extra"] = self.extra
427
+ return _json_bytes(obj)
428
+
429
+ def decode(self, data: bytes) -> None:
430
+ obj = json.loads(data or b"{}")
431
+ self.sight_url = obj.get("sightUrl", "")
432
+ self.local_path = obj.get("local", "")
433
+ self.content = obj.get("content", "")
434
+ self.height = obj.get("height", 0)
435
+ self.width = obj.get("width", 0)
436
+ self.size = obj.get("size", 0)
437
+ self.duration = obj.get("duration", 0)
438
+ self.name = obj.get("name", "")
439
+ self.extra = obj.get("extra", "")
440
+
441
+ def conversation_digest(self) -> str:
442
+ return "[Video]"
443
+
444
+
445
+ class UnknownMessage(MessageContent):
446
+ """Fallback for content types the SDK does not model."""
447
+
448
+ def __init__(self, message_type: str = "") -> None:
449
+ self.message_type = message_type
450
+ self.content = ""
451
+ self.flags = 0
452
+
453
+ def encode(self) -> bytes:
454
+ return self.content.encode("utf-8") if isinstance(self.content, str) else bytes(self.content)
455
+
456
+ def decode(self, data: bytes) -> None:
457
+ self.content = data.decode("utf-8", errors="replace") if isinstance(data, (bytes, bytearray)) else str(data)
458
+
459
+ def get_content_type(self) -> str:
460
+ return self.message_type or MESSAGE_CONTENT_TYPE_UNKNOWN
461
+
462
+ def get_flags(self) -> int:
463
+ return self.flags
imbot_sdk/models.py ADDED
@@ -0,0 +1,99 @@
1
+ """High-level domain models exposed to SDK users.
2
+
3
+ :class:`Conversation`, :class:`Message`, :class:`ConversationInfo`,
4
+ :class:`UserInfo` and friends — plain data holders decoupled from the protobuf
5
+ wire types.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Dict, List, Optional
10
+
11
+ from .pb import appmessages_pb2 as pb
12
+
13
+ # Message flag bits
14
+ MESSAGE_FLAG_NONE = 0
15
+ MESSAGE_FLAG_IS_CMD = 1
16
+ MESSAGE_FLAG_IS_COUNTABLE = 2
17
+ MESSAGE_FLAG_IS_STATUS = 4
18
+ MESSAGE_FLAG_IS_SAVE = 8
19
+ MESSAGE_FLAG_IS_MODIFIED = 16
20
+ MESSAGE_FLAG_IS_MERGED = 32
21
+ MESSAGE_FLAG_IS_MUTE = 64
22
+ MESSAGE_FLAG_IS_BROADCAST = 128
23
+
24
+ DEFAULT_MESSAGE_FLAGS = MESSAGE_FLAG_IS_COUNTABLE | MESSAGE_FLAG_IS_SAVE
25
+
26
+ MESSAGE_CONTENT_TYPE_UNKNOWN = "jg:unknown"
27
+
28
+
29
+ @dataclass
30
+ class UserInfo:
31
+ user_id: str = ""
32
+ user_name: str = ""
33
+ user_portrait: str = ""
34
+ extras: Dict[str, str] = field(default_factory=dict)
35
+ updated_time: int = 0
36
+
37
+
38
+ @dataclass
39
+ class Conversation:
40
+ """Target conversation + channel type."""
41
+
42
+ conversation: str = ""
43
+ sub_channel: str = ""
44
+ conversation_type: int = pb.Unknown # ChannelType enum value
45
+
46
+
47
+ @dataclass
48
+ class MessageMentionInfo:
49
+ mention_type: int = pb.MentionDefault
50
+ target_user_ids: List[str] = field(default_factory=list)
51
+
52
+
53
+ @dataclass
54
+ class Message:
55
+ conversation: Optional[Conversation] = None
56
+ msg_id: str = ""
57
+ has_read: bool = False
58
+ msg_time: int = 0
59
+ sender_id: str = ""
60
+ msg_type: str = ""
61
+ msg_content: object = None # a messages.MessageContent subclass
62
+ group_id: str = ""
63
+ refered_message: Optional["Message"] = None
64
+ mention_info: Optional[MessageMentionInfo] = None
65
+
66
+
67
+ @dataclass
68
+ class ConversationMentionInfo:
69
+ sender_id: str = ""
70
+ msg_id: str = ""
71
+ msg_time: int = 0
72
+ mention_type: int = pb.MentionDefault
73
+
74
+
75
+ @dataclass
76
+ class ConversationInfo:
77
+ conversation: Optional[Conversation] = None
78
+ latest_message: Optional[Message] = None
79
+ unread_count: int = 0
80
+ sort_time: int = 0
81
+ is_top: bool = False
82
+ mute: bool = False
83
+ draft: str = ""
84
+ mention_info: Optional[ConversationMentionInfo] = None
85
+ display_name: str = ""
86
+ alias: str = ""
87
+ portrait: str = ""
88
+
89
+
90
+ @dataclass
91
+ class MessageReactionItem:
92
+ reaction_id: str = ""
93
+ user_info_list: List[UserInfo] = field(default_factory=list)
94
+
95
+
96
+ @dataclass
97
+ class MessageReaction:
98
+ message_id: str = ""
99
+ item_list: List[MessageReactionItem] = field(default_factory=list)
File without changes