dimp 2.3.2__tar.gz → 2.3.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.
- {dimp-2.3.2 → dimp-2.3.3}/PKG-INFO +68 -47
- {dimp-2.3.2 → dimp-2.3.3}/README.md +67 -46
- {dimp-2.3.2 → dimp-2.3.3}/dimp/__init__.py +10 -8
- {dimp-2.3.2 → dimp-2.3.3}/dimp/crypto/keys.py +1 -1
- {dimp-2.3.2 → dimp-2.3.3}/dimp/crypto/pnf.py +2 -2
- {dimp-2.3.2 → dimp-2.3.3}/dimp/crypto/ted.py +5 -5
- {dimp-2.3.2 → dimp-2.3.3}/dimp/dkd/__init__.py +9 -6
- {dimp-2.3.2 → dimp-2.3.3}/dimp/mkm/document.py +3 -3
- {dimp-2.3.2 → dimp-2.3.3}/dimp/msg/envelope.py +2 -2
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/__init__.py +2 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/base.py +1 -1
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/commands.py +1 -1
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/contents.py +4 -4
- dimp-2.3.3/dimp/protocol/customized.py +113 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/files.py +3 -3
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/groups.py +1 -1
- {dimp-2.3.2 → dimp-2.3.3}/dimp.egg-info/PKG-INFO +68 -47
- {dimp-2.3.2 → dimp-2.3.3}/dimp.egg-info/SOURCES.txt +1 -0
- dimp-2.3.3/dimp.egg-info/requires.txt +2 -0
- {dimp-2.3.2 → dimp-2.3.3}/setup.py +3 -3
- dimp-2.3.2/dimp.egg-info/requires.txt +0 -2
- {dimp-2.3.2 → dimp-2.3.3}/LICENSE +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/crypto/__init__.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/crypto/algorithms.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/mkm/__init__.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/mkm/docs.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/mkm/meta.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/msg/__init__.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/msg/base.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/msg/instant.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/msg/reliable.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/msg/secure.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/plugins/__init__.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/docs.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/forward.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/helpers.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/money.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/quote.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/receipt.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp/protocol/types.py +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp.egg-info/dependency_links.txt +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/dimp.egg-info/top_level.txt +0 -0
- {dimp-2.3.2 → dimp-2.3.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dimp
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: Decentralized Instant Messaging Protocol
|
|
5
5
|
Home-page: https://github.com/dimchat/core-py
|
|
6
6
|
Author: Albert Moky
|
|
@@ -45,8 +45,9 @@ License-File: LICENSE
|
|
|
45
45
|
3. (S-C) handshake success
|
|
46
46
|
|
|
47
47
|
```python
|
|
48
|
+
from abc import ABC, abstractmethod
|
|
48
49
|
from enum import IntEnum
|
|
49
|
-
from typing import Optional,
|
|
50
|
+
from typing import Optional, Dict
|
|
50
51
|
|
|
51
52
|
from dimp import Command, BaseCommand
|
|
52
53
|
|
|
@@ -58,7 +59,20 @@ class HandshakeState(IntEnum):
|
|
|
58
59
|
Success = 3 # S -> C, handshake accepted
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
def handshake_state(title: str, session: str = None) -> HandshakeState:
|
|
63
|
+
# Server -> Client
|
|
64
|
+
if title == 'DIM!': # or title == 'OK!':
|
|
65
|
+
return HandshakeState.Success
|
|
66
|
+
if title == 'DIM?':
|
|
67
|
+
return HandshakeState.Again
|
|
68
|
+
# Client -> Server: "Hello world!"
|
|
69
|
+
if session is None or len(session) == 0:
|
|
70
|
+
return HandshakeState.Start
|
|
71
|
+
else:
|
|
72
|
+
return HandshakeState.Restart
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class HandshakeCommand(Command, ABC):
|
|
62
76
|
"""
|
|
63
77
|
Handshake Command
|
|
64
78
|
~~~~~~~~~~~~~~~~~
|
|
@@ -74,37 +88,24 @@ class HandshakeCommand(BaseCommand):
|
|
|
74
88
|
"""
|
|
75
89
|
HANDSHAKE = 'handshake'
|
|
76
90
|
|
|
77
|
-
def __init__(self, content: Dict[str, Any] = None, title: str = None, session: str = None):
|
|
78
|
-
if content is None:
|
|
79
|
-
# 1. new command with title & session key
|
|
80
|
-
assert title is not None, 'handshake command error: %s' % session
|
|
81
|
-
cmd = self.HANDSHAKE
|
|
82
|
-
super().__init__(cmd=cmd)
|
|
83
|
-
self['title'] = title
|
|
84
|
-
self['message'] = title # TODO: remove after all clients upgraded
|
|
85
|
-
if session is not None:
|
|
86
|
-
self['session'] = session
|
|
87
|
-
else:
|
|
88
|
-
# 2. command info from network
|
|
89
|
-
assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
|
|
90
|
-
super().__init__(content)
|
|
91
|
-
|
|
92
91
|
@property
|
|
92
|
+
@abstractmethod
|
|
93
93
|
def title(self) -> str:
|
|
94
|
-
|
|
95
|
-
text = self.get_str(key='title', default=None)
|
|
96
|
-
if text is None:
|
|
97
|
-
# compatible with v1.0
|
|
98
|
-
text = self.get_str(key='message', default='')
|
|
99
|
-
return text
|
|
94
|
+
raise NotImplemented
|
|
100
95
|
|
|
101
96
|
@property
|
|
97
|
+
@abstractmethod
|
|
102
98
|
def session(self) -> Optional[str]:
|
|
103
|
-
|
|
99
|
+
raise NotImplemented
|
|
104
100
|
|
|
105
101
|
@property
|
|
102
|
+
@abstractmethod
|
|
106
103
|
def state(self) -> HandshakeState:
|
|
107
|
-
|
|
104
|
+
raise NotImplemented
|
|
105
|
+
|
|
106
|
+
#
|
|
107
|
+
# Factories
|
|
108
|
+
#
|
|
108
109
|
|
|
109
110
|
@classmethod
|
|
110
111
|
def offer(cls, session: str = None) -> Command:
|
|
@@ -114,7 +115,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
114
115
|
:param session: Old session key
|
|
115
116
|
:return: HandshakeCommand object
|
|
116
117
|
"""
|
|
117
|
-
return
|
|
118
|
+
return BaseHandshakeCommand(title='Hello world!', session=session)
|
|
118
119
|
|
|
119
120
|
@classmethod
|
|
120
121
|
def ask(cls, session: str) -> Command:
|
|
@@ -124,7 +125,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
124
125
|
:param session: New session key
|
|
125
126
|
:return: HandshakeCommand object
|
|
126
127
|
"""
|
|
127
|
-
return
|
|
128
|
+
return BaseHandshakeCommand(title='DIM?', session=session)
|
|
128
129
|
|
|
129
130
|
@classmethod
|
|
130
131
|
def accepted(cls, session: str = None) -> Command:
|
|
@@ -133,7 +134,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
133
134
|
|
|
134
135
|
:return: HandshakeCommand object
|
|
135
136
|
"""
|
|
136
|
-
return
|
|
137
|
+
return BaseHandshakeCommand(title='DIM!', session=session)
|
|
137
138
|
|
|
138
139
|
start = offer # (1. C->S) first handshake, without session
|
|
139
140
|
again = ask # (2. S->C) ask client to handshake with new session key
|
|
@@ -141,17 +142,34 @@ class HandshakeCommand(BaseCommand):
|
|
|
141
142
|
success = accepted # (4. S->C) notice the client that handshake accepted
|
|
142
143
|
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
class BaseHandshakeCommand(BaseCommand, HandshakeCommand):
|
|
146
|
+
|
|
147
|
+
def __init__(self, content: Dict = None, title: str = None, session: str = None):
|
|
148
|
+
if content is None:
|
|
149
|
+
# 1. new command with title & session key
|
|
150
|
+
assert title is not None, 'handshake command error: %s' % session
|
|
151
|
+
cmd = self.HANDSHAKE
|
|
152
|
+
super().__init__(cmd=cmd)
|
|
153
|
+
self['title'] = title
|
|
154
|
+
self['message'] = title # TODO: remove after all clients upgraded
|
|
155
|
+
if session is not None:
|
|
156
|
+
self['session'] = session
|
|
157
|
+
else:
|
|
158
|
+
# 2. command info from network
|
|
159
|
+
assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
|
|
160
|
+
super().__init__(content)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def title(self) -> str:
|
|
164
|
+
return self.get_str(key='title', default='')
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def session(self) -> Optional[str]:
|
|
168
|
+
return self.get_str(key='session')
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def state(self) -> HandshakeState:
|
|
172
|
+
return handshake_state(title=self.title, session=self.session)
|
|
155
173
|
```
|
|
156
174
|
|
|
157
175
|
### extends Content
|
|
@@ -165,13 +183,13 @@ from dimp import Content
|
|
|
165
183
|
from dimp import BaseContent
|
|
166
184
|
|
|
167
185
|
|
|
168
|
-
class
|
|
186
|
+
class AppContent(Content, ABC):
|
|
169
187
|
"""
|
|
170
188
|
Application Customized message
|
|
171
189
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
172
190
|
|
|
173
191
|
data format: {
|
|
174
|
-
type :
|
|
192
|
+
type : 0xA0,
|
|
175
193
|
sn : 123,
|
|
176
194
|
|
|
177
195
|
app : "{APP_ID}", // application (e.g.: "chat.dim.sechat")
|
|
@@ -199,22 +217,25 @@ class CustomizedContent(Content, ABC):
|
|
|
199
217
|
""" Action Name """
|
|
200
218
|
raise NotImplemented
|
|
201
219
|
|
|
220
|
+
#
|
|
221
|
+
# Factory
|
|
222
|
+
#
|
|
202
223
|
@classmethod
|
|
203
224
|
def create(cls, app: str, mod: str, act: str):
|
|
204
|
-
return
|
|
225
|
+
return ApplicationContent(app=app, mod=mod, act=act)
|
|
205
226
|
|
|
206
227
|
|
|
207
|
-
class
|
|
228
|
+
class ApplicationContent(BaseContent, AppContent):
|
|
208
229
|
|
|
209
230
|
def __init__(self, content: Dict[str, Any] = None,
|
|
210
|
-
msg_type:
|
|
231
|
+
msg_type: str = None,
|
|
211
232
|
app: str = None, mod: str = None, act: str = None):
|
|
212
233
|
if content is None:
|
|
213
234
|
# 1. new content with type, application, module & action
|
|
214
235
|
assert app is not None and mod is not None and act is not None, \
|
|
215
236
|
'customized content error: %s, %s, %s, %s' % (msg_type, app, mod, act)
|
|
216
237
|
if msg_type is None:
|
|
217
|
-
msg_type = ContentType.
|
|
238
|
+
msg_type = ContentType.APPLICATION
|
|
218
239
|
super().__init__(None, msg_type)
|
|
219
240
|
self['app'] = app
|
|
220
241
|
self['mod'] = mod
|
|
@@ -244,5 +265,5 @@ class AppCustomizedContent(BaseContent, CustomizedContent):
|
|
|
244
265
|
|
|
245
266
|
----
|
|
246
267
|
|
|
247
|
-
Copyright © 2018 Albert Moky
|
|
268
|
+
Copyright © 2018-2025 Albert Moky
|
|
248
269
|
[](https://github.com/moky?tab=followers)
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
3. (S-C) handshake success
|
|
32
32
|
|
|
33
33
|
```python
|
|
34
|
+
from abc import ABC, abstractmethod
|
|
34
35
|
from enum import IntEnum
|
|
35
|
-
from typing import Optional,
|
|
36
|
+
from typing import Optional, Dict
|
|
36
37
|
|
|
37
38
|
from dimp import Command, BaseCommand
|
|
38
39
|
|
|
@@ -44,7 +45,20 @@ class HandshakeState(IntEnum):
|
|
|
44
45
|
Success = 3 # S -> C, handshake accepted
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
def handshake_state(title: str, session: str = None) -> HandshakeState:
|
|
49
|
+
# Server -> Client
|
|
50
|
+
if title == 'DIM!': # or title == 'OK!':
|
|
51
|
+
return HandshakeState.Success
|
|
52
|
+
if title == 'DIM?':
|
|
53
|
+
return HandshakeState.Again
|
|
54
|
+
# Client -> Server: "Hello world!"
|
|
55
|
+
if session is None or len(session) == 0:
|
|
56
|
+
return HandshakeState.Start
|
|
57
|
+
else:
|
|
58
|
+
return HandshakeState.Restart
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class HandshakeCommand(Command, ABC):
|
|
48
62
|
"""
|
|
49
63
|
Handshake Command
|
|
50
64
|
~~~~~~~~~~~~~~~~~
|
|
@@ -60,37 +74,24 @@ class HandshakeCommand(BaseCommand):
|
|
|
60
74
|
"""
|
|
61
75
|
HANDSHAKE = 'handshake'
|
|
62
76
|
|
|
63
|
-
def __init__(self, content: Dict[str, Any] = None, title: str = None, session: str = None):
|
|
64
|
-
if content is None:
|
|
65
|
-
# 1. new command with title & session key
|
|
66
|
-
assert title is not None, 'handshake command error: %s' % session
|
|
67
|
-
cmd = self.HANDSHAKE
|
|
68
|
-
super().__init__(cmd=cmd)
|
|
69
|
-
self['title'] = title
|
|
70
|
-
self['message'] = title # TODO: remove after all clients upgraded
|
|
71
|
-
if session is not None:
|
|
72
|
-
self['session'] = session
|
|
73
|
-
else:
|
|
74
|
-
# 2. command info from network
|
|
75
|
-
assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
|
|
76
|
-
super().__init__(content)
|
|
77
|
-
|
|
78
77
|
@property
|
|
78
|
+
@abstractmethod
|
|
79
79
|
def title(self) -> str:
|
|
80
|
-
|
|
81
|
-
text = self.get_str(key='title', default=None)
|
|
82
|
-
if text is None:
|
|
83
|
-
# compatible with v1.0
|
|
84
|
-
text = self.get_str(key='message', default='')
|
|
85
|
-
return text
|
|
80
|
+
raise NotImplemented
|
|
86
81
|
|
|
87
82
|
@property
|
|
83
|
+
@abstractmethod
|
|
88
84
|
def session(self) -> Optional[str]:
|
|
89
|
-
|
|
85
|
+
raise NotImplemented
|
|
90
86
|
|
|
91
87
|
@property
|
|
88
|
+
@abstractmethod
|
|
92
89
|
def state(self) -> HandshakeState:
|
|
93
|
-
|
|
90
|
+
raise NotImplemented
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# Factories
|
|
94
|
+
#
|
|
94
95
|
|
|
95
96
|
@classmethod
|
|
96
97
|
def offer(cls, session: str = None) -> Command:
|
|
@@ -100,7 +101,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
100
101
|
:param session: Old session key
|
|
101
102
|
:return: HandshakeCommand object
|
|
102
103
|
"""
|
|
103
|
-
return
|
|
104
|
+
return BaseHandshakeCommand(title='Hello world!', session=session)
|
|
104
105
|
|
|
105
106
|
@classmethod
|
|
106
107
|
def ask(cls, session: str) -> Command:
|
|
@@ -110,7 +111,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
110
111
|
:param session: New session key
|
|
111
112
|
:return: HandshakeCommand object
|
|
112
113
|
"""
|
|
113
|
-
return
|
|
114
|
+
return BaseHandshakeCommand(title='DIM?', session=session)
|
|
114
115
|
|
|
115
116
|
@classmethod
|
|
116
117
|
def accepted(cls, session: str = None) -> Command:
|
|
@@ -119,7 +120,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
119
120
|
|
|
120
121
|
:return: HandshakeCommand object
|
|
121
122
|
"""
|
|
122
|
-
return
|
|
123
|
+
return BaseHandshakeCommand(title='DIM!', session=session)
|
|
123
124
|
|
|
124
125
|
start = offer # (1. C->S) first handshake, without session
|
|
125
126
|
again = ask # (2. S->C) ask client to handshake with new session key
|
|
@@ -127,17 +128,34 @@ class HandshakeCommand(BaseCommand):
|
|
|
127
128
|
success = accepted # (4. S->C) notice the client that handshake accepted
|
|
128
129
|
|
|
129
130
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
class BaseHandshakeCommand(BaseCommand, HandshakeCommand):
|
|
132
|
+
|
|
133
|
+
def __init__(self, content: Dict = None, title: str = None, session: str = None):
|
|
134
|
+
if content is None:
|
|
135
|
+
# 1. new command with title & session key
|
|
136
|
+
assert title is not None, 'handshake command error: %s' % session
|
|
137
|
+
cmd = self.HANDSHAKE
|
|
138
|
+
super().__init__(cmd=cmd)
|
|
139
|
+
self['title'] = title
|
|
140
|
+
self['message'] = title # TODO: remove after all clients upgraded
|
|
141
|
+
if session is not None:
|
|
142
|
+
self['session'] = session
|
|
143
|
+
else:
|
|
144
|
+
# 2. command info from network
|
|
145
|
+
assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
|
|
146
|
+
super().__init__(content)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def title(self) -> str:
|
|
150
|
+
return self.get_str(key='title', default='')
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def session(self) -> Optional[str]:
|
|
154
|
+
return self.get_str(key='session')
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def state(self) -> HandshakeState:
|
|
158
|
+
return handshake_state(title=self.title, session=self.session)
|
|
141
159
|
```
|
|
142
160
|
|
|
143
161
|
### extends Content
|
|
@@ -151,13 +169,13 @@ from dimp import Content
|
|
|
151
169
|
from dimp import BaseContent
|
|
152
170
|
|
|
153
171
|
|
|
154
|
-
class
|
|
172
|
+
class AppContent(Content, ABC):
|
|
155
173
|
"""
|
|
156
174
|
Application Customized message
|
|
157
175
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
158
176
|
|
|
159
177
|
data format: {
|
|
160
|
-
type :
|
|
178
|
+
type : 0xA0,
|
|
161
179
|
sn : 123,
|
|
162
180
|
|
|
163
181
|
app : "{APP_ID}", // application (e.g.: "chat.dim.sechat")
|
|
@@ -185,22 +203,25 @@ class CustomizedContent(Content, ABC):
|
|
|
185
203
|
""" Action Name """
|
|
186
204
|
raise NotImplemented
|
|
187
205
|
|
|
206
|
+
#
|
|
207
|
+
# Factory
|
|
208
|
+
#
|
|
188
209
|
@classmethod
|
|
189
210
|
def create(cls, app: str, mod: str, act: str):
|
|
190
|
-
return
|
|
211
|
+
return ApplicationContent(app=app, mod=mod, act=act)
|
|
191
212
|
|
|
192
213
|
|
|
193
|
-
class
|
|
214
|
+
class ApplicationContent(BaseContent, AppContent):
|
|
194
215
|
|
|
195
216
|
def __init__(self, content: Dict[str, Any] = None,
|
|
196
|
-
msg_type:
|
|
217
|
+
msg_type: str = None,
|
|
197
218
|
app: str = None, mod: str = None, act: str = None):
|
|
198
219
|
if content is None:
|
|
199
220
|
# 1. new content with type, application, module & action
|
|
200
221
|
assert app is not None and mod is not None and act is not None, \
|
|
201
222
|
'customized content error: %s, %s, %s, %s' % (msg_type, app, mod, act)
|
|
202
223
|
if msg_type is None:
|
|
203
|
-
msg_type = ContentType.
|
|
224
|
+
msg_type = ContentType.APPLICATION
|
|
204
225
|
super().__init__(None, msg_type)
|
|
205
226
|
self['app'] = app
|
|
206
227
|
self['mod'] = mod
|
|
@@ -230,5 +251,5 @@ class AppCustomizedContent(BaseContent, CustomizedContent):
|
|
|
230
251
|
|
|
231
252
|
----
|
|
232
253
|
|
|
233
|
-
Copyright © 2018 Albert Moky
|
|
254
|
+
Copyright © 2018-2025 Albert Moky
|
|
234
255
|
[](https://github.com/moky?tab=followers)
|
|
@@ -119,6 +119,8 @@ __all__ = [
|
|
|
119
119
|
'Meta',
|
|
120
120
|
'Document', 'Visa', 'Bulletin',
|
|
121
121
|
|
|
122
|
+
'MetaType', 'DocumentType',
|
|
123
|
+
|
|
122
124
|
'AddressFactory',
|
|
123
125
|
'IDFactory',
|
|
124
126
|
'MetaFactory',
|
|
@@ -132,8 +134,6 @@ __all__ = [
|
|
|
132
134
|
'BaseMeta',
|
|
133
135
|
'BaseDocument', 'BaseVisa', 'BaseBulletin',
|
|
134
136
|
|
|
135
|
-
'MetaType', 'DocumentType',
|
|
136
|
-
|
|
137
137
|
#
|
|
138
138
|
# DaoKeDao
|
|
139
139
|
#
|
|
@@ -145,11 +145,12 @@ __all__ = [
|
|
|
145
145
|
'InstantMessage', 'SecureMessage', 'ReliableMessage',
|
|
146
146
|
|
|
147
147
|
# contents
|
|
148
|
-
'TextContent', '
|
|
149
|
-
'
|
|
148
|
+
'TextContent', 'PageContent', 'NameCard',
|
|
149
|
+
'ForwardContent', 'CombineContent', 'ArrayContent',
|
|
150
150
|
'FileContent', 'ImageContent', 'AudioContent', 'VideoContent',
|
|
151
151
|
'MoneyContent', 'TransferContent',
|
|
152
|
-
'QuoteContent',
|
|
152
|
+
'QuoteContent',
|
|
153
|
+
'CustomizedContent',
|
|
153
154
|
|
|
154
155
|
# commands
|
|
155
156
|
'Command',
|
|
@@ -163,11 +164,12 @@ __all__ = [
|
|
|
163
164
|
|
|
164
165
|
# extend contents
|
|
165
166
|
'BaseContent',
|
|
166
|
-
'BaseTextContent', '
|
|
167
|
-
'
|
|
167
|
+
'BaseTextContent', 'WebPageContent', 'NameCardContent',
|
|
168
|
+
'SecretContent', 'CombineForwardContent', 'ListContent',
|
|
168
169
|
'BaseFileContent', 'ImageFileContent', 'AudioFileContent', 'VideoFileContent',
|
|
169
170
|
'BaseMoneyContent', 'TransferMoneyContent',
|
|
170
|
-
'BaseQuoteContent',
|
|
171
|
+
'BaseQuoteContent',
|
|
172
|
+
'AppCustomizedContent',
|
|
171
173
|
|
|
172
174
|
# extend commands
|
|
173
175
|
'BaseCommand',
|
|
@@ -60,7 +60,7 @@ class BaseKey(Dictionary, CryptographyKey, ABC):
|
|
|
60
60
|
@classmethod
|
|
61
61
|
def get_key_algorithm(cls, key: Dict) -> str:
|
|
62
62
|
ext = SharedCryptoExtensions()
|
|
63
|
-
algorithm = ext.helper.get_key_algorithm(key=key
|
|
63
|
+
algorithm = ext.helper.get_key_algorithm(key=key)
|
|
64
64
|
return '' if algorithm is None else algorithm
|
|
65
65
|
|
|
66
66
|
@classmethod
|
|
@@ -100,7 +100,7 @@ class BaseFileWrapper(Dictionary):
|
|
|
100
100
|
|
|
101
101
|
@property
|
|
102
102
|
def filename(self) -> Optional[str]:
|
|
103
|
-
return self.get_str(key='filename'
|
|
103
|
+
return self.get_str(key='filename')
|
|
104
104
|
|
|
105
105
|
@filename.setter
|
|
106
106
|
def filename(self, name: Optional[str]):
|
|
@@ -117,7 +117,7 @@ class BaseFileWrapper(Dictionary):
|
|
|
117
117
|
def url(self) -> Optional[URI]:
|
|
118
118
|
remote = self.__url
|
|
119
119
|
if remote is None:
|
|
120
|
-
remote = self.get_str(key='URL'
|
|
120
|
+
remote = self.get_str(key='URL')
|
|
121
121
|
# TODO: convert str to URI
|
|
122
122
|
self.__url = remote
|
|
123
123
|
return remote
|
|
@@ -69,10 +69,10 @@ class BaseDataWrapper(Dictionary):
|
|
|
69
69
|
|
|
70
70
|
# Override
|
|
71
71
|
def __str__(self) -> str:
|
|
72
|
-
text = self.get_str(key='data'
|
|
72
|
+
text = self.get_str(key='data')
|
|
73
73
|
if text is None: # or len(text) == 0:
|
|
74
74
|
return ''
|
|
75
|
-
alg = self.get_str(key='algorithm'
|
|
75
|
+
alg = self.get_str(key='algorithm')
|
|
76
76
|
if alg is None or alg == EncodeAlgorithms.DEFAULT:
|
|
77
77
|
alg = ''
|
|
78
78
|
if len(alg) == 0:
|
|
@@ -85,7 +85,7 @@ class BaseDataWrapper(Dictionary):
|
|
|
85
85
|
def encode(self, mime_type: str) -> str:
|
|
86
86
|
""" toString(mimeType) """
|
|
87
87
|
# get encoded data
|
|
88
|
-
text = self.get_str(key='data'
|
|
88
|
+
text = self.get_str(key='data')
|
|
89
89
|
if text is None: # or len(text) == 0:
|
|
90
90
|
return ''
|
|
91
91
|
alg = self.algorithm
|
|
@@ -98,7 +98,7 @@ class BaseDataWrapper(Dictionary):
|
|
|
98
98
|
|
|
99
99
|
@property
|
|
100
100
|
def algorithm(self) -> str:
|
|
101
|
-
alg = self.get_str(key='algorithm'
|
|
101
|
+
alg = self.get_str(key='algorithm')
|
|
102
102
|
if alg is None or len(alg) == 0:
|
|
103
103
|
alg = EncodeAlgorithms.DEFAULT
|
|
104
104
|
return alg
|
|
@@ -118,7 +118,7 @@ class BaseDataWrapper(Dictionary):
|
|
|
118
118
|
def data(self) -> Optional[bytes]:
|
|
119
119
|
binary = self.__data
|
|
120
120
|
if binary is None:
|
|
121
|
-
text = self.get_str(key='data'
|
|
121
|
+
text = self.get_str(key='data')
|
|
122
122
|
if text is None or len(text) == 0:
|
|
123
123
|
# assert False, 'TED data empty: %s' % self.dictionary
|
|
124
124
|
return None
|
|
@@ -37,6 +37,7 @@ from ..protocol.forward import SecretContent, CombineForwardContent, ListContent
|
|
|
37
37
|
from ..protocol.files import BaseFileContent, ImageFileContent, AudioFileContent, VideoFileContent
|
|
38
38
|
from ..protocol.money import BaseMoneyContent, TransferMoneyContent
|
|
39
39
|
from ..protocol.quote import BaseQuoteContent
|
|
40
|
+
from ..protocol.customized import AppCustomizedContent
|
|
40
41
|
|
|
41
42
|
from ..protocol.base import BaseCommand
|
|
42
43
|
from ..protocol.commands import BaseMetaCommand, BaseDocumentCommand
|
|
@@ -63,11 +64,12 @@ __all__ = [
|
|
|
63
64
|
'InstantMessage', 'SecureMessage', 'ReliableMessage',
|
|
64
65
|
|
|
65
66
|
# contents
|
|
66
|
-
'TextContent', '
|
|
67
|
-
'
|
|
67
|
+
'TextContent', 'PageContent', 'NameCard',
|
|
68
|
+
'ForwardContent', 'CombineContent', 'ArrayContent',
|
|
68
69
|
'FileContent', 'ImageContent', 'AudioContent', 'VideoContent',
|
|
69
70
|
'MoneyContent', 'TransferContent',
|
|
70
|
-
'QuoteContent',
|
|
71
|
+
'QuoteContent',
|
|
72
|
+
'CustomizedContent',
|
|
71
73
|
|
|
72
74
|
# commands
|
|
73
75
|
'Command',
|
|
@@ -93,11 +95,12 @@ __all__ = [
|
|
|
93
95
|
|
|
94
96
|
# contents
|
|
95
97
|
'BaseContent',
|
|
96
|
-
'BaseTextContent', '
|
|
97
|
-
'
|
|
98
|
+
'BaseTextContent', 'WebPageContent', 'NameCardContent',
|
|
99
|
+
'SecretContent', 'CombineForwardContent', 'ListContent',
|
|
98
100
|
'BaseFileContent', 'ImageFileContent', 'AudioFileContent', 'VideoFileContent',
|
|
99
101
|
'BaseMoneyContent', 'TransferMoneyContent',
|
|
100
|
-
'BaseQuoteContent',
|
|
102
|
+
'BaseQuoteContent',
|
|
103
|
+
'AppCustomizedContent',
|
|
101
104
|
|
|
102
105
|
# commands
|
|
103
106
|
'BaseCommand',
|
|
@@ -114,7 +114,7 @@ class BaseDocument(Dictionary, Document):
|
|
|
114
114
|
:return: JsON string
|
|
115
115
|
"""
|
|
116
116
|
if self.__json is None:
|
|
117
|
-
self.__json = self.get_str(key='data'
|
|
117
|
+
self.__json = self.get_str(key='data')
|
|
118
118
|
return self.__json
|
|
119
119
|
|
|
120
120
|
@property # private
|
|
@@ -259,12 +259,12 @@ class BaseDocument(Dictionary, Document):
|
|
|
259
259
|
@property # Override
|
|
260
260
|
def time(self) -> Optional[DateTime]:
|
|
261
261
|
seconds = self.get_property(name='time')
|
|
262
|
-
return Converter.get_datetime(value=seconds
|
|
262
|
+
return Converter.get_datetime(value=seconds)
|
|
263
263
|
|
|
264
264
|
@property # Override
|
|
265
265
|
def name(self) -> Optional[str]:
|
|
266
266
|
text = self.get_property(name='name')
|
|
267
|
-
return Converter.get_str(value=text
|
|
267
|
+
return Converter.get_str(value=text)
|
|
268
268
|
|
|
269
269
|
@name.setter # Override
|
|
270
270
|
def name(self, text: str):
|
|
@@ -102,7 +102,7 @@ class MessageEnvelope(Dictionary, Envelope):
|
|
|
102
102
|
@property # Override
|
|
103
103
|
def time(self) -> Optional[DateTime]:
|
|
104
104
|
if self.__time is None:
|
|
105
|
-
self.__time = self.get_datetime(key='time'
|
|
105
|
+
self.__time = self.get_datetime(key='time')
|
|
106
106
|
return self.__time
|
|
107
107
|
|
|
108
108
|
@property # Override
|
|
@@ -116,7 +116,7 @@ class MessageEnvelope(Dictionary, Envelope):
|
|
|
116
116
|
|
|
117
117
|
@property # Override
|
|
118
118
|
def type(self) -> Optional[str]:
|
|
119
|
-
return self.get_str(key='type'
|
|
119
|
+
return self.get_str(key='type')
|
|
120
120
|
|
|
121
121
|
@type.setter # Override
|
|
122
122
|
def type(self, value: str):
|
|
@@ -52,6 +52,7 @@ from .forward import ForwardContent, CombineContent, ArrayContent
|
|
|
52
52
|
from .files import FileContent, ImageContent, AudioContent, VideoContent
|
|
53
53
|
from .money import MoneyContent, TransferContent
|
|
54
54
|
from .quote import QuoteContent
|
|
55
|
+
from .customized import CustomizedContent
|
|
55
56
|
|
|
56
57
|
from .base import Command, CommandFactory
|
|
57
58
|
from .commands import MetaCommand, DocumentCommand
|
|
@@ -108,6 +109,7 @@ __all__ = [
|
|
|
108
109
|
'FileContent', 'ImageContent', 'AudioContent', 'VideoContent',
|
|
109
110
|
'MoneyContent', 'TransferContent',
|
|
110
111
|
'QuoteContent',
|
|
112
|
+
'CustomizedContent',
|
|
111
113
|
|
|
112
114
|
# commands
|
|
113
115
|
'Command', # 'CommandFactory',
|
|
@@ -226,7 +226,7 @@ class BaseContent(Dictionary, Content):
|
|
|
226
226
|
@property # Override
|
|
227
227
|
def time(self) -> Optional[DateTime]:
|
|
228
228
|
if self.__time is None:
|
|
229
|
-
self.__time = self.get_datetime(key='time'
|
|
229
|
+
self.__time = self.get_datetime(key='time')
|
|
230
230
|
return self.__time
|
|
231
231
|
|
|
232
232
|
@property # Override
|
|
@@ -291,7 +291,7 @@ class WebPageContent(BaseContent, PageContent):
|
|
|
291
291
|
def icon(self) -> Optional[PortableNetworkFile]:
|
|
292
292
|
img = self.__icon
|
|
293
293
|
if img is None:
|
|
294
|
-
base64 = self.get_str(key='icon'
|
|
294
|
+
base64 = self.get_str(key='icon')
|
|
295
295
|
img = self.__icon = PortableNetworkFile.parse(base64)
|
|
296
296
|
return img
|
|
297
297
|
|
|
@@ -309,7 +309,7 @@ class WebPageContent(BaseContent, PageContent):
|
|
|
309
309
|
|
|
310
310
|
@property # Override
|
|
311
311
|
def desc(self) -> Optional[str]:
|
|
312
|
-
return self.get_str(key='desc'
|
|
312
|
+
return self.get_str(key='desc')
|
|
313
313
|
|
|
314
314
|
@desc.setter # Override
|
|
315
315
|
def desc(self, text: Optional[str]):
|
|
@@ -325,7 +325,7 @@ class WebPageContent(BaseContent, PageContent):
|
|
|
325
325
|
@property # Override
|
|
326
326
|
def url(self) -> URI:
|
|
327
327
|
# TODO: convert str to URI
|
|
328
|
-
return self.get_str(key='URL'
|
|
328
|
+
return self.get_str(key='URL')
|
|
329
329
|
|
|
330
330
|
@url.setter # Override
|
|
331
331
|
def url(self, locator: URI):
|
|
@@ -338,7 +338,7 @@ class WebPageContent(BaseContent, PageContent):
|
|
|
338
338
|
|
|
339
339
|
@property # Override
|
|
340
340
|
def html(self) -> Optional[str]:
|
|
341
|
-
return self.get_str(key='desc'
|
|
341
|
+
return self.get_str(key='desc')
|
|
342
342
|
|
|
343
343
|
@html.setter # Override
|
|
344
344
|
def html(self, content: Optional[str]):
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# DIMP : Decentralized Instant Messaging Protocol
|
|
4
|
+
#
|
|
5
|
+
# Written in 2019 by Moky <albert.moky@gmail.com>
|
|
6
|
+
#
|
|
7
|
+
# ==============================================================================
|
|
8
|
+
# MIT License
|
|
9
|
+
#
|
|
10
|
+
# Copyright (c) 2019 Albert Moky
|
|
11
|
+
#
|
|
12
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
# in the Software without restriction, including without limitation the rights
|
|
15
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
# furnished to do so, subject to the following conditions:
|
|
18
|
+
#
|
|
19
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
# copies or substantial portions of the Software.
|
|
21
|
+
#
|
|
22
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
# SOFTWARE.
|
|
29
|
+
# ==============================================================================
|
|
30
|
+
|
|
31
|
+
from abc import ABC, abstractmethod
|
|
32
|
+
from typing import Dict
|
|
33
|
+
|
|
34
|
+
from dkd import Content
|
|
35
|
+
|
|
36
|
+
from .types import ContentType
|
|
37
|
+
from .base import BaseContent
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CustomizedContent(Content, ABC):
|
|
41
|
+
"""
|
|
42
|
+
Application Customized message
|
|
43
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
44
|
+
|
|
45
|
+
data format: {
|
|
46
|
+
type : i2s(0xCC),
|
|
47
|
+
sn : 123,
|
|
48
|
+
|
|
49
|
+
app : "{APP_ID}", // application (e.g.: "chat.dim.sechat")
|
|
50
|
+
mod : "{MODULE}", // module name (e.g.: "drift_bottle")
|
|
51
|
+
act : "{ACTION}", // action name (e.g.: "throw")
|
|
52
|
+
extra : info // action parameters
|
|
53
|
+
}
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def application(self) -> str:
|
|
59
|
+
""" App ID """
|
|
60
|
+
raise NotImplemented
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def module(self) -> str:
|
|
65
|
+
""" Module Name """
|
|
66
|
+
raise NotImplemented
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def action(self) -> str:
|
|
71
|
+
""" Action Name """
|
|
72
|
+
raise NotImplemented
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Factory method
|
|
76
|
+
#
|
|
77
|
+
@classmethod
|
|
78
|
+
def create(cls, app: str, mod: str, act: str):
|
|
79
|
+
return AppCustomizedContent(app=app, mod=mod, act=act)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class AppCustomizedContent(BaseContent, CustomizedContent):
|
|
83
|
+
|
|
84
|
+
def __init__(self, content: Dict = None,
|
|
85
|
+
msg_type: str = None,
|
|
86
|
+
app: str = None, mod: str = None, act: str = None):
|
|
87
|
+
if content is None:
|
|
88
|
+
# 1. new content with type, application, module & action
|
|
89
|
+
assert app is not None and mod is not None and act is not None, \
|
|
90
|
+
'customized content error: %s, %s, %s, %s' % (msg_type, app, mod, act)
|
|
91
|
+
if msg_type is None:
|
|
92
|
+
msg_type = ContentType.CUSTOMIZED
|
|
93
|
+
super().__init__(None, msg_type)
|
|
94
|
+
self['app'] = app
|
|
95
|
+
self['mod'] = mod
|
|
96
|
+
self['act'] = act
|
|
97
|
+
else:
|
|
98
|
+
# 2. content info from network
|
|
99
|
+
assert msg_type is None and app is None and mod is None and act is None, \
|
|
100
|
+
'params error: %s, %s, %s, %s, %s' % (content, msg_type, app, mod, act)
|
|
101
|
+
super().__init__(content)
|
|
102
|
+
|
|
103
|
+
@property # Override
|
|
104
|
+
def application(self) -> str:
|
|
105
|
+
return self.get_str(key='app', default='')
|
|
106
|
+
|
|
107
|
+
@property # Override
|
|
108
|
+
def module(self) -> str:
|
|
109
|
+
return self.get_str(key='mod', default='')
|
|
110
|
+
|
|
111
|
+
@property # Override
|
|
112
|
+
def action(self) -> str:
|
|
113
|
+
return self.get_str(key='act', default='')
|
|
@@ -341,7 +341,7 @@ class ImageFileContent(BaseFileContent, ImageContent):
|
|
|
341
341
|
def thumbnail(self) -> Optional[PortableNetworkFile]:
|
|
342
342
|
img = self.__thumbnail
|
|
343
343
|
if img is None:
|
|
344
|
-
base64 = self.get_str(key='thumbnail'
|
|
344
|
+
base64 = self.get_str(key='thumbnail')
|
|
345
345
|
img = self.__thumbnail = PortableNetworkFile.parse(base64)
|
|
346
346
|
return img
|
|
347
347
|
|
|
@@ -365,7 +365,7 @@ class AudioFileContent(BaseFileContent, AudioContent):
|
|
|
365
365
|
|
|
366
366
|
@property # Override
|
|
367
367
|
def text(self) -> Optional[str]:
|
|
368
|
-
return self.get_str(key='text'
|
|
368
|
+
return self.get_str(key='text')
|
|
369
369
|
|
|
370
370
|
@text.setter # Override
|
|
371
371
|
def text(self, asr: str):
|
|
@@ -387,7 +387,7 @@ class VideoFileContent(BaseFileContent, VideoContent):
|
|
|
387
387
|
def snapshot(self) -> Optional[PortableNetworkFile]:
|
|
388
388
|
img = self.__snapshot
|
|
389
389
|
if img is None:
|
|
390
|
-
base64 = self.get_str(key='snapshot'
|
|
390
|
+
base64 = self.get_str(key='snapshot')
|
|
391
391
|
img = self.__snapshot = PortableNetworkFile.parse(base64)
|
|
392
392
|
return img
|
|
393
393
|
|
|
@@ -385,7 +385,7 @@ class QueryGroupCommand(BaseGroupCommand, QueryCommand):
|
|
|
385
385
|
|
|
386
386
|
@property # Override
|
|
387
387
|
def last_time(self) -> Optional[DateTime]:
|
|
388
|
-
return self.get_datetime(key='last_time'
|
|
388
|
+
return self.get_datetime(key='last_time')
|
|
389
389
|
|
|
390
390
|
|
|
391
391
|
class ResetGroupCommand(BaseGroupCommand, ResetCommand):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dimp
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: Decentralized Instant Messaging Protocol
|
|
5
5
|
Home-page: https://github.com/dimchat/core-py
|
|
6
6
|
Author: Albert Moky
|
|
@@ -45,8 +45,9 @@ License-File: LICENSE
|
|
|
45
45
|
3. (S-C) handshake success
|
|
46
46
|
|
|
47
47
|
```python
|
|
48
|
+
from abc import ABC, abstractmethod
|
|
48
49
|
from enum import IntEnum
|
|
49
|
-
from typing import Optional,
|
|
50
|
+
from typing import Optional, Dict
|
|
50
51
|
|
|
51
52
|
from dimp import Command, BaseCommand
|
|
52
53
|
|
|
@@ -58,7 +59,20 @@ class HandshakeState(IntEnum):
|
|
|
58
59
|
Success = 3 # S -> C, handshake accepted
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
def handshake_state(title: str, session: str = None) -> HandshakeState:
|
|
63
|
+
# Server -> Client
|
|
64
|
+
if title == 'DIM!': # or title == 'OK!':
|
|
65
|
+
return HandshakeState.Success
|
|
66
|
+
if title == 'DIM?':
|
|
67
|
+
return HandshakeState.Again
|
|
68
|
+
# Client -> Server: "Hello world!"
|
|
69
|
+
if session is None or len(session) == 0:
|
|
70
|
+
return HandshakeState.Start
|
|
71
|
+
else:
|
|
72
|
+
return HandshakeState.Restart
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class HandshakeCommand(Command, ABC):
|
|
62
76
|
"""
|
|
63
77
|
Handshake Command
|
|
64
78
|
~~~~~~~~~~~~~~~~~
|
|
@@ -74,37 +88,24 @@ class HandshakeCommand(BaseCommand):
|
|
|
74
88
|
"""
|
|
75
89
|
HANDSHAKE = 'handshake'
|
|
76
90
|
|
|
77
|
-
def __init__(self, content: Dict[str, Any] = None, title: str = None, session: str = None):
|
|
78
|
-
if content is None:
|
|
79
|
-
# 1. new command with title & session key
|
|
80
|
-
assert title is not None, 'handshake command error: %s' % session
|
|
81
|
-
cmd = self.HANDSHAKE
|
|
82
|
-
super().__init__(cmd=cmd)
|
|
83
|
-
self['title'] = title
|
|
84
|
-
self['message'] = title # TODO: remove after all clients upgraded
|
|
85
|
-
if session is not None:
|
|
86
|
-
self['session'] = session
|
|
87
|
-
else:
|
|
88
|
-
# 2. command info from network
|
|
89
|
-
assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
|
|
90
|
-
super().__init__(content)
|
|
91
|
-
|
|
92
91
|
@property
|
|
92
|
+
@abstractmethod
|
|
93
93
|
def title(self) -> str:
|
|
94
|
-
|
|
95
|
-
text = self.get_str(key='title', default=None)
|
|
96
|
-
if text is None:
|
|
97
|
-
# compatible with v1.0
|
|
98
|
-
text = self.get_str(key='message', default='')
|
|
99
|
-
return text
|
|
94
|
+
raise NotImplemented
|
|
100
95
|
|
|
101
96
|
@property
|
|
97
|
+
@abstractmethod
|
|
102
98
|
def session(self) -> Optional[str]:
|
|
103
|
-
|
|
99
|
+
raise NotImplemented
|
|
104
100
|
|
|
105
101
|
@property
|
|
102
|
+
@abstractmethod
|
|
106
103
|
def state(self) -> HandshakeState:
|
|
107
|
-
|
|
104
|
+
raise NotImplemented
|
|
105
|
+
|
|
106
|
+
#
|
|
107
|
+
# Factories
|
|
108
|
+
#
|
|
108
109
|
|
|
109
110
|
@classmethod
|
|
110
111
|
def offer(cls, session: str = None) -> Command:
|
|
@@ -114,7 +115,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
114
115
|
:param session: Old session key
|
|
115
116
|
:return: HandshakeCommand object
|
|
116
117
|
"""
|
|
117
|
-
return
|
|
118
|
+
return BaseHandshakeCommand(title='Hello world!', session=session)
|
|
118
119
|
|
|
119
120
|
@classmethod
|
|
120
121
|
def ask(cls, session: str) -> Command:
|
|
@@ -124,7 +125,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
124
125
|
:param session: New session key
|
|
125
126
|
:return: HandshakeCommand object
|
|
126
127
|
"""
|
|
127
|
-
return
|
|
128
|
+
return BaseHandshakeCommand(title='DIM?', session=session)
|
|
128
129
|
|
|
129
130
|
@classmethod
|
|
130
131
|
def accepted(cls, session: str = None) -> Command:
|
|
@@ -133,7 +134,7 @@ class HandshakeCommand(BaseCommand):
|
|
|
133
134
|
|
|
134
135
|
:return: HandshakeCommand object
|
|
135
136
|
"""
|
|
136
|
-
return
|
|
137
|
+
return BaseHandshakeCommand(title='DIM!', session=session)
|
|
137
138
|
|
|
138
139
|
start = offer # (1. C->S) first handshake, without session
|
|
139
140
|
again = ask # (2. S->C) ask client to handshake with new session key
|
|
@@ -141,17 +142,34 @@ class HandshakeCommand(BaseCommand):
|
|
|
141
142
|
success = accepted # (4. S->C) notice the client that handshake accepted
|
|
142
143
|
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
class BaseHandshakeCommand(BaseCommand, HandshakeCommand):
|
|
146
|
+
|
|
147
|
+
def __init__(self, content: Dict = None, title: str = None, session: str = None):
|
|
148
|
+
if content is None:
|
|
149
|
+
# 1. new command with title & session key
|
|
150
|
+
assert title is not None, 'handshake command error: %s' % session
|
|
151
|
+
cmd = self.HANDSHAKE
|
|
152
|
+
super().__init__(cmd=cmd)
|
|
153
|
+
self['title'] = title
|
|
154
|
+
self['message'] = title # TODO: remove after all clients upgraded
|
|
155
|
+
if session is not None:
|
|
156
|
+
self['session'] = session
|
|
157
|
+
else:
|
|
158
|
+
# 2. command info from network
|
|
159
|
+
assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
|
|
160
|
+
super().__init__(content)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def title(self) -> str:
|
|
164
|
+
return self.get_str(key='title', default='')
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def session(self) -> Optional[str]:
|
|
168
|
+
return self.get_str(key='session')
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def state(self) -> HandshakeState:
|
|
172
|
+
return handshake_state(title=self.title, session=self.session)
|
|
155
173
|
```
|
|
156
174
|
|
|
157
175
|
### extends Content
|
|
@@ -165,13 +183,13 @@ from dimp import Content
|
|
|
165
183
|
from dimp import BaseContent
|
|
166
184
|
|
|
167
185
|
|
|
168
|
-
class
|
|
186
|
+
class AppContent(Content, ABC):
|
|
169
187
|
"""
|
|
170
188
|
Application Customized message
|
|
171
189
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
172
190
|
|
|
173
191
|
data format: {
|
|
174
|
-
type :
|
|
192
|
+
type : 0xA0,
|
|
175
193
|
sn : 123,
|
|
176
194
|
|
|
177
195
|
app : "{APP_ID}", // application (e.g.: "chat.dim.sechat")
|
|
@@ -199,22 +217,25 @@ class CustomizedContent(Content, ABC):
|
|
|
199
217
|
""" Action Name """
|
|
200
218
|
raise NotImplemented
|
|
201
219
|
|
|
220
|
+
#
|
|
221
|
+
# Factory
|
|
222
|
+
#
|
|
202
223
|
@classmethod
|
|
203
224
|
def create(cls, app: str, mod: str, act: str):
|
|
204
|
-
return
|
|
225
|
+
return ApplicationContent(app=app, mod=mod, act=act)
|
|
205
226
|
|
|
206
227
|
|
|
207
|
-
class
|
|
228
|
+
class ApplicationContent(BaseContent, AppContent):
|
|
208
229
|
|
|
209
230
|
def __init__(self, content: Dict[str, Any] = None,
|
|
210
|
-
msg_type:
|
|
231
|
+
msg_type: str = None,
|
|
211
232
|
app: str = None, mod: str = None, act: str = None):
|
|
212
233
|
if content is None:
|
|
213
234
|
# 1. new content with type, application, module & action
|
|
214
235
|
assert app is not None and mod is not None and act is not None, \
|
|
215
236
|
'customized content error: %s, %s, %s, %s' % (msg_type, app, mod, act)
|
|
216
237
|
if msg_type is None:
|
|
217
|
-
msg_type = ContentType.
|
|
238
|
+
msg_type = ContentType.APPLICATION
|
|
218
239
|
super().__init__(None, msg_type)
|
|
219
240
|
self['app'] = app
|
|
220
241
|
self['mod'] = mod
|
|
@@ -244,5 +265,5 @@ class AppCustomizedContent(BaseContent, CustomizedContent):
|
|
|
244
265
|
|
|
245
266
|
----
|
|
246
267
|
|
|
247
|
-
Copyright © 2018 Albert Moky
|
|
268
|
+
Copyright © 2018-2025 Albert Moky
|
|
248
269
|
[](https://github.com/moky?tab=followers)
|
|
@@ -14,7 +14,7 @@ import io
|
|
|
14
14
|
|
|
15
15
|
from setuptools import setup, find_packages
|
|
16
16
|
|
|
17
|
-
__version__ = '2.3.
|
|
17
|
+
__version__ = '2.3.3'
|
|
18
18
|
__author__ = 'Albert Moky'
|
|
19
19
|
__contact__ = 'albert.moky@gmail.com'
|
|
20
20
|
|
|
@@ -38,7 +38,7 @@ setup(
|
|
|
38
38
|
'Operating System :: OS Independent',
|
|
39
39
|
],
|
|
40
40
|
install_requires=[
|
|
41
|
-
'dkd>=2.3.
|
|
42
|
-
'mkm>=2.3.
|
|
41
|
+
'dkd>=2.3.3',
|
|
42
|
+
'mkm>=2.3.3',
|
|
43
43
|
]
|
|
44
44
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|