dimp 2.0.0__tar.gz → 2.2.1__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 (60) hide show
  1. dimp-2.2.1/PKG-INFO +255 -0
  2. dimp-2.2.1/README.md +241 -0
  3. {dimp-2.0.0 → dimp-2.2.1}/dimp/__init__.py +90 -47
  4. {dimp-2.0.0 → dimp-2.2.1}/dimp/crypto/__init__.py +8 -6
  5. {dimp-2.0.0 → dimp-2.2.1}/dimp/crypto/keys.py +16 -17
  6. {dimp-2.0.0 → dimp-2.2.1}/dimp/crypto/pnf.py +2 -2
  7. {dimp-2.0.0 → dimp-2.2.1}/dimp/crypto/ted.py +7 -5
  8. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/__init__.py +42 -22
  9. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/base.py +6 -6
  10. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/contents.py +18 -14
  11. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/files.py +30 -13
  12. dimp-2.2.1/dimp/dkd/quote.py +140 -0
  13. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/receipt.py +2 -10
  14. dimp-2.0.0/dimp/mkm/delegate.py → dimp-2.2.1/dimp/mkm/__init__.py +38 -22
  15. {dimp-2.0.0 → dimp-2.2.1}/dimp/mkm/docs.py +9 -9
  16. {dimp-2.0.0 → dimp-2.2.1}/dimp/mkm/document.py +13 -13
  17. {dimp-2.0.0 → dimp-2.2.1}/dimp/mkm/meta.py +50 -79
  18. {dimp-2.0.0/dimp/mkm → dimp-2.2.1/dimp/msg}/__init__.py +25 -31
  19. {dimp-2.0.0 → dimp-2.2.1}/dimp/msg/instant.py +5 -6
  20. dimp-2.2.1/dimp/plugins/__init__.py +88 -0
  21. dimp-2.0.0/dimp/protocol/customized.py → dimp-2.2.1/dimp/plugins/cmd.py +36 -33
  22. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/__init__.py +33 -26
  23. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/commands.py +38 -14
  24. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/contents.py +10 -10
  25. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/files.py +5 -4
  26. dimp-2.0.0/dimp/msg/__init__.py → dimp-2.2.1/dimp/protocol/helpers.py +9 -17
  27. dimp-2.2.1/dimp/protocol/quote.py +159 -0
  28. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/receipt.py +2 -56
  29. dimp-2.2.1/dimp.egg-info/PKG-INFO +255 -0
  30. {dimp-2.0.0 → dimp-2.2.1}/dimp.egg-info/SOURCES.txt +5 -12
  31. dimp-2.2.1/dimp.egg-info/requires.txt +2 -0
  32. {dimp-2.0.0 → dimp-2.2.1}/setup.py +3 -3
  33. dimp-2.0.0/PKG-INFO +0 -22
  34. dimp-2.0.0/README.md +0 -8
  35. dimp-2.0.0/dimp/barrack.py +0 -236
  36. dimp-2.0.0/dimp/dkd/customized.py +0 -85
  37. dimp-2.0.0/dimp/dkd/factory.py +0 -89
  38. dimp-2.0.0/dimp/mkm/entity.py +0 -188
  39. dimp-2.0.0/dimp/mkm/group.py +0 -191
  40. dimp-2.0.0/dimp/mkm/helper.py +0 -168
  41. dimp-2.0.0/dimp/mkm/user.py +0 -331
  42. dimp-2.0.0/dimp/packer.py +0 -108
  43. dimp-2.0.0/dimp/processor.py +0 -93
  44. dimp-2.0.0/dimp/transceiver.py +0 -218
  45. dimp-2.0.0/dimp.egg-info/PKG-INFO +0 -22
  46. dimp-2.0.0/dimp.egg-info/requires.txt +0 -2
  47. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/commands.py +0 -0
  48. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/group_admins.py +0 -0
  49. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/groups.py +0 -0
  50. {dimp-2.0.0 → dimp-2.2.1}/dimp/dkd/money.py +0 -0
  51. {dimp-2.0.0 → dimp-2.2.1}/dimp/msg/base.py +0 -0
  52. {dimp-2.0.0 → dimp-2.2.1}/dimp/msg/envelope.py +0 -0
  53. {dimp-2.0.0 → dimp-2.2.1}/dimp/msg/reliable.py +0 -0
  54. {dimp-2.0.0 → dimp-2.2.1}/dimp/msg/secure.py +0 -0
  55. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/docs.py +0 -0
  56. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/groups.py +0 -0
  57. {dimp-2.0.0 → dimp-2.2.1}/dimp/protocol/money.py +0 -0
  58. {dimp-2.0.0 → dimp-2.2.1}/dimp.egg-info/dependency_links.txt +0 -0
  59. {dimp-2.0.0 → dimp-2.2.1}/dimp.egg-info/top_level.txt +0 -0
  60. {dimp-2.0.0 → dimp-2.2.1}/setup.cfg +0 -0
dimp-2.2.1/PKG-INFO ADDED
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.1
2
+ Name: dimp
3
+ Version: 2.2.1
4
+ Summary: Decentralized Instant Messaging Protocol
5
+ Home-page: https://github.com/dimchat/core-py
6
+ Author: Albert Moky
7
+ Author-email: albert.moky@gmail.com
8
+ License: MIT
9
+ Description: # Decentralized Instant Messaging Protocol (Python)
10
+
11
+ [![License](https://img.shields.io/github/license/dimchat/core-py)](https://github.com/dimchat/core-py/blob/master/LICENSE)
12
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/dimchat/core-py/pulls)
13
+ [![Platform](https://img.shields.io/badge/Platform-Python%203-brightgreen.svg)](https://github.com/dimchat/core-py/wiki)
14
+ [![Issues](https://img.shields.io/github/issues/dimchat/core-py)](https://github.com/dimchat/core-py/issues)
15
+ [![Repo Size](https://img.shields.io/github/repo-size/dimchat/core-py)](https://github.com/dimchat/core-py/archive/refs/heads/main.zip)
16
+ [![Tags](https://img.shields.io/github/tag/dimchat/core-py)](https://github.com/dimchat/core-py/tags)
17
+ [![Version](https://img.shields.io/pypi/v/dimp)](https://pypi.org/project/dimp)
18
+
19
+ [![Watchers](https://img.shields.io/github/watchers/dimchat/core-py)](https://github.com/dimchat/core-py/watchers)
20
+ [![Forks](https://img.shields.io/github/forks/dimchat/core-py)](https://github.com/dimchat/core-py/forks)
21
+ [![Stars](https://img.shields.io/github/stars/dimchat/core-py)](https://github.com/dimchat/core-py/stargazers)
22
+ [![Followers](https://img.shields.io/github/followers/dimchat)](https://github.com/orgs/dimchat/followers)
23
+
24
+ ## Dependencies
25
+
26
+ | Name | Version | Description |
27
+ |------|---------|-------------|
28
+ | [Ming Ke Ming (名可名)](https://github.com/dimchat/mkm-py) | [![Version](https://img.shields.io/pypi/v/mkm)](https://pypi.org/project/mkm) | Decentralized User Identity Authentication |
29
+ | [Dao Ke Dao (道可道)](https://github.com/dimchat/dkd-py) | [![Version](https://img.shields.io/pypi/v/dkd)](https://pypi.org/project/dkd) | Universal Message Module |
30
+
31
+ ## Examples
32
+
33
+ ### extends Command
34
+
35
+ * _Handshake Command Protocol_
36
+ 0. (C-S) handshake start
37
+ 1. (S-C) handshake again with new session
38
+ 2. (C-S) handshake restart with new session
39
+ 3. (S-C) handshake success
40
+
41
+ ```python
42
+ from enum import IntEnum
43
+ from typing import Optional, Any, Dict
44
+
45
+ from dimp import Command, BaseCommand
46
+
47
+
48
+ class HandshakeState(IntEnum):
49
+ Start = 0 # C -> S, without session key(or session expired)
50
+ Again = 1 # S -> C, with new session key
51
+ Restart = 2 # C -> S, with new session key
52
+ Success = 3 # S -> C, handshake accepted
53
+
54
+
55
+ class HandshakeCommand(BaseCommand):
56
+ """
57
+ Handshake Command
58
+ ~~~~~~~~~~~~~~~~~
59
+
60
+ data format: {
61
+ type : 0x88,
62
+ sn : 123,
63
+
64
+ command : "handshake", // command name
65
+ title : "Hello world!", // "DIM?", "DIM!"
66
+ session : "{SESSION_ID}", // session key
67
+ }
68
+ """
69
+ HANDSHAKE = 'handshake'
70
+
71
+ def __init__(self, content: Dict[str, Any] = None, title: str = None, session: str = None):
72
+ if content is None:
73
+ # 1. new command with title & session key
74
+ assert title is not None, 'handshake command error: %s' % session
75
+ cmd = self.HANDSHAKE
76
+ super().__init__(cmd=cmd)
77
+ self['title'] = title
78
+ self['message'] = title # TODO: remove after all clients upgraded
79
+ if session is not None:
80
+ self['session'] = session
81
+ else:
82
+ # 2. command info from network
83
+ assert title is None and session is None, 'params error: %s, %s, %s' % (content, title, session)
84
+ super().__init__(content)
85
+
86
+ @property
87
+ def title(self) -> str:
88
+ # TODO: modify after all clients upgraded
89
+ text = self.get_str(key='title', default=None)
90
+ if text is None:
91
+ # compatible with v1.0
92
+ text = self.get_str(key='message', default='')
93
+ return text
94
+
95
+ @property
96
+ def session(self) -> Optional[str]:
97
+ return self.get_str(key='session', default=None)
98
+
99
+ @property
100
+ def state(self) -> HandshakeState:
101
+ return handshake_state(title=self.title, session=self.session)
102
+
103
+ #
104
+ # Factories
105
+ #
106
+
107
+ @classmethod
108
+ def offer(cls, session: str = None) -> Command:
109
+ """
110
+ Create client-station handshake offer
111
+
112
+ :param session: Old session key
113
+ :return: HandshakeCommand object
114
+ """
115
+ return HandshakeCommand(title='Hello world!', session=session)
116
+
117
+ @classmethod
118
+ def ask(cls, session: str) -> Command:
119
+ """
120
+ Create station-client handshake again with new session
121
+
122
+ :param session: New session key
123
+ :return: HandshakeCommand object
124
+ """
125
+ return HandshakeCommand(title='DIM?', session=session)
126
+
127
+ @classmethod
128
+ def accepted(cls, session: str = None) -> Command:
129
+ """
130
+ Create station-client handshake success notice
131
+
132
+ :return: HandshakeCommand object
133
+ """
134
+ return HandshakeCommand(title='DIM!', session=session)
135
+
136
+ start = offer # (1. C->S) first handshake, without session
137
+ again = ask # (2. S->C) ask client to handshake with new session key
138
+ restart = offer # (3. C->S) handshake with new session key
139
+ success = accepted # (4. S->C) notice the client that handshake accepted
140
+
141
+
142
+ def handshake_state(title: str, session: str = None) -> HandshakeState:
143
+ # Server -> Client
144
+ if title == 'DIM!': # or title == 'OK!':
145
+ return HandshakeState.Success
146
+ if title == 'DIM?':
147
+ return HandshakeState.Again
148
+ # Client -> Server: "Hello world!"
149
+ if session is None or len(session) == 0:
150
+ return HandshakeState.Start
151
+ else:
152
+ return HandshakeState.Restart
153
+ ```
154
+
155
+ ### extends Content
156
+
157
+ ```python
158
+ from abc import ABC, abstractmethod
159
+ from typing import Any, Dict
160
+
161
+ from dimp import ContentType
162
+ from dimp import Content
163
+ from dimp import BaseContent
164
+
165
+
166
+ class CustomizedContent(Content, ABC):
167
+ """
168
+ Application Customized message
169
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
170
+
171
+ data format: {
172
+ type : 0xCC,
173
+ sn : 123,
174
+
175
+ app : "{APP_ID}", // application (e.g.: "chat.dim.sechat")
176
+ mod : "{MODULE}", // module name (e.g.: "drift_bottle")
177
+ act : "{ACTION}", // action name (e.g.: "throw")
178
+ extra : info // action parameters
179
+ }
180
+ """
181
+
182
+ @property
183
+ @abstractmethod
184
+ def application(self) -> str:
185
+ """ App ID """
186
+ raise NotImplemented
187
+
188
+ @property
189
+ @abstractmethod
190
+ def module(self) -> str:
191
+ """ Module Name """
192
+ raise NotImplemented
193
+
194
+ @property
195
+ @abstractmethod
196
+ def action(self) -> str:
197
+ """ Action Name """
198
+ raise NotImplemented
199
+
200
+ #
201
+ # Factory method
202
+ #
203
+ @classmethod
204
+ def create(cls, app: str, mod: str, act: str):
205
+ return AppCustomizedContent(app=app, mod=mod, act=act)
206
+
207
+
208
+ class AppCustomizedContent(BaseContent, CustomizedContent):
209
+
210
+ def __init__(self, content: Dict[str, Any] = None,
211
+ msg_type: int = None,
212
+ app: str = None, mod: str = None, act: str = None):
213
+ if content is None:
214
+ # 1. new content with type, application, module & action
215
+ assert app is not None and mod is not None and act is not None, \
216
+ 'customized content error: %s, %s, %s, %s' % (msg_type, app, mod, act)
217
+ if msg_type is None:
218
+ msg_type = ContentType.CUSTOMIZED.value
219
+ super().__init__(None, msg_type)
220
+ self['app'] = app
221
+ self['mod'] = mod
222
+ self['act'] = act
223
+ else:
224
+ # 2. content info from network
225
+ assert msg_type is None and app is None and mod is None and act is None, \
226
+ 'params error: %s, %s, %s, %s, %s' % (content, msg_type, app, mod, act)
227
+ super().__init__(content)
228
+
229
+ @property # Override
230
+ def application(self) -> str:
231
+ return self.get_str(key='app', default='')
232
+
233
+ @property # Override
234
+ def module(self) -> str:
235
+ return self.get_str(key='mod', default='')
236
+
237
+ @property # Override
238
+ def action(self) -> str:
239
+ return self.get_str(key='act', default='')
240
+ ```
241
+
242
+ ### extends ID Address
243
+
244
+ * Examples in [dim plugins](https://pypi.org/project/dimplugins)
245
+
246
+ ----
247
+
248
+ Copyright © 2018 Albert Moky
249
+ [![Followers](https://img.shields.io/github/followers/moky)](https://github.com/moky?tab=followers)
250
+
251
+ Platform: UNKNOWN
252
+ Classifier: Programming Language :: Python :: 3
253
+ Classifier: License :: OSI Approved :: MIT License
254
+ Classifier: Operating System :: OS Independent
255
+ Description-Content-Type: text/markdown
dimp-2.2.1/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # Decentralized Instant Messaging Protocol (Python)
2
+
3
+ [![License](https://img.shields.io/github/license/dimchat/core-py)](https://github.com/dimchat/core-py/blob/master/LICENSE)
4
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/dimchat/core-py/pulls)
5
+ [![Platform](https://img.shields.io/badge/Platform-Python%203-brightgreen.svg)](https://github.com/dimchat/core-py/wiki)
6
+ [![Issues](https://img.shields.io/github/issues/dimchat/core-py)](https://github.com/dimchat/core-py/issues)
7
+ [![Repo Size](https://img.shields.io/github/repo-size/dimchat/core-py)](https://github.com/dimchat/core-py/archive/refs/heads/main.zip)
8
+ [![Tags](https://img.shields.io/github/tag/dimchat/core-py)](https://github.com/dimchat/core-py/tags)
9
+ [![Version](https://img.shields.io/pypi/v/dimp)](https://pypi.org/project/dimp)
10
+
11
+ [![Watchers](https://img.shields.io/github/watchers/dimchat/core-py)](https://github.com/dimchat/core-py/watchers)
12
+ [![Forks](https://img.shields.io/github/forks/dimchat/core-py)](https://github.com/dimchat/core-py/forks)
13
+ [![Stars](https://img.shields.io/github/stars/dimchat/core-py)](https://github.com/dimchat/core-py/stargazers)
14
+ [![Followers](https://img.shields.io/github/followers/dimchat)](https://github.com/orgs/dimchat/followers)
15
+
16
+ ## Dependencies
17
+
18
+ | Name | Version | Description |
19
+ |------|---------|-------------|
20
+ | [Ming Ke Ming (名可名)](https://github.com/dimchat/mkm-py) | [![Version](https://img.shields.io/pypi/v/mkm)](https://pypi.org/project/mkm) | Decentralized User Identity Authentication |
21
+ | [Dao Ke Dao (道可道)](https://github.com/dimchat/dkd-py) | [![Version](https://img.shields.io/pypi/v/dkd)](https://pypi.org/project/dkd) | Universal Message Module |
22
+
23
+ ## Examples
24
+
25
+ ### extends Command
26
+
27
+ * _Handshake Command Protocol_
28
+ 0. (C-S) handshake start
29
+ 1. (S-C) handshake again with new session
30
+ 2. (C-S) handshake restart with new session
31
+ 3. (S-C) handshake success
32
+
33
+ ```python
34
+ from enum import IntEnum
35
+ from typing import Optional, Any, Dict
36
+
37
+ from dimp import Command, BaseCommand
38
+
39
+
40
+ class HandshakeState(IntEnum):
41
+ Start = 0 # C -> S, without session key(or session expired)
42
+ Again = 1 # S -> C, with new session key
43
+ Restart = 2 # C -> S, with new session key
44
+ Success = 3 # S -> C, handshake accepted
45
+
46
+
47
+ class HandshakeCommand(BaseCommand):
48
+ """
49
+ Handshake Command
50
+ ~~~~~~~~~~~~~~~~~
51
+
52
+ data format: {
53
+ type : 0x88,
54
+ sn : 123,
55
+
56
+ command : "handshake", // command name
57
+ title : "Hello world!", // "DIM?", "DIM!"
58
+ session : "{SESSION_ID}", // session key
59
+ }
60
+ """
61
+ HANDSHAKE = 'handshake'
62
+
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
+ @property
79
+ def title(self) -> str:
80
+ # TODO: modify after all clients upgraded
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
86
+
87
+ @property
88
+ def session(self) -> Optional[str]:
89
+ return self.get_str(key='session', default=None)
90
+
91
+ @property
92
+ def state(self) -> HandshakeState:
93
+ return handshake_state(title=self.title, session=self.session)
94
+
95
+ #
96
+ # Factories
97
+ #
98
+
99
+ @classmethod
100
+ def offer(cls, session: str = None) -> Command:
101
+ """
102
+ Create client-station handshake offer
103
+
104
+ :param session: Old session key
105
+ :return: HandshakeCommand object
106
+ """
107
+ return HandshakeCommand(title='Hello world!', session=session)
108
+
109
+ @classmethod
110
+ def ask(cls, session: str) -> Command:
111
+ """
112
+ Create station-client handshake again with new session
113
+
114
+ :param session: New session key
115
+ :return: HandshakeCommand object
116
+ """
117
+ return HandshakeCommand(title='DIM?', session=session)
118
+
119
+ @classmethod
120
+ def accepted(cls, session: str = None) -> Command:
121
+ """
122
+ Create station-client handshake success notice
123
+
124
+ :return: HandshakeCommand object
125
+ """
126
+ return HandshakeCommand(title='DIM!', session=session)
127
+
128
+ start = offer # (1. C->S) first handshake, without session
129
+ again = ask # (2. S->C) ask client to handshake with new session key
130
+ restart = offer # (3. C->S) handshake with new session key
131
+ success = accepted # (4. S->C) notice the client that handshake accepted
132
+
133
+
134
+ def handshake_state(title: str, session: str = None) -> HandshakeState:
135
+ # Server -> Client
136
+ if title == 'DIM!': # or title == 'OK!':
137
+ return HandshakeState.Success
138
+ if title == 'DIM?':
139
+ return HandshakeState.Again
140
+ # Client -> Server: "Hello world!"
141
+ if session is None or len(session) == 0:
142
+ return HandshakeState.Start
143
+ else:
144
+ return HandshakeState.Restart
145
+ ```
146
+
147
+ ### extends Content
148
+
149
+ ```python
150
+ from abc import ABC, abstractmethod
151
+ from typing import Any, Dict
152
+
153
+ from dimp import ContentType
154
+ from dimp import Content
155
+ from dimp import BaseContent
156
+
157
+
158
+ class CustomizedContent(Content, ABC):
159
+ """
160
+ Application Customized message
161
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162
+
163
+ data format: {
164
+ type : 0xCC,
165
+ sn : 123,
166
+
167
+ app : "{APP_ID}", // application (e.g.: "chat.dim.sechat")
168
+ mod : "{MODULE}", // module name (e.g.: "drift_bottle")
169
+ act : "{ACTION}", // action name (e.g.: "throw")
170
+ extra : info // action parameters
171
+ }
172
+ """
173
+
174
+ @property
175
+ @abstractmethod
176
+ def application(self) -> str:
177
+ """ App ID """
178
+ raise NotImplemented
179
+
180
+ @property
181
+ @abstractmethod
182
+ def module(self) -> str:
183
+ """ Module Name """
184
+ raise NotImplemented
185
+
186
+ @property
187
+ @abstractmethod
188
+ def action(self) -> str:
189
+ """ Action Name """
190
+ raise NotImplemented
191
+
192
+ #
193
+ # Factory method
194
+ #
195
+ @classmethod
196
+ def create(cls, app: str, mod: str, act: str):
197
+ return AppCustomizedContent(app=app, mod=mod, act=act)
198
+
199
+
200
+ class AppCustomizedContent(BaseContent, CustomizedContent):
201
+
202
+ def __init__(self, content: Dict[str, Any] = None,
203
+ msg_type: int = None,
204
+ app: str = None, mod: str = None, act: str = None):
205
+ if content is None:
206
+ # 1. new content with type, application, module & action
207
+ assert app is not None and mod is not None and act is not None, \
208
+ 'customized content error: %s, %s, %s, %s' % (msg_type, app, mod, act)
209
+ if msg_type is None:
210
+ msg_type = ContentType.CUSTOMIZED.value
211
+ super().__init__(None, msg_type)
212
+ self['app'] = app
213
+ self['mod'] = mod
214
+ self['act'] = act
215
+ else:
216
+ # 2. content info from network
217
+ assert msg_type is None and app is None and mod is None and act is None, \
218
+ 'params error: %s, %s, %s, %s, %s' % (content, msg_type, app, mod, act)
219
+ super().__init__(content)
220
+
221
+ @property # Override
222
+ def application(self) -> str:
223
+ return self.get_str(key='app', default='')
224
+
225
+ @property # Override
226
+ def module(self) -> str:
227
+ return self.get_str(key='mod', default='')
228
+
229
+ @property # Override
230
+ def action(self) -> str:
231
+ return self.get_str(key='act', default='')
232
+ ```
233
+
234
+ ### extends ID Address
235
+
236
+ * Examples in [dim plugins](https://pypi.org/project/dimplugins)
237
+
238
+ ----
239
+
240
+ Copyright © 2018 Albert Moky
241
+ [![Followers](https://img.shields.io/github/followers/moky)](https://github.com/moky?tab=followers)