dimples 0.3.2__tar.gz → 0.3.4__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.
- {dimples-0.3.2 → dimples-0.3.4}/PKG-INFO +1 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples/__init__.py +2 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/__init__.py +2 -0
- dimples-0.3.4/dimples/client/archivist.py +243 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/checkpoint.py +10 -6
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_query.py +19 -0
- dimples-0.3.4/dimples/client/facebook.py +172 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/messenger.py +26 -155
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/packer.py +51 -5
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/processor.py +35 -11
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/__init__.py +2 -0
- dimples-0.3.4/dimples/common/archivist.py +214 -0
- dimples-0.3.4/dimples/common/facebook.py +239 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/messenger.py +8 -64
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/packer.py +71 -80
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/login.py +18 -11
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/gatekeeper.py +8 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/queue.py +19 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_login.py +1 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/octopus.py +6 -5
- {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/shared.py +6 -2
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/delegate.py +27 -58
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/emitter.py +31 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/helper.py +14 -2
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/manager.py +1 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/__init__.py +2 -0
- dimples-0.3.4/dimples/server/archivist.py +118 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/broadcast.py +25 -4
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/handshake.py +6 -2
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/login.py +4 -2
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/report.py +1 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/dispatcher.py +3 -0
- dimples-0.3.4/dimples/server/messenger.py +106 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/packer.py +2 -13
- dimples-0.3.4/dimples/server/processor.py +278 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/session.py +4 -5
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/trace.py +12 -7
- {dimples-0.3.2 → dimples-0.3.4}/dimples/station/handler.py +1 -17
- {dimples-0.3.2 → dimples-0.3.4}/dimples/station/shared.py +30 -2
- {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/__init__.py +34 -4
- {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/PKG-INFO +1 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/SOURCES.txt +3 -1
- {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/requires.txt +2 -2
- {dimples-0.3.2 → dimples-0.3.4}/setup.py +3 -3
- dimples-0.3.2/dimples/client/facebook.py +0 -60
- dimples-0.3.2/dimples/common/facebook.py +0 -243
- dimples-0.3.2/dimples/server/messenger.py +0 -215
- dimples-0.3.2/dimples/server/processor.py +0 -132
- dimples-0.3.2/dimples/utils/checker.py +0 -104
- {dimples-0.3.2 → dimples-0.3.4}/README.md +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/commands.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/creator.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/group.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_expel.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_invite.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_join.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_quit.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_reset.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_resign.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/handshake.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/session.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/state.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/transition.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/client/terminal.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/anonymous.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/ans.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/btc.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/compatible.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/entity.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/meta.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/network.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/account.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/message.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/session.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/ans.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/block.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/handshake.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/mute.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/report.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/register.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/common/session.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/gate.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/mars.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/mtp.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/protocol/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/protocol/mars.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/protocol/ws.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/seeker.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/session.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/ws.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/account.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/base.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/document.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/group.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/group_history.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/group_keys.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/login.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/meta.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/private.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/station.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/user.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/message.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/session.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_cipherkey.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_document.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_group.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_group_history.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_group_keys.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_message.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_meta.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_private.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_station.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_user.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/start.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/admin.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/builder.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/group/packer.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/register/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/register/base.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/register/ext.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/register/run.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/register/shared.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/ans.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/document.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/push.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/server/session_center.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/station/__init__.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/station/start.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/cache.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/config.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/dos.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/log.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/singleton.py +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/dependency_links.txt +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/entry_points.txt +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/top_level.txt +0 -0
- {dimples-0.3.2 → dimples-0.3.4}/setup.cfg +0 -0
|
@@ -221,6 +221,7 @@ __all__ = [
|
|
|
221
221
|
#
|
|
222
222
|
|
|
223
223
|
'AddressNameService', 'CipherKeyDelegate',
|
|
224
|
+
'Archivist',
|
|
224
225
|
'Facebook', 'Messenger',
|
|
225
226
|
'MessageProcessor', 'MessagePacker',
|
|
226
227
|
|
|
@@ -330,6 +331,7 @@ __all__ = [
|
|
|
330
331
|
#
|
|
331
332
|
'Anonymous', 'Register',
|
|
332
333
|
'AddressNameServer', 'ANSFactory',
|
|
334
|
+
'CommonArchivist',
|
|
333
335
|
'CommonFacebook', 'CommonMessenger',
|
|
334
336
|
'CommonMessagePacker', 'Transmitter',
|
|
335
337
|
'Session',
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
from .network import ClientSession
|
|
33
33
|
from .network import SessionState
|
|
34
34
|
|
|
35
|
+
from .archivist import ClientArchivist
|
|
35
36
|
from .facebook import ClientFacebook
|
|
36
37
|
from .messenger import ClientMessenger
|
|
37
38
|
from .packer import ClientMessagePacker
|
|
@@ -47,6 +48,7 @@ __all__ = [
|
|
|
47
48
|
#
|
|
48
49
|
'ClientSession', 'SessionState',
|
|
49
50
|
|
|
51
|
+
'ClientArchivist',
|
|
50
52
|
'ClientFacebook', 'ClientMessenger',
|
|
51
53
|
'ClientMessagePacker',
|
|
52
54
|
'ClientMessageProcessor', 'ClientContentProcessorCreator',
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# DIM-SDK : Decentralized Instant Messaging Software Development Kit
|
|
4
|
+
#
|
|
5
|
+
# Written in 2023 by Moky <albert.moky@gmail.com>
|
|
6
|
+
#
|
|
7
|
+
# ==============================================================================
|
|
8
|
+
# MIT License
|
|
9
|
+
#
|
|
10
|
+
# Copyright (c) 2023 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
|
|
32
|
+
from typing import Dict, List
|
|
33
|
+
|
|
34
|
+
from dimsdk import ID
|
|
35
|
+
from dimsdk import FrequencyChecker
|
|
36
|
+
from dimsdk import Document
|
|
37
|
+
from dimsdk import MetaCommand, DocumentCommand, GroupCommand, QueryCommand
|
|
38
|
+
from dimsdk import Station
|
|
39
|
+
|
|
40
|
+
from ..common import AccountDBI
|
|
41
|
+
from ..common import CommonArchivist
|
|
42
|
+
from ..common import CommonMessenger
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_facebook(archivist: CommonArchivist):
|
|
46
|
+
facebook = archivist.facebook
|
|
47
|
+
if facebook is not None:
|
|
48
|
+
from .facebook import ClientFacebook
|
|
49
|
+
assert isinstance(facebook, ClientFacebook), 'facebook error: %s' % facebook
|
|
50
|
+
return facebook
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_messenger(archivist: CommonArchivist):
|
|
54
|
+
messenger = archivist.messenger
|
|
55
|
+
if messenger is not None:
|
|
56
|
+
assert isinstance(messenger, CommonMessenger), 'messenger error: %s' % messenger
|
|
57
|
+
return messenger
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ClientArchivist(CommonArchivist, ABC):
|
|
61
|
+
|
|
62
|
+
# each respond will be expired after 10 minutes
|
|
63
|
+
RESPOND_EXPIRES = 600.0 # seconds
|
|
64
|
+
|
|
65
|
+
def __init__(self, database: AccountDBI):
|
|
66
|
+
super().__init__(database=database)
|
|
67
|
+
self.__document_responses = FrequencyChecker(expires=self.RESPOND_EXPIRES)
|
|
68
|
+
self.__last_active_members: Dict[ID, ID] = {} # group => member
|
|
69
|
+
|
|
70
|
+
# protected
|
|
71
|
+
def is_documents_respond_expired(self, identifier: ID, force: bool) -> bool:
|
|
72
|
+
return self.__document_responses.is_expired(key=identifier, force=force)
|
|
73
|
+
|
|
74
|
+
def set_last_active_member(self, member: ID, group: ID):
|
|
75
|
+
self.__last_active_members[group] = member
|
|
76
|
+
|
|
77
|
+
# Override
|
|
78
|
+
def query_meta(self, identifier: ID) -> bool:
|
|
79
|
+
if not self.is_meta_query_expired(identifier=identifier):
|
|
80
|
+
# query not expired yet
|
|
81
|
+
self.info(msg='meta query not expired yet: %s' % identifier)
|
|
82
|
+
return False
|
|
83
|
+
messenger = get_messenger(archivist=self)
|
|
84
|
+
if messenger is None:
|
|
85
|
+
self.warning(msg='messenger not ready yet')
|
|
86
|
+
return False
|
|
87
|
+
self.info(msg='querying meta for: %s' % identifier)
|
|
88
|
+
command = MetaCommand.query(identifier=identifier)
|
|
89
|
+
_, r_msg = messenger.send_content(sender=None, receiver=Station.ANY, content=command, priority=1)
|
|
90
|
+
return r_msg is not None
|
|
91
|
+
|
|
92
|
+
# Override
|
|
93
|
+
def query_documents(self, identifier: ID, documents: List[Document]) -> bool:
|
|
94
|
+
if not self.is_documents_query_expired(identifier=identifier):
|
|
95
|
+
# query not expired yet
|
|
96
|
+
self.info(msg='document query not expired yet: %s' % identifier)
|
|
97
|
+
return False
|
|
98
|
+
messenger = get_messenger(archivist=self)
|
|
99
|
+
if messenger is None:
|
|
100
|
+
self.warning(msg='messenger not ready yet')
|
|
101
|
+
return False
|
|
102
|
+
last_time = self.get_last_document_time(identifier=identifier, documents=documents)
|
|
103
|
+
self.info(msg='querying document for: %s, last time: %s' % (identifier, last_time))
|
|
104
|
+
command = DocumentCommand.query(identifier=identifier, last_time=last_time)
|
|
105
|
+
_, r_msg = messenger.send_content(sender=None, receiver=Station.ANY, content=command, priority=1)
|
|
106
|
+
return r_msg is not None
|
|
107
|
+
|
|
108
|
+
# Override
|
|
109
|
+
def query_members(self, group: ID, members: List[ID]) -> bool:
|
|
110
|
+
if not self.is_members_query_expired(group=group):
|
|
111
|
+
# query not expired yet
|
|
112
|
+
self.info('members query not expired yet: %s' % group)
|
|
113
|
+
return False
|
|
114
|
+
facebook = get_facebook(archivist=self)
|
|
115
|
+
messenger = get_messenger(archivist=self)
|
|
116
|
+
if facebook is None or messenger is None:
|
|
117
|
+
self.warning(msg='facebook messenger not ready yet')
|
|
118
|
+
return False
|
|
119
|
+
user = facebook.current_user
|
|
120
|
+
if user is None:
|
|
121
|
+
self.error(msg='failed to get current user')
|
|
122
|
+
return False
|
|
123
|
+
me = user.identifier
|
|
124
|
+
last_time = self.get_last_group_history_time(group=group)
|
|
125
|
+
self.info(msg='querying members for group: %s, last time: %s' % (group, last_time))
|
|
126
|
+
# build query command for group members
|
|
127
|
+
command = GroupCommand.query(group=group, last_time=last_time)
|
|
128
|
+
# 1. check group bots
|
|
129
|
+
ok = self.query_members_from_assistants(command=command, sender=me, group=group)
|
|
130
|
+
if ok:
|
|
131
|
+
return True
|
|
132
|
+
# 2. check administrators
|
|
133
|
+
ok = self.query_members_from_administrators(command=command, sender=me, group=group)
|
|
134
|
+
if ok:
|
|
135
|
+
return True
|
|
136
|
+
# 3. check group owner
|
|
137
|
+
ok = self.query_members_from_owner(command=command, sender=me, group=group)
|
|
138
|
+
if ok:
|
|
139
|
+
return True
|
|
140
|
+
# all failed, try last active member
|
|
141
|
+
last_member = self.__last_active_members.get(group)
|
|
142
|
+
if last_member is None:
|
|
143
|
+
r_msg = None
|
|
144
|
+
else:
|
|
145
|
+
self.info(msg='querying members from: %s, group: %s' % (last_member, group))
|
|
146
|
+
_, r_msg = messenger.send_content(sender=me, receiver=last_member, content=command, priority=1)
|
|
147
|
+
self.error(msg='group not ready: %s' % group)
|
|
148
|
+
return r_msg is not None
|
|
149
|
+
|
|
150
|
+
# protected
|
|
151
|
+
def query_members_from_assistants(self, command: QueryCommand, sender: ID, group: ID) -> bool:
|
|
152
|
+
facebook = get_facebook(archivist=self)
|
|
153
|
+
messenger = get_messenger(archivist=self)
|
|
154
|
+
if facebook is None or messenger is None:
|
|
155
|
+
self.warning(msg='facebook messenger not ready yet')
|
|
156
|
+
return False
|
|
157
|
+
bots = facebook.assistants(group)
|
|
158
|
+
if len(bots) == 0:
|
|
159
|
+
self.warning(msg='assistants not designated for group: %s' % group)
|
|
160
|
+
return False
|
|
161
|
+
success = 0
|
|
162
|
+
# querying members from bots
|
|
163
|
+
self.info(msg='querying members from bots: %s, group: %s' % (bots, group))
|
|
164
|
+
for receiver in bots:
|
|
165
|
+
if receiver == sender:
|
|
166
|
+
self.warning(msg='ignore cycled querying: %s, group: %s' % (receiver, group))
|
|
167
|
+
continue
|
|
168
|
+
_, r_msg = messenger.send_content(sender=sender, receiver=receiver, content=command, priority=1)
|
|
169
|
+
if r_msg is not None:
|
|
170
|
+
success += 1
|
|
171
|
+
if success == 0:
|
|
172
|
+
# failed
|
|
173
|
+
return False
|
|
174
|
+
last_member = self.__last_active_members.get(group)
|
|
175
|
+
if last_member is None or last_member in bots:
|
|
176
|
+
# last active member is a bot??
|
|
177
|
+
pass
|
|
178
|
+
else:
|
|
179
|
+
self.info(msg='querying members from: %s, group: %s' % (last_member, group))
|
|
180
|
+
messenger.send_content(sender=sender, receiver=last_member, content=command, priority=1)
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
# protected
|
|
184
|
+
def query_members_from_administrators(self, command: QueryCommand, sender: ID, group: ID) -> bool:
|
|
185
|
+
facebook = get_facebook(archivist=self)
|
|
186
|
+
messenger = get_messenger(archivist=self)
|
|
187
|
+
if facebook is None or messenger is None:
|
|
188
|
+
self.warning(msg='facebook messenger not ready yet')
|
|
189
|
+
return False
|
|
190
|
+
admins = facebook.administrators(group)
|
|
191
|
+
if len(admins) == 0:
|
|
192
|
+
self.warning(msg='administrators not found for group: %s' % group)
|
|
193
|
+
return False
|
|
194
|
+
success = 0
|
|
195
|
+
# querying members from admins
|
|
196
|
+
self.info(msg='querying members from admins: %s, group: %s' % (admins, group))
|
|
197
|
+
for receiver in admins:
|
|
198
|
+
if receiver == sender:
|
|
199
|
+
self.warning(msg='ignore cycled querying: %s, group: %s' % (receiver, group))
|
|
200
|
+
continue
|
|
201
|
+
_, r_msg = messenger.send_content(sender=sender, receiver=receiver, content=command, priority=1)
|
|
202
|
+
if r_msg is not None:
|
|
203
|
+
success += 1
|
|
204
|
+
if success == 0:
|
|
205
|
+
# failed
|
|
206
|
+
return False
|
|
207
|
+
last_member = self.__last_active_members.get(group)
|
|
208
|
+
if last_member is None or last_member in admins:
|
|
209
|
+
# last active member is an admin, already queried
|
|
210
|
+
pass
|
|
211
|
+
else:
|
|
212
|
+
self.info(msg='querying members from: %s, group: %s' % (last_member, group))
|
|
213
|
+
messenger.send_content(sender=sender, receiver=last_member, content=command, priority=1)
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
# protected
|
|
217
|
+
def query_members_from_owner(self, command: QueryCommand, sender: ID, group: ID) -> bool:
|
|
218
|
+
facebook = get_facebook(archivist=self)
|
|
219
|
+
messenger = get_messenger(archivist=self)
|
|
220
|
+
if facebook is None or messenger is None:
|
|
221
|
+
self.warning(msg='facebook messenger not ready yet')
|
|
222
|
+
return False
|
|
223
|
+
owner = facebook.owner(group)
|
|
224
|
+
if owner is None:
|
|
225
|
+
self.warning(msg='owner not found for group: %s' % group)
|
|
226
|
+
return False
|
|
227
|
+
elif owner == sender:
|
|
228
|
+
self.error(msg='you are the owner of group: %s' % group)
|
|
229
|
+
return False
|
|
230
|
+
# querying members from owner
|
|
231
|
+
self.info(msg='querying members from owner: %s, group: %s' % (owner, group))
|
|
232
|
+
_, r_msg = messenger.send_content(sender=sender, receiver=owner, content=command, priority=1)
|
|
233
|
+
if r_msg is None:
|
|
234
|
+
# failed
|
|
235
|
+
return False
|
|
236
|
+
last_member = self.__last_active_members.get(group)
|
|
237
|
+
if last_member is None or last_member == owner:
|
|
238
|
+
# last active member is the owner, already queried
|
|
239
|
+
pass
|
|
240
|
+
else:
|
|
241
|
+
self.info(msg='querying members from: %s, group: %s' % (last_member, group))
|
|
242
|
+
messenger.send_content(sender=sender, receiver=last_member, content=command, priority=1)
|
|
243
|
+
return True
|
|
@@ -40,7 +40,7 @@ class SigPool:
|
|
|
40
40
|
def __init__(self):
|
|
41
41
|
super().__init__()
|
|
42
42
|
self._next_time = 0
|
|
43
|
-
self.__caches: Dict[str, float] = {} #
|
|
43
|
+
self.__caches: Dict[str, float] = {} # signature:receiver => timestamp
|
|
44
44
|
|
|
45
45
|
def purge(self, now: DateTime):
|
|
46
46
|
""" remove expired traces """
|
|
@@ -52,22 +52,26 @@ class SigPool:
|
|
|
52
52
|
self._next_time = timestamp + 3600
|
|
53
53
|
expired = timestamp - self.EXPIRES
|
|
54
54
|
keys = set(self.__caches.keys())
|
|
55
|
-
for
|
|
56
|
-
msg_time = self.__caches.get(
|
|
55
|
+
for tag in keys:
|
|
56
|
+
msg_time = self.__caches.get(tag)
|
|
57
57
|
if msg_time is None or msg_time < expired:
|
|
58
|
-
self.__caches.pop(
|
|
58
|
+
self.__caches.pop(tag, None)
|
|
59
59
|
return True
|
|
60
60
|
|
|
61
61
|
def duplicated(self, msg: ReliableMessage) -> bool:
|
|
62
62
|
""" check whether duplicated """
|
|
63
63
|
sig = msg.get('signature')
|
|
64
64
|
assert sig is not None, 'message error: %s' % msg
|
|
65
|
-
|
|
65
|
+
if len(sig) > 16:
|
|
66
|
+
sig = sig[-16:]
|
|
67
|
+
add = msg.receiver.address
|
|
68
|
+
tag = '%s:%s' % (sig, add)
|
|
69
|
+
cached = self.__caches.get(tag)
|
|
66
70
|
if cached is None:
|
|
67
71
|
# cache not found, create a new one with message time
|
|
68
72
|
when = msg.time
|
|
69
73
|
timestamp = 0 if when is None else when.timestamp
|
|
70
|
-
self.__caches[
|
|
74
|
+
self.__caches[tag] = timestamp
|
|
71
75
|
return False
|
|
72
76
|
else:
|
|
73
77
|
return True
|
|
@@ -78,6 +78,25 @@ class QueryCommandProcessor(GroupCommandProcessor):
|
|
|
78
78
|
}
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
# check last group time
|
|
82
|
+
query_time = content.last_time
|
|
83
|
+
if query_time is not None:
|
|
84
|
+
# check last group history time
|
|
85
|
+
archivist = self.facebook.archivist
|
|
86
|
+
last_time = archivist.get_last_group_history_time(group=group)
|
|
87
|
+
if last_time is None:
|
|
88
|
+
self.error(msg='group history error: %s' % group)
|
|
89
|
+
elif not last_time.after(query_time):
|
|
90
|
+
# group history not updated
|
|
91
|
+
text = 'Group history not updated.'
|
|
92
|
+
return self._respond_receipt(text=text, content=content, envelope=r_msg.envelope, extra={
|
|
93
|
+
'template': 'Group history not updated: ${ID}, last time: ${time}',
|
|
94
|
+
'replacements': {
|
|
95
|
+
'ID': str(group),
|
|
96
|
+
'time': last_time.timestamp,
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
81
100
|
# 3. send newest group history commands
|
|
82
101
|
ok = self.send_group_histories(group=group, receiver=sender)
|
|
83
102
|
assert ok, 'failed to send history for group: %s => %s' % (group, sender)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ==============================================================================
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2023 Albert Moky
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
# ==============================================================================
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
Facebook for client
|
|
28
|
+
~~~~~~~~~~~~~~~~~~~
|
|
29
|
+
|
|
30
|
+
Barrack for cache entities
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from typing import Optional, List
|
|
34
|
+
|
|
35
|
+
from dimsdk import EntityType
|
|
36
|
+
from dimsdk import ID, Document, Bulletin
|
|
37
|
+
from dimsdk import BroadcastHelper
|
|
38
|
+
|
|
39
|
+
from ..common import CommonFacebook
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ClientFacebook(CommonFacebook):
|
|
43
|
+
|
|
44
|
+
# Override
|
|
45
|
+
def save_document(self, document: Document) -> bool:
|
|
46
|
+
ok = super().save_document(document=document)
|
|
47
|
+
if ok and isinstance(document, Bulletin):
|
|
48
|
+
# check administrators
|
|
49
|
+
array = document.get_property(key='administrators')
|
|
50
|
+
if array is not None:
|
|
51
|
+
group = document.identifier
|
|
52
|
+
assert group.is_group, 'group ID error: %s' % group
|
|
53
|
+
admins = ID.convert(array=array)
|
|
54
|
+
ok = self.save_administrators(administrators=admins, group=group)
|
|
55
|
+
return ok
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# GroupDataSource
|
|
59
|
+
#
|
|
60
|
+
|
|
61
|
+
# Override
|
|
62
|
+
def founder(self, identifier: ID) -> Optional[ID]:
|
|
63
|
+
# check broadcast group
|
|
64
|
+
if identifier.is_broadcast:
|
|
65
|
+
# founder of broadcast group
|
|
66
|
+
return BroadcastHelper.broadcast_founder(group=identifier)
|
|
67
|
+
# check bulletin document
|
|
68
|
+
doc = self.bulletin(identifier=identifier)
|
|
69
|
+
if doc is None:
|
|
70
|
+
# the owner(founder) should be set in the bulletin document of group
|
|
71
|
+
return None
|
|
72
|
+
db = self.archivist
|
|
73
|
+
# check local storage
|
|
74
|
+
user = db.founder(identifier=identifier)
|
|
75
|
+
if user is not None:
|
|
76
|
+
# got from local storage
|
|
77
|
+
return user
|
|
78
|
+
# get from bulletin document
|
|
79
|
+
user = doc.founder
|
|
80
|
+
if user is None:
|
|
81
|
+
self.error(msg='founder not designated for group: %s' % identifier)
|
|
82
|
+
return user
|
|
83
|
+
|
|
84
|
+
# Override
|
|
85
|
+
def owner(self, identifier: ID) -> Optional[ID]:
|
|
86
|
+
# check broadcast group
|
|
87
|
+
if identifier.is_broadcast:
|
|
88
|
+
# owner of broadcast group
|
|
89
|
+
return BroadcastHelper.broadcast_owner(group=identifier)
|
|
90
|
+
# check bulletin document
|
|
91
|
+
doc = self.bulletin(identifier=identifier)
|
|
92
|
+
if doc is None:
|
|
93
|
+
# the owner(founder) should be set in the bulletin document of group
|
|
94
|
+
return None
|
|
95
|
+
db = self.archivist
|
|
96
|
+
# check local storage
|
|
97
|
+
user = db.owner(identifier=identifier)
|
|
98
|
+
if user is not None:
|
|
99
|
+
# got from local storage
|
|
100
|
+
return user
|
|
101
|
+
# check group type
|
|
102
|
+
if identifier.type == EntityType.GROUP:
|
|
103
|
+
# Polylogue's owner is its founder
|
|
104
|
+
user = db.founder(identifier=identifier)
|
|
105
|
+
if user is None:
|
|
106
|
+
user = doc.founder
|
|
107
|
+
if user is None:
|
|
108
|
+
self.error(msg='owner not found for group: %s' % identifier)
|
|
109
|
+
return user
|
|
110
|
+
|
|
111
|
+
# Override
|
|
112
|
+
def members(self, identifier: ID) -> List[ID]:
|
|
113
|
+
owner = self.owner(identifier=identifier)
|
|
114
|
+
if owner is None:
|
|
115
|
+
self.error(msg='group empty: %s' % identifier)
|
|
116
|
+
return []
|
|
117
|
+
db = self.archivist
|
|
118
|
+
# check local storage
|
|
119
|
+
users = db.members(identifier=identifier)
|
|
120
|
+
db.check_members(group=identifier, members=users)
|
|
121
|
+
if len(users) == 0:
|
|
122
|
+
users = [owner]
|
|
123
|
+
else:
|
|
124
|
+
assert users[0] == owner, 'group owner must be the first member: %s' % identifier
|
|
125
|
+
return users
|
|
126
|
+
|
|
127
|
+
# Override
|
|
128
|
+
def assistants(self, identifier: ID) -> List[ID]:
|
|
129
|
+
# check bulletin document
|
|
130
|
+
doc = self.bulletin(identifier=identifier)
|
|
131
|
+
if doc is None:
|
|
132
|
+
# the assistants should be set in the bulletin document of group
|
|
133
|
+
return []
|
|
134
|
+
db = self.archivist
|
|
135
|
+
# check local storage
|
|
136
|
+
bots = db.assistants(identifier=identifier)
|
|
137
|
+
if len(bots) > 0:
|
|
138
|
+
# got from local storage
|
|
139
|
+
return bots
|
|
140
|
+
# get from bulletin document
|
|
141
|
+
bots = doc.assistants
|
|
142
|
+
return [] if bots is None else bots
|
|
143
|
+
|
|
144
|
+
#
|
|
145
|
+
# Organizational Structure
|
|
146
|
+
#
|
|
147
|
+
|
|
148
|
+
def administrators(self, group: ID) -> List[ID]:
|
|
149
|
+
# check bulletin document
|
|
150
|
+
doc = self.bulletin(identifier=group)
|
|
151
|
+
if doc is None:
|
|
152
|
+
# the administrators should be set in the bulletin document
|
|
153
|
+
return []
|
|
154
|
+
db = self.archivist
|
|
155
|
+
# the 'administrators' should be saved into local storage
|
|
156
|
+
# when the newest bulletin document received,
|
|
157
|
+
# so we must get them from the local storage only,
|
|
158
|
+
# not from the bulletin document.
|
|
159
|
+
return db.administrators(group=group)
|
|
160
|
+
|
|
161
|
+
# protected
|
|
162
|
+
def save_administrators(self, administrators: List[ID], group: ID) -> bool:
|
|
163
|
+
db = self.archivist
|
|
164
|
+
return db.save_administrators(administrators, group=group)
|
|
165
|
+
|
|
166
|
+
# protected
|
|
167
|
+
def save_members(self, members: List[ID], group: ID) -> bool:
|
|
168
|
+
db = self.archivist
|
|
169
|
+
return db.save_members(members, group=group)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# TODO: ANS?
|