dimp 1.0.2__tar.gz → 2.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.
Files changed (54) hide show
  1. {dimp-1.0.2 → dimp-2.0.0}/PKG-INFO +1 -1
  2. {dimp-1.0.2 → dimp-2.0.0}/dimp/barrack.py +27 -27
  3. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/commands.py +12 -14
  4. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/contents.py +73 -49
  5. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/files.py +16 -89
  6. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/groups.py +9 -2
  7. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/delegate.py +2 -2
  8. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/entity.py +8 -8
  9. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/group.py +28 -27
  10. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/user.py +38 -36
  11. {dimp-1.0.2 → dimp-2.0.0}/dimp/packer.py +6 -6
  12. {dimp-1.0.2 → dimp-2.0.0}/dimp/processor.py +5 -5
  13. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/commands.py +8 -11
  14. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/contents.py +63 -22
  15. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/files.py +6 -6
  16. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/groups.py +9 -3
  17. {dimp-1.0.2 → dimp-2.0.0}/dimp/transceiver.py +24 -24
  18. {dimp-1.0.2 → dimp-2.0.0}/dimp.egg-info/PKG-INFO +1 -1
  19. dimp-2.0.0/dimp.egg-info/requires.txt +2 -0
  20. {dimp-1.0.2 → dimp-2.0.0}/setup.py +3 -3
  21. dimp-1.0.2/dimp.egg-info/requires.txt +0 -2
  22. {dimp-1.0.2 → dimp-2.0.0}/README.md +0 -0
  23. {dimp-1.0.2 → dimp-2.0.0}/dimp/__init__.py +0 -0
  24. {dimp-1.0.2 → dimp-2.0.0}/dimp/crypto/__init__.py +0 -0
  25. {dimp-1.0.2 → dimp-2.0.0}/dimp/crypto/keys.py +0 -0
  26. {dimp-1.0.2 → dimp-2.0.0}/dimp/crypto/pnf.py +0 -0
  27. {dimp-1.0.2 → dimp-2.0.0}/dimp/crypto/ted.py +0 -0
  28. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/__init__.py +0 -0
  29. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/base.py +0 -0
  30. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/customized.py +0 -0
  31. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/factory.py +0 -0
  32. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/group_admins.py +0 -0
  33. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/money.py +0 -0
  34. {dimp-1.0.2 → dimp-2.0.0}/dimp/dkd/receipt.py +0 -0
  35. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/__init__.py +0 -0
  36. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/docs.py +0 -0
  37. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/document.py +0 -0
  38. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/helper.py +0 -0
  39. {dimp-1.0.2 → dimp-2.0.0}/dimp/mkm/meta.py +0 -0
  40. {dimp-1.0.2 → dimp-2.0.0}/dimp/msg/__init__.py +0 -0
  41. {dimp-1.0.2 → dimp-2.0.0}/dimp/msg/base.py +0 -0
  42. {dimp-1.0.2 → dimp-2.0.0}/dimp/msg/envelope.py +0 -0
  43. {dimp-1.0.2 → dimp-2.0.0}/dimp/msg/instant.py +0 -0
  44. {dimp-1.0.2 → dimp-2.0.0}/dimp/msg/reliable.py +0 -0
  45. {dimp-1.0.2 → dimp-2.0.0}/dimp/msg/secure.py +0 -0
  46. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/__init__.py +0 -0
  47. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/customized.py +0 -0
  48. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/docs.py +0 -0
  49. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/money.py +0 -0
  50. {dimp-1.0.2 → dimp-2.0.0}/dimp/protocol/receipt.py +0 -0
  51. {dimp-1.0.2 → dimp-2.0.0}/dimp.egg-info/SOURCES.txt +0 -0
  52. {dimp-1.0.2 → dimp-2.0.0}/dimp.egg-info/dependency_links.txt +0 -0
  53. {dimp-1.0.2 → dimp-2.0.0}/dimp.egg-info/top_level.txt +0 -0
  54. {dimp-1.0.2 → dimp-2.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dimp
3
- Version: 1.0.2
3
+ Version: 2.0.0
4
4
  Summary: Decentralized Instant Messaging Protocol
5
5
  Home-page: https://github.com/dimchat/core-py
6
6
  Author: Albert Moky
@@ -85,7 +85,7 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
85
85
  return finger >> 1
86
86
 
87
87
  @abstractmethod # protected
88
- def create_user(self, identifier: ID) -> Optional[User]:
88
+ async def create_user(self, identifier: ID) -> Optional[User]:
89
89
  """
90
90
  Create user when visa.key exists
91
91
 
@@ -95,7 +95,7 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
95
95
  raise NotImplemented
96
96
 
97
97
  @abstractmethod # protected
98
- def create_group(self, identifier: ID) -> Optional[Group]:
98
+ async def create_group(self, identifier: ID) -> Optional[Group]:
99
99
  """
100
100
  Create group when members exist
101
101
 
@@ -105,25 +105,25 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
105
105
  raise NotImplemented
106
106
 
107
107
  # protected
108
- def visa_key(self, identifier: ID) -> Optional[EncryptKey]:
109
- visa = self.visa(identifier=identifier)
108
+ async def get_visa_key(self, identifier: ID) -> Optional[EncryptKey]:
109
+ visa = await self.get_visa(identifier=identifier)
110
110
  if visa is not None: # and visa.valid:
111
111
  return visa.public_key
112
112
 
113
113
  # protected
114
- def meta_key(self, identifier: ID) -> Optional[VerifyKey]:
115
- meta = self.meta(identifier=identifier)
114
+ async def get_meta_key(self, identifier: ID) -> Optional[VerifyKey]:
115
+ meta = await self.get_meta(identifier=identifier)
116
116
  if meta is not None: # and meta.valid:
117
117
  return meta.public_key
118
118
 
119
- def visa(self, identifier: ID) -> Optional[Visa]:
119
+ async def get_visa(self, identifier: ID) -> Optional[Visa]:
120
120
  """ Get last visa document """
121
- documents = self.documents(identifier=identifier)
121
+ documents = await self.get_documents(identifier=identifier)
122
122
  return DocumentHelper.last_visa(documents=documents)
123
123
 
124
- def bulletin(self, identifier: ID) -> Optional[Bulletin]:
124
+ async def get_bulletin(self, identifier: ID) -> Optional[Bulletin]:
125
125
  """ get last bulletin document """
126
- documents = self.documents(identifier=identifier)
126
+ documents = await self.get_documents(identifier=identifier)
127
127
  return DocumentHelper.last_bulletin(documents=documents)
128
128
 
129
129
  #
@@ -131,23 +131,23 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
131
131
  #
132
132
 
133
133
  # Override
134
- def user(self, identifier: ID) -> Optional[User]:
134
+ async def get_user(self, identifier: ID) -> Optional[User]:
135
135
  # 1. get from user cache
136
136
  usr = self.__users.get(identifier)
137
137
  if usr is None:
138
138
  # 2. create and cache it
139
- usr = self.create_user(identifier=identifier)
139
+ usr = await self.create_user(identifier=identifier)
140
140
  if usr is not None:
141
141
  self.cache_user(user=usr)
142
142
  return usr
143
143
 
144
144
  # Override
145
- def group(self, identifier: ID) -> Optional[Group]:
145
+ async def get_group(self, identifier: ID) -> Optional[Group]:
146
146
  # 1. get from group cache
147
147
  grp = self.__groups.get(identifier)
148
148
  if grp is None:
149
149
  # 2. create and cache it
150
- grp = self.create_group(identifier=identifier)
150
+ grp = await self.create_group(identifier=identifier)
151
151
  if grp is not None:
152
152
  self.cache_group(group=grp)
153
153
  return grp
@@ -157,30 +157,30 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
157
157
  #
158
158
 
159
159
  # Override
160
- def public_key_for_encryption(self, identifier: ID) -> Optional[EncryptKey]:
160
+ async def public_key_for_encryption(self, identifier: ID) -> Optional[EncryptKey]:
161
161
  # 1. get key from visa
162
- key = self.visa_key(identifier=identifier)
162
+ key = await self.get_visa_key(identifier=identifier)
163
163
  if key is not None:
164
164
  # if visa.key exists, use it for encryption
165
165
  return key
166
166
  # 2. get key from meta
167
- key = self.meta_key(identifier=identifier)
167
+ key = await self.get_meta_key(identifier=identifier)
168
168
  if isinstance(key, EncryptKey):
169
169
  # if visa.key not exists and meta.key is encrypt key,
170
170
  # use it for encryption
171
171
  return key
172
172
 
173
173
  # Override
174
- def public_keys_for_verification(self, identifier: ID) -> List[VerifyKey]:
174
+ async def public_keys_for_verification(self, identifier: ID) -> List[VerifyKey]:
175
175
  keys = []
176
176
  # 1. get key from meta
177
- key = self.meta_key(identifier=identifier)
177
+ key = await self.get_meta_key(identifier=identifier)
178
178
  if key is not None:
179
179
  # the sender may use identity key to sign message.data,
180
180
  # try to verify it with meta.key
181
181
  keys.append(key)
182
182
  # 2. get key from visa
183
- key = self.visa_key(identifier=identifier)
183
+ key = await self.get_visa_key(identifier=identifier)
184
184
  if isinstance(key, VerifyKey):
185
185
  # the sender may use communication key to sign message.data,
186
186
  # so try to verify it with visa.key here
@@ -193,19 +193,19 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
193
193
  #
194
194
 
195
195
  # Override
196
- def founder(self, identifier: ID) -> Optional[ID]:
196
+ async def get_founder(self, identifier: ID) -> Optional[ID]:
197
197
  # check for broadcast
198
198
  if identifier.is_broadcast:
199
199
  # founder of broadcast group
200
200
  return BroadcastHelper.broadcast_founder(group=identifier)
201
201
  # get from document
202
- doc = self.bulletin(identifier=identifier)
202
+ doc = await self.get_bulletin(identifier=identifier)
203
203
  if doc is not None: # and doc.valid:
204
204
  return doc.founder
205
205
  # TODO: load founder from database
206
206
 
207
207
  # Override
208
- def owner(self, identifier: ID) -> Optional[ID]:
208
+ async def get_owner(self, identifier: ID) -> Optional[ID]:
209
209
  # check for broadcast
210
210
  if identifier.is_broadcast:
211
211
  # owner of broadcast group
@@ -213,11 +213,11 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
213
213
  # check group type
214
214
  if identifier.type == EntityType.GROUP:
215
215
  # Polylogue's owner is its founder
216
- return self.founder(identifier=identifier)
216
+ return await self.get_founder(identifier=identifier)
217
217
  # TODO: load owner from database
218
218
 
219
219
  # Override
220
- def members(self, identifier: ID) -> List[ID]:
220
+ async def get_members(self, identifier: ID) -> List[ID]:
221
221
  # check for broadcast
222
222
  if identifier.is_broadcast:
223
223
  # members of broadcast group
@@ -226,8 +226,8 @@ class Barrack(EntityDelegate, UserDataSource, GroupDataSource, ABC):
226
226
  return []
227
227
 
228
228
  # Override
229
- def assistants(self, identifier: ID) -> List[ID]:
230
- doc = self.bulletin(identifier=identifier)
229
+ async def get_assistants(self, identifier: ID) -> List[ID]:
230
+ doc = await self.get_bulletin(identifier=identifier)
231
231
  if doc is not None: # and doc.valid:
232
232
  bots = doc.assistants
233
233
  if bots is not None:
@@ -38,6 +38,7 @@
38
38
 
39
39
  from typing import Optional, Any, Dict
40
40
 
41
+ from mkm.types import DateTime
41
42
  from mkm import ID, Meta, Document
42
43
 
43
44
  from ..protocol import MetaCommand, DocumentCommand
@@ -123,23 +124,25 @@ class BaseDocumentCommand(BaseMetaCommand, DocumentCommand):
123
124
  identifier: ID = None,
124
125
  meta: Optional[Meta] = None,
125
126
  document: Optional[Document] = None,
126
- signature: Optional[str] = None):
127
+ last_time: Optional[DateTime] = None):
127
128
  if content is None:
128
129
  # 1. new command with ID, meta, document & signature
129
130
  if identifier is None:
130
- assert document is not None, 'document command error: %s, %s' % (meta, signature)
131
+ assert document is not None, 'document command error: %s, %s' % (meta, last_time)
131
132
  identifier = document.identifier
132
- assert identifier is not None, 'document command error: %s, %s, %s' % (meta, document, signature)
133
+ assert identifier is not None, 'document command error: %s, %s, %s' % (meta, document, last_time)
133
134
  cmd = Command.DOCUMENT
134
135
  super().__init__(cmd=cmd, identifier=identifier, meta=meta)
136
+ # respond with document info
135
137
  if document is not None:
136
138
  self['document'] = document.dictionary
137
- if signature is not None:
138
- self['signature'] = signature
139
+ # query with last document time
140
+ if last_time is not None:
141
+ self.set_datetime(key='last_time', value=last_time)
139
142
  else:
140
143
  # 2. command info from network
141
- assert identifier is None and meta is None and document is None and signature is None, \
142
- 'params error: %s, %s, %s, %s, %s' % (content, identifier, meta, document, signature)
144
+ assert identifier is None and meta is None and document is None and last_time is None, \
145
+ 'params error: %s, %s, %s, %s, %s' % (content, identifier, meta, document, last_time)
143
146
  super().__init__(content)
144
147
  # lazy load
145
148
  self.__doc = document
@@ -154,10 +157,5 @@ class BaseDocumentCommand(BaseMetaCommand, DocumentCommand):
154
157
  return self.__doc
155
158
 
156
159
  @property # Override
157
- def signature(self) -> Optional[str]:
158
- """
159
- signature for checking new document
160
-
161
- :return: part of signature in current document (base64)
162
- """
163
- return self.get_str(key='signature', default=None)
160
+ def last_time(self) -> Optional[DateTime]:
161
+ return self.get_datetime(key='last_time', default=None)
@@ -30,7 +30,7 @@
30
30
 
31
31
  from typing import Optional, Any, Dict, List
32
32
 
33
- from mkm.format import TransportableData, PortableNetworkFile
33
+ from mkm.format import PortableNetworkFile
34
34
  from mkm.types import URI
35
35
  from mkm import ID
36
36
  from dkd import ContentType, Content
@@ -184,44 +184,45 @@ class WebPageContent(BaseContent, PageContent):
184
184
  type : 0x20,
185
185
  sn : 123,
186
186
 
187
- URL : "https://github.com/moky/dimp", // Page URL
188
- icon : "...", // base64_encode(icon)
189
- title : "...",
190
- desc : "..."
187
+ title : "...", // Web title
188
+ icon : "...", // base64_encode(icon)
189
+ desc : "...",
190
+
191
+ URL : "https://github.com/moky/dimp",
192
+
193
+ HTML : "...", // Web content
194
+ mime_type : "text/html", // Content-Type
195
+ encoding : "utf8",
196
+ base : "about:blank" // Base URL
191
197
  }
192
198
  """
193
199
 
194
200
  def __init__(self, content: Dict[str, Any] = None,
195
- url: URI = None, title: str = None,
196
- desc: Optional[str] = None, icon: Optional[TransportableData] = None):
201
+ url: URI = None, html: str = None, title: str = None,
202
+ desc: Optional[str] = None, icon: Optional[URI] = None):
197
203
  if content is None:
198
204
  # 1. new content with url, title, desc & icon
199
205
  assert url is not None and title is not None, 'page info error: %s, %s, %s, %s' % (url, title, desc, icon)
200
206
  msg_type = ContentType.PAGE.value
201
207
  super().__init__(None, msg_type)
202
- self['URL'] = url
203
- self['title'] = title
204
- if desc is not None:
205
- self['desc'] = desc
206
- if icon is not None:
207
- self.set_icon(icon)
208
208
  else:
209
209
  # 2. content info from network
210
- assert url is None and title is None and desc is None and icon is None, \
211
- 'params error: %s, %s, %s, %s, %s' % (content, url, title, desc, icon)
212
210
  super().__init__(content)
213
- # lazy
214
- self.__icon = icon
215
-
216
- @property # Override
217
- def url(self) -> URI:
218
- # TODO: convert str to URI
219
- return self.get_str(key='URL', default=None)
211
+ # URL or HTML
212
+ if url is not None:
213
+ self.url = url
214
+ if html is not None:
215
+ self.html = html
216
+ # title, icon, description
217
+ self.title = title
218
+ if desc is not None:
219
+ self.desc = desc
220
+ if icon is not None:
221
+ self.icon = icon
220
222
 
221
- @url.setter # Override
222
- def url(self, string: URI):
223
- # TODO: convert URI to str
224
- self['URL'] = string
223
+ #
224
+ # Web Title
225
+ #
225
226
 
226
227
  @property # Override
227
228
  def title(self) -> str:
@@ -231,40 +232,63 @@ class WebPageContent(BaseContent, PageContent):
231
232
  def title(self, string: str):
232
233
  self['title'] = string
233
234
 
235
+ #
236
+ # Fav Icon
237
+ #
238
+
239
+ @property # Override
240
+ def icon(self) -> Optional[URI]:
241
+ base64 = self.get_str(key='icon', default=None)
242
+ # TODO: convert 'data:' URI
243
+ return base64
244
+
245
+ @icon.setter # Override
246
+ def icon(self, base64: URI):
247
+ self['icon'] = base64
248
+
249
+ #
250
+ # Description
251
+ #
252
+
234
253
  @property # Override
235
254
  def desc(self) -> Optional[str]:
236
255
  return self.get_str(key='desc', default=None)
237
256
 
238
257
  @desc.setter # Override
239
- def desc(self, string: Optional[str]):
240
- if string is None:
258
+ def desc(self, text: Optional[str]):
259
+ if text is None:
241
260
  self.pop('desc', None)
242
261
  else:
243
- self['desc'] = string
262
+ self['desc'] = text
263
+
264
+ #
265
+ # Page URL
266
+ #
244
267
 
245
268
  @property # Override
246
- def icon(self) -> Optional[bytes]:
247
- ted = self.__icon
248
- if ted is None:
249
- base64 = self.get('icon')
250
- self.__icon = ted = TransportableData.parse(base64)
251
- if ted is not None:
252
- return ted.data
253
-
254
- @icon.setter
255
- def icon(self, image: Optional[bytes]):
256
- if image is None or len(image) == 0:
257
- ted = None
258
- else:
259
- ted = TransportableData.create(data=image)
260
- self.set_icon(ted)
269
+ def url(self) -> URI:
270
+ # TODO: convert str to URI
271
+ return self.get_str(key='URL', default=None)
261
272
 
262
- def set_icon(self, ted: Optional[TransportableData]):
263
- if ted is None:
264
- self.pop('icon')
273
+ @url.setter # Override
274
+ def url(self, locator: URI):
275
+ # TODO: convert URI to str
276
+ self['URL'] = locator
277
+
278
+ #
279
+ # Page content
280
+ #
281
+
282
+ @property # Override
283
+ def html(self) -> Optional[str]:
284
+ return self.get_str(key='desc', default=None)
285
+
286
+ @html.setter # Override
287
+ def html(self, content: Optional[str]):
288
+ if content is None:
289
+ self.pop('HTML', None)
265
290
  else:
266
- self['icon'] = ted.object
267
- self.__icon = ted
291
+ self['HTML'] = content
268
292
 
269
293
 
270
294
  class NameCardContent(BaseContent, NameCard):
@@ -43,19 +43,7 @@ from .contents import BaseContent
43
43
 
44
44
 
45
45
  class BaseFileContent(BaseContent, FileContent):
46
- """
47
- File Message Content
48
- ~~~~~~~~~~~~~~~~~~~~
49
-
50
- data format: {
51
- type : 0x10,
52
- sn : 123,
53
-
54
- URL : "http://", // upload to CDN
55
- data : "...", // if (!URL) base64_encode(fileContent)
56
- filename : "..."
57
- }
58
- """
46
+ """ File Message Content """
59
47
 
60
48
  def __init__(self, content: Dict[str, Any] = None,
61
49
  msg_type: int = None,
@@ -120,64 +108,27 @@ class BaseFileContent(BaseContent, FileContent):
120
108
 
121
109
 
122
110
  class ImageFileContent(BaseFileContent, ImageContent):
123
- """
124
- Image Message Content
125
- ~~~~~~~~~~~~~~~~~~~~~
126
-
127
- data format: {
128
- type : 0x12,
129
- sn : 123,
130
-
131
- URL : "http://", // upload to CDN
132
- data : "...", // if (!URL) base64_encode(image)
133
- thumbnail : "...", // base64_encode(smallImage)
134
- filename : "..."
135
- }
136
- """
111
+ """ Image Message Content """
137
112
 
138
113
  def __init__(self, content: Dict[str, Any] = None,
139
114
  data: Optional[TransportableData] = None, filename: Optional[str] = None,
140
115
  url: Optional[URI] = None, password: Optional[DecryptKey] = None):
141
116
  msg_type = ContentType.IMAGE.value if content is None else None
142
117
  super().__init__(content, msg_type, data=data, filename=filename, url=url, password=password)
143
- # lazy load
144
- self.__thumbnail: Optional[TransportableData] = None
145
118
 
146
119
  @property # Override
147
- def thumbnail(self) -> Optional[bytes]:
148
- ted = self.__thumbnail
149
- if ted is None:
150
- base64 = self.get('thumbnail')
151
- self.__thumbnail = ted = TransportableData.parse(base64)
152
- if ted is not None:
153
- return ted.data
120
+ def thumbnail(self) -> Optional[URI]:
121
+ base64 = self.get_str(key='thumbnail', default=None)
122
+ # TODO: convert 'data:' URI
123
+ return base64
154
124
 
155
125
  @thumbnail.setter # Override
156
- def thumbnail(self, image: bytes):
157
- if image is None: # or len(image) == 0:
158
- self.pop('thumbnail', None)
159
- ted = None
160
- else:
161
- ted = TransportableData.create(data=image)
162
- self['thumbnail'] = ted.object
163
- self.__thumbnail = ted
126
+ def thumbnail(self, base64: URI):
127
+ self['thumbnail'] = base64
164
128
 
165
129
 
166
130
  class AudioFileContent(BaseFileContent, AudioContent):
167
- """
168
- Audio Message Content
169
- ~~~~~~~~~~~~~~~~~~~~~
170
-
171
- data format: {
172
- type : 0x14,
173
- sn : 123,
174
-
175
- URL : "http://", // upload to CDN
176
- data : "...", // if (!URL) base64_encode(audio)
177
- text : "...", // Automatic Speech Recognition
178
- filename : "..."
179
- }
180
- """
131
+ """ Audio Message Content """
181
132
 
182
133
  def __init__(self, content: Dict[str, Any] = None,
183
134
  data: Optional[TransportableData] = None, filename: Optional[str] = None,
@@ -195,44 +146,20 @@ class AudioFileContent(BaseFileContent, AudioContent):
195
146
 
196
147
 
197
148
  class VideoFileContent(BaseFileContent, VideoContent):
198
- """
199
- Video Message Content
200
- ~~~~~~~~~~~~~~~~~~~~~
201
-
202
- data format: {
203
- type : 0x16,
204
- sn : 123,
205
-
206
- URL : "http://", // upload to CDN
207
- data : "...", // if (!URL) base64_encode(video)
208
- snapshot : "...", // base64_encode(smallImage)
209
- filename : "..."
210
- }
211
- """
149
+ """ Video Message Content """
212
150
 
213
151
  def __init__(self, content: Dict[str, Any] = None,
214
152
  data: Optional[TransportableData] = None, filename: Optional[str] = None,
215
153
  url: Optional[URI] = None, password: Optional[DecryptKey] = None):
216
154
  msg_type = ContentType.VIDEO.value if content is None else None
217
155
  super().__init__(content, msg_type, data=data, filename=filename, url=url, password=password)
218
- # lazy load
219
- self.__snapshot: Optional[TransportableData] = None
220
156
 
221
157
  @property # Override
222
- def snapshot(self) -> Optional[bytes]:
223
- ted = self.__snapshot
224
- if ted is None:
225
- base64 = self.get('snapshot')
226
- self.__snapshot = ted = TransportableData.parse(base64)
227
- if ted is not None:
228
- return ted.data
158
+ def snapshot(self) -> Optional[URI]:
159
+ base64 = self.get_str(key='thumbnail', default=None)
160
+ # TODO: convert 'data:' URI
161
+ return base64
229
162
 
230
163
  @snapshot.setter # Override
231
- def snapshot(self, image: bytes):
232
- if image is None: # or len(image) == 0:
233
- self.pop('snapshot', None)
234
- ted = None
235
- else:
236
- ted = TransportableData.create(data=image)
237
- self['snapshot'] = ted.object
238
- self.__snapshot = ted
164
+ def snapshot(self, base64: URI):
165
+ self['snapshot'] = base64
@@ -39,8 +39,9 @@
39
39
 
40
40
  from typing import Optional, Any, Dict, List
41
41
 
42
- from dkd import ContentType
42
+ from mkm.types import DateTime
43
43
  from mkm import ID
44
+ from dkd import ContentType
44
45
 
45
46
  from ..protocol import HistoryCommand, GroupCommand
46
47
  from ..protocol import InviteCommand, ExpelCommand, JoinCommand, QuitCommand, QueryCommand, ResetCommand
@@ -200,7 +201,7 @@ class QuitGroupCommand(BaseGroupCommand, QuitCommand):
200
201
 
201
202
  class QueryGroupCommand(BaseGroupCommand, QueryCommand):
202
203
 
203
- def __init__(self, content: Dict[str, Any] = None, group: ID = None):
204
+ def __init__(self, content: Dict[str, Any] = None, group: ID = None, last_time: DateTime = None):
204
205
  """
205
206
  Create query group members command (not history command)
206
207
 
@@ -210,6 +211,12 @@ class QueryGroupCommand(BaseGroupCommand, QueryCommand):
210
211
  """
211
212
  cmd = GroupCommand.QUERY if content is None else None
212
213
  super().__init__(content, cmd=cmd, group=group)
214
+ if last_time is not None:
215
+ self.set_datetime(key='last_time', value=last_time)
216
+
217
+ @property # Override
218
+ def last_time(self) -> Optional[DateTime]:
219
+ return self.get_datetime(key='last_time', default=None)
213
220
 
214
221
 
215
222
  class ResetGroupCommand(BaseGroupCommand, ResetCommand):
@@ -41,7 +41,7 @@ from .group import Group
41
41
  class EntityDelegate(ABC):
42
42
 
43
43
  @abstractmethod
44
- def user(self, identifier: ID) -> Optional[User]:
44
+ async def get_user(self, identifier: ID) -> Optional[User]:
45
45
  """
46
46
  Create user with ID
47
47
 
@@ -51,7 +51,7 @@ class EntityDelegate(ABC):
51
51
  raise NotImplemented
52
52
 
53
53
  @abstractmethod
54
- def group(self, identifier: ID) -> Optional[Group]:
54
+ async def get_group(self, identifier: ID) -> Optional[Group]:
55
55
  """
56
56
  Create group with ID
57
57
 
@@ -48,7 +48,7 @@ class EntityDataSource(ABC):
48
48
  """
49
49
 
50
50
  @abstractmethod
51
- def meta(self, identifier: ID) -> Optional[Meta]:
51
+ async def get_meta(self, identifier: ID) -> Optional[Meta]:
52
52
  """
53
53
  Get meta for entity
54
54
 
@@ -58,7 +58,7 @@ class EntityDataSource(ABC):
58
58
  raise NotImplemented
59
59
 
60
60
  @abstractmethod
61
- def documents(self, identifier: ID) -> List[Document]:
61
+ async def get_documents(self, identifier: ID) -> List[Document]:
62
62
  """
63
63
  Get documents for entity ID
64
64
 
@@ -104,13 +104,13 @@ class Entity(ABC):
104
104
 
105
105
  @property
106
106
  @abstractmethod
107
- def meta(self) -> Meta:
107
+ async def meta(self) -> Meta:
108
108
  """ Get meta """
109
109
  raise NotImplemented
110
110
 
111
111
  @property
112
112
  @abstractmethod
113
- def documents(self) -> List[Document]:
113
+ async def documents(self) -> List[Document]:
114
114
  """ Get documents """
115
115
  raise NotImplemented
116
116
 
@@ -176,13 +176,13 @@ class BaseEntity(Entity):
176
176
  return self.__id.type
177
177
 
178
178
  @property # Override
179
- def meta(self) -> Meta:
179
+ async def meta(self) -> Meta:
180
180
  delegate = self.data_source
181
181
  # assert delegate is not None, 'entity delegate not set yet'
182
- return delegate.meta(identifier=self.__id)
182
+ return await delegate.get_meta(identifier=self.__id)
183
183
 
184
184
  @property # Override
185
- def documents(self) -> List[Document]:
185
+ async def documents(self) -> List[Document]:
186
186
  delegate = self.data_source
187
187
  # assert delegate is not None, 'entity delegate not set yet'
188
- return delegate.documents(identifier=self.__id)
188
+ return await delegate.get_documents(identifier=self.__id)