def __load_records(self) -> Dict[str, ID]: path = self.__path() self.info('Loading ANS records from: %s' % path) dictionary = {} text = self.read_text(path=path) if text is not None: lines = text.splitlines() for record in lines: pair = record.split('\t') if len(pair) != 2: self.error('invalid record: %s' % record) continue k = pair[0] v = pair[1] dictionary[k] = ID.parse(identifier=v) # # Reserved names # dictionary['all'] = EVERYONE dictionary[EVERYONE.name] = EVERYONE dictionary[ANYONE.name] = ANYONE dictionary['owner'] = ANYONE dictionary['founder'] = ID.parse( identifier='moky@4DnqXWdTV8wuZgfqSCX9GjE2kNq7HJrUgQ' ) # 'Albert Moky' return dictionary
def __init__(self): super().__init__() # caches self.__ids = {} self.__private_keys = {} self.__metas = {} self.__profiles = {} self.__users = {} # load built-in users self.__load_user(identifier=ID.parse(self.HULK)) self.__load_user(identifier=ID.parse(self.MOKI))
def __init__(self, messenger: ClientMessenger): super().__init__() # delegate for send message self.messenger = messenger # group gid = ID.parse(identifier=group_naruto) self.__group = messenger.facebook.group(gid)
def upload_profile() -> Response: try: form = request.form.to_dict() identifier = form['ID'] meta = form['meta'] profile = form['profile'] identifier = ID.parse(identifier=identifier) # save meta if meta is None: meta = g_facebook.meta(identifier=identifier) if meta is None: raise LookupError('meta not found: %s' % identifier) else: meta = Meta.parse(meta=json.loads(meta)) if not g_facebook.save_meta(meta=meta, identifier=identifier): raise ValueError('meta not acceptable: %s' % identifier) # save profile if profile is None: raise ValueError('profile empty: %s' % identifier) else: profile = Document.parse(document=json.loads(profile)) if not g_facebook.save_document(document=profile): raise ValueError('profile not acceptable: %s' % identifier) # OK return user(identifier) except Exception as error: res = { 'code': 500, 'name': 'Internal Server Error', 'message': '%s' % error } xml = render_template('error.xml', result=res) return respond_xml(xml)
def scan_ids(self) -> List[ID]: ids = [] directory = os.path.join(self.root, 'public') # get all files in messages directory and sort by filename files = os.listdir(directory) for filename in files: path = os.path.join(directory, filename, 'meta.js') if not os.path.exists(path): self.warning('meta file not exists: %s' % path) continue address = ID.parse(identifier=filename) if address is None: self.error('ID/address error: %s' % filename) continue meta = self.meta(identifier=address) if meta is None: self.error('meta error: %s' % address) else: identifier = meta.generate_identifier(network=address.type) self.debug('loaded meta for %s from %s: %s' % (identifier, path, meta)) # the ID contains 'username' now if identifier != address: # switch cache key # self.__caches.pop(address) self.__caches[identifier] = meta ids.append(identifier) self.debug('Scanned %d ID(s) from %s' % (len(ids), directory)) return ids
def do_profile(self, name: str): if self.client is None: self.info('login first') return facebook = g_messenger.facebook user = facebook.current_user profile = None if name is None: identifier = user.identifier elif name.startswith('{') and name.endswith('}'): identifier = user.identifier profile = json_decode(data=utf8_encode(string=name)) else: identifier = ID.parse(identifier=name) if identifier is None: self.info('I don\'t understand.') return if profile: private_key = facebook.private_key_for_signature(identifier=identifier) assert private_key is not None, 'failed to get private key for client: %s' % self.client # create new profile and set all properties tai = Document.create(doc_type=Document.VISA, identifier=identifier) for key in profile: tai.set_property(key, profile.get(key)) tai.sign(private_key=private_key) cmd = DocumentCommand.response(identifier=identifier, document=tai) else: cmd = DocumentCommand.query(identifier=identifier) self.client.send_command(cmd=cmd)
def create_server(identifier: str, host: str, port: int = 9394) -> Station: """ Create Local Server """ identifier = ID.parse(identifier=identifier) server = Station(identifier=identifier, host=host, port=port) g_facebook.cache_user(user=server) Log.info('local station created: %s' % server) return server
def assistants(self, identifier: ID) -> Optional[List[ID]]: array = super().assistants(identifier=identifier) if array is not None and len(array) > 0: return array # get from ANS robot = ID.parse(identifier='assistant') if robot is not None: return [robot]
def do_login(self, name: str): sender = ID.parse(identifier=name) if sender is None: self.info('unknown user: %s' % name) else: self.info('login as %s' % sender) self.login(identifier=sender) facebook = g_messenger.facebook self.prompt = Console.prompt + facebook.name(identifier=sender) + '$ '
def __roaming(self, cmd: LoginCommand, sender: ID) -> Optional[ID]: # check time expires old = self.database.login_command(identifier=sender) if old is not None: if cmd.time < old.time: return None # get station ID assert cmd.station is not None, 'login command error: %s' % cmd sid = ID.parse(identifier=cmd.station.get('ID')) if sid == g_station.identifier: return None return sid
def load_station(identifier: Union[ID, str], facebook: CommonFacebook) -> Station: """ Load station info from 'etc' directory :param identifier - station ID :param facebook - social network data source :return station with info from 'dims/etc/{address}/*' """ identifier = ID.parse(identifier=identifier) # check meta meta = facebook.meta(identifier=identifier) if meta is None: # load from 'etc' directory meta = Meta.parse(meta=load_station_info(identifier=identifier, filename='meta.js')) if meta is None: raise LookupError('failed to get meta for station: %s' % identifier) elif not facebook.save_meta(meta=meta, identifier=identifier): raise ValueError('meta error: %s' % meta) # check private key private_key = facebook.private_key_for_signature(identifier=identifier) if private_key is None: # load from 'etc' directory private_key = PrivateKey.parse(key=load_station_info(identifier=identifier, filename='secret.js')) if private_key is None: pass elif not facebook.save_private_key(key=private_key, identifier=identifier): raise AssertionError('failed to save private key for ID: %s, %s' % (identifier, private_key)) # check profile profile = load_station_info(identifier=identifier, filename='profile.js') if profile is None: raise LookupError('failed to get profile for station: %s' % identifier) Log.info('station profile: %s' % profile) name = profile.get('name') host = profile.get('host') port = profile.get('port') # create station if private_key is None: # remote station station = Station(identifier=identifier, host=host, port=port) else: # create profile profile = Document.create(doc_type=Document.PROFILE, identifier=identifier) profile.set_property('name', name) profile.set_property('host', host) profile.set_property('port', port) profile.sign(private_key=private_key) if not facebook.save_document(document=profile): raise AssertionError('failed to save profile: %s' % profile) # local station station = Station(identifier=identifier, host=host, port=port) facebook.cache_user(user=station) Log.info('station loaded: %s' % station) return station
def update_keys(self, key_map: dict) -> bool: """ Update cipher key table into memory cache :param key_map: cipher keys(with direction) from local storage :return: False on nothing changed """ changed = False for _from in key_map: sender = ID.parse(identifier=_from) table = key_map.get(_from) assert isinstance( table, dict), 'sender table error: %s, %s' % (_from, table) for _to in table: receiver = ID.parse(identifier=_to) pw = table.get(_to) key = SymmetricKey.parse(key=pw) # TODO: check whether exists an old key changed = True # cache key with direction self.__cache_cipher_key(key, sender, receiver) return changed
def user(address: str) -> Response: try: if address.find('@') < 0: address = Address.parse(address=address) identifier = ID.parse(identifier=address) info = g_worker.user_info(identifier=identifier) if info is None: messages = [] else: identifier = ID.parse(identifier=info.get('ID')) messages = g_worker.messages(identifier=identifier, start=0, count=20) xml = render_template('user.xml', user=info, messages=messages) except Exception as error: res = { 'code': 500, 'name': 'Internal Server Error', 'message': '%s' % error } xml = render_template('error.xml', result=res) return respond_xml(xml)
def do_call(self, name: str): if self.client is None: self.info('login first') return receiver = ID.parse(identifier=name) if receiver is None: self.info('unknown user: %s' % name) else: facebook = g_messenger.facebook meta = facebook.meta(identifier=receiver) self.info('talking with %s now, meta=%s' % (receiver, meta)) # switch receiver self.receiver = receiver
def send(self, identifier: str) -> int: identifier = ID.parse(identifier=identifier) device = Device(identifier) tokens = device.tokens if tokens is None: print('Device token not found, failed to push message: %s' % self.__payload.alert) return 0 count = 0 for token in tokens: self.__client.send_notification(token_hex=token, notification=self.__payload) count += 1 print('Message has been sent to %d device(s)' % count) return count
def __roaming(self, cmd: LoginCommand, sender: ID) -> Optional[ID]: # check time expires old = self.database.login_command(identifier=sender) if old is not None: if cmd.time < old.time: return None station = self.messenger.get_context(key='station') assert station is not None, 'current station not in the context' # get station ID assert cmd.station is not None, 'login command error: %s' % cmd sid = ID.parse(identifier=cmd.station.get('ID')) if sid == station.identifier: return None return sid
def load_freshmen() -> List[ID]: freshmen = [] text = Storage.read_text(path=freshmen_file) if text is None: return freshmen array = text.splitlines() for item in array: identifier = ID.parse(identifier=item) if identifier is None: Log.error('ID error: %s' % item) elif identifier.type == NetworkType.MAIN: freshmen.append(identifier) else: # Log.error('Not a user ID: %s' % identifier) pass return freshmen
def users(self) -> list: array = [] anyone = ANYONE id_list = self.__recommended_users(address=anyone.address) for item in id_list: identifier = item.strip() if len(identifier) == 0: # skip empty lines continue identifier = ID.parse(identifier=identifier) info = self.user_info(identifier=identifier) if info is None: # user info not found continue array.append(info) return array
def __roaming(self, receiver: ID) -> Optional[ID]: login = self.database.login_command(identifier=receiver) if login is None: return None station = login.station if station is None: return None # check time expires now = time.time() login_time = login.time if login_time is None: self.error('%s login time not set: %s' % (receiver, login)) return None if (now - login_time) > (3600 * 24 * 7): t_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(login_time)) self.debug('%s login expired: [%s] %s' % (receiver, t_str, login)) return None sid = ID.parse(identifier=station.get('ID')) if sid == self.station: return None return sid
def load_user(identifier: str, facebook: CommonFacebook) -> User: identifier = ID.parse(identifier=identifier) # check meta try: meta = facebook.meta(identifier=identifier) except AssertionError: meta = None if meta is None: # load from 'etc' directory meta = Meta.parse(meta=load_robot_info(identifier=identifier, filename='meta.js')) if meta is None: raise LookupError('failed to get meta for robot: %s' % identifier) elif not facebook.save_meta(meta=meta, identifier=identifier): raise ValueError('meta error: %s' % meta) # check private key private_key = facebook.private_key_for_signature(identifier=identifier) if private_key is None: # load from 'etc' directory private_key = PrivateKey.parse(key=load_robot_info(identifier=identifier, filename='secret.js')) if private_key is None: raise AssertionError('private key not found for ID: %s' % identifier) elif not facebook.save_private_key(key=private_key, identifier=identifier): raise AssertionError('failed to save private key for ID: %s, %s' % (identifier, private_key)) # check profile profile = load_robot_info(identifier=identifier, filename='profile.js') if profile is None: raise LookupError('failed to get profile for robot: %s' % identifier) Log.info('robot profile: %s' % profile) name = profile.get('name') avatar = profile.get('avatar') # create profile profile = Document.create(doc_type=Document.VISA, identifier=identifier) profile.set_property('name', name) profile.set_property('avatar', avatar) profile.sign(private_key=private_key) if not facebook.save_document(document=profile): raise AssertionError('failed to save profile: %s' % profile) # create local user return facebook.user(identifier=identifier)
def __login_station(self, identifier: ID) -> Optional[Station]: login = self.database.login_command(identifier=identifier) if login is None: self.error('login info not found: %s' % identifier) return None station = login.station if station is None: self.error('login station not found: %s -> %s' % (identifier, login)) return None sid = station.get('ID') if sid is None: self.error('login station error: %s -> %s' % (identifier, login)) return None facebook = self.facebook sid = ID.parse(identifier=sid) assert sid.type == NetworkType.STATION, 'station ID error: %s' % station if sid == self.station: self.debug('login station is current station: %s -> %s' % (identifier, sid)) return None # anything else? return facebook.user(identifier=sid)
def profile(address: str) -> Response: path = request.path try: address = ID.parse(identifier=address) info = g_facebook.document(identifier=address) if info is None: res = { 'code': 404, 'name': 'Not Found', 'message': 'profile not found: %s' % address } else: res = info except Exception as error: res = { 'code': 500, 'name': 'Internal Server Error', 'message': '%s' % error } js = json.dumps(res) if path.endswith('.js'): # JS callback js = 'dim.js.respond(%s,{"path":"%s"});' % (js, path) return respond_js(js)
def __init__(self, identifier: str): self.__identifier = ID.parse(identifier=identifier)
def load_naruto(): gid = ID.parse(identifier=group_naruto) Log.info('naruto group: %s' % gid) meta = Meta.parse(meta=load_robot_info(gid, 'meta.js')) g_facebook.save_meta(identifier=gid, meta=meta)
def __process_group_message( self, msg: ReliableMessage) -> Optional[ReliableMessage]: """ Separate group message and forward them one by one if members not found, drop this message and query group info from sender; if 'keys' found in group message, update them to cache; remove non-exists member from 'keys split group message with members, forward them :param msg: :return: ReliableMessage as result """ s_msg = self.verify_message(msg=msg) if s_msg is None: # signature error? return None sender = msg.sender receiver = msg.receiver if not self.facebook.exists_member(member=sender, group=receiver): if not self.facebook.is_owner(member=sender, group=receiver): # not allow, kick it out cmd = GroupCommand.expel(group=receiver, member=sender) sender = self.facebook.current_user.identifier receiver = msg.sender env = Envelope.create(sender=sender, receiver=receiver) i_msg = InstantMessage.create(head=env, body=cmd) s_msg = self.encrypt_message(msg=i_msg) if s_msg is None: self.error('failed to encrypt message: %s' % i_msg) self.suspend_message(msg=i_msg) return None return self.sign_message(msg=s_msg) members = self.facebook.members(receiver) if members is None or len(members) == 0: # members not found for this group, # query it from sender res = GroupCommand.query(group=receiver) else: # check 'keys' keys = msg.get('keys') if keys is not None: # remove non-exists members from 'keys' expel_list = [] for item in keys: if item == 'digest': self.info('key digest: %s' % keys[item]) continue m = ID.parse(identifier=item) if m not in members: self.info('remove non-exists member: %s' % item) expel_list.append(m) if len(expel_list) > 0: # send 'expel' command to the sender cmd = GroupCommand.expel(group=receiver, members=expel_list) g_messenger.send_content(sender=None, receiver=sender, content=cmd) # update key map self.__key_cache.update_keys(keys=keys, sender=sender, group=receiver) # split and forward group message, # respond receipt with success or failed list res = self.__split_group_message(msg=msg, members=members) # pack response if res is not None: sender = self.facebook.current_user.identifier receiver = msg.sender env = Envelope.create(sender=sender, receiver=receiver) i_msg = InstantMessage.create(head=env, body=res) s_msg = self.encrypt_message(msg=i_msg) if s_msg is None: self.error('failed to encrypt message: %s' % i_msg) self.suspend_message(msg=i_msg) return None return self.sign_message(msg=s_msg)
from etc.cfg_bots import group_naruto from etc.cfg_bots import lingling_id, xiaoxiao_id, chatroom_id, tokentalkteam_id from etc.cfg_loader import load_robot_info, load_station g_released = True Log.info("local storage directory: %s" % base_dir) Storage.root = base_dir """ Current Station ~~~~~~~~~~~~~~~ """ station_id = ID.parse(identifier=station_id) station_host = '127.0.0.1' # station_host = '134.175.87.98' # dimchat-gz # station_host = '124.156.108.150' # dimchat-hk station_port = 9394 g_station = Server(identifier=station_id, host=station_host, port=station_port) g_facebook = ClientFacebook() g_facebook.cache_user(user=g_station) # Address Name Service g_ans = AddressNameServer() g_ans.save('station', g_station.identifier) g_ans.save('moki', ID.parse(identifier='moki@4WDfe3zZ4T7opFSi3iDAKiuTnUHjxmXekk'))
def channel(address: str) -> Response: path = request.path if path is None: ext = 'xml' elif path.endswith('.rss'): ext = 'rss' elif path.endswith('.js'): ext = 'js' elif path.endswith('.json'): ext = 'json' else: ext = 'xml' try: if address.find('@') < 0: address = Address.parse(address=address) identifier = ID.parse(identifier=address) info = g_worker.user_info(identifier=identifier) if info is None: res = { 'code': 404, 'name': 'Not Found', 'message': '%s not found' % address } xml = render_template('error.xml', result=res) else: start = request.args.get('start') count = request.args.get('count') if start is None: start = 0 else: start = int(start) if count is None: count = 20 else: count = int(count) identifier = ID.parse(identifier=info.get('ID')) messages = g_worker.messages(identifier=identifier, start=start, count=count) if ext == 'rss': xml = render_template('channel.rss', user=info, messages=messages) else: xml = render_template('channel.xml', user=info, messages=messages) except Exception as error: res = { 'code': 500, 'name': 'Internal Server Error', 'message': '%s' % error } xml = render_template('error.xml', result=res) if ext == 'js' or ext == 'json': info = xmltodict.parse(xml) if 'xml' in info: info = info['xml'] elif 'result' in info: info = info['result'] js = json.dumps(info) if ext == 'js': js = 'dwitter.js.respond(%s,{"path":"%s"});' % (js, path) return respond_js(js) else: return respond_xml(xml)
def identifier(self, identifier: str) -> Optional[ID]: try: return ID.parse(identifier=identifier) except ValueError: self.error('ID error: %s' % identifier)
def identifier(self) -> ID: return ID.parse(identifier=self.get('ID'))
identifier = ID.parse(identifier=identifier) server = Station(identifier=identifier, host=host, port=port) g_facebook.cache_user(user=server) Log.info('local station created: %s' % server) return server """ Loading info ~~~~~~~~~~~~ """ # load ANS reserved records Log.info('-------- Loading ANS reserved records') for key, value in ans_reserved_records.items(): _id = ID.parse(identifier=value) assert _id is not None, 'ANS record error: %s, %s' % (key, value) Log.info('Name: %s -> ID: %s' % (key, _id)) if key in ans_keywords: # remove reserved name temporary index = ans_keywords.index(key) ans_keywords.remove(key) g_ans.save(key, _id) ans_keywords.insert(index, key) else: # not reserved name, save it directly g_ans.save(key, _id) # scan accounts Log.info('-------- scanning accounts') g_database.scan_ids()