diff options
Diffstat (limited to 'external')
-rwxr-xr-x | external/messenger.py | 144 |
1 files changed, 103 insertions, 41 deletions
diff --git a/external/messenger.py b/external/messenger.py index 76ca90d..9ac7d26 100755 --- a/external/messenger.py +++ b/external/messenger.py @@ -29,10 +29,12 @@ INVITE = "invite" LEAVE = "leave" SEARCH = "search" SEND = "send" +USER_COMMAND = "user_command" CLOSE = "close" # external -> ezbr SAVE_CONFIG = "save_config" +SYSTEM_MESSAGE = "system_message" JOINED = "joined" LEFT = "left" USER_INFO_UPDATED = "user_info_updated" @@ -71,9 +73,9 @@ def stripFbLinkPrefix(url): # ---- MESSENGER CLIENT CLASS THAT HANDLES EVENTS ---- class MessengerBridgeClient(fbchat.Client): - def __init__(self, *args, **kwargs): + def __init__(self, bridge, *args, **kwargs): + self.bridge = bridge super(MessengerBridgeClient, self).__init__(*args, **kwargs) - self.bridge = None def setBridge(self, bridge): self.bridge = bridge @@ -87,9 +89,20 @@ class MessengerBridgeClient(fbchat.Client): self.bridge.onPersonRemoved(*args, **kwargs) def onTitleChange(self, *args, **kwargs): self.bridge.onTitleChange(*args, **kwargs) + def on2FACode(self, *args, **kwargs): + return self.bridge.on2FACode(*args, **kwargs) # ---- SEPARATE THREADS FOR INITIAL SYNC & CLIENT LISTEN ---- +class LoginThread(threading.Thread): + def __init__(self, bridge, *args, **kwargs): + super(LoginThread, self).__init__(*args, **kwargs) + + self.bridge = bridge + + def run(self): + self.bridge.processLogin() + class SyncerThread(threading.Thread): def __init__(self, bridge, thread_queue, *args, **kwargs): super(SyncerThread, self).__init__(*args, **kwargs) @@ -121,6 +134,9 @@ class MessengerBridge: def __init__(self): self.init_backlog_length = 100 + self.config = None + self.login_in_progress = None + # We cache maps between two kinds of identifiers: # - facebook uids of users # - identifiers for the bridge, which are the username when defined (otherwise equal to above) @@ -135,6 +151,9 @@ class MessengerBridge: # caches for the people that are in rooms so that we don't send JOINED every time (map keys = "<userId>--<threadId>") self.others_joined_map = {} + # queue for thread syncing + self.sync_thread_queue = queue.Queue(100) + def getUserId(self, user): retval = None if user.url is not None and not "?" in user.url: @@ -222,48 +241,21 @@ class MessengerBridge: sys.stdout.write(msgstr + "\n") sys.stdout.flush() + def system_message(self, msg): + self.write({ + "_type": SYSTEM_MESSAGE, + "value": msg, + }) + def handle_cmd(self, cmd): ty = cmd["_type"] if ty == CONFIGURE: - self.init_backlog_length = int(cmd["data"]["initial_backlog"]) - - has_pickle = "client_pickle" in cmd["data"] and len(cmd["data"]["client_pickle"]) > 0 - if has_pickle: - data = base64.b64decode(cmd["data"]["client_pickle"]) - data = zlib.decompress(data) - self.client = pickle.loads(data) + if self.login_in_progress is None: + self.config = cmd["data"] + self.login_in_progress = queue.Queue(1) + LoginThread(self).start() else: - email, password = cmd["data"]["email"], cmd["data"]["password"] - self.client = MessengerBridgeClient(email=email, password=password, max_tries=1) - ## TODO: save client in new client_pickle config value - - if not self.client.isLoggedIn(): - return {"_type": REP_ERROR, "error": "Unable to login (invalid pickle?)"} - - if not has_pickle: - new_config = cmd["data"] - data = pickle.dumps(self.client) - data = zlib.compress(data) - new_config["client_pickle"] = base64.b64encode(data).decode('ascii') - self.write({"_type": SAVE_CONFIG, "data": new_config}) - - self.client.setBridge(self) - - self.my_user_id = self.getUserIdFromUid(self.client.uid) - - threads = self.client.fetchThreadList(limit=10) - # ensure we have a correct mapping for bridged user IDs to fb uids - # (this should be fast) - for thread in threads: - if thread.type == ThreadType.USER: - self.getUserId(thread) - - self.sync_thread_queue = queue.Queue(100) - SyncerThread(self, self.sync_thread_queue).start() - for thread in reversed(threads): - self.sync_thread_queue.put(thread) - - ClientListenThread(self.client).start() + return {"_type": REP_ERROR, "error": "Already logging in (CONFIGURE sent twice)"} elif ty == CLOSE: self.close() @@ -272,7 +264,10 @@ class MessengerBridge: return {"_type": REP_OK, "user": self.my_user_id} elif ty == JOIN: - self.ensure_i_joined(cmd["room"]) + if self.client is None: + pass + else: + self.ensure_i_joined(cmd["room"]) elif ty == LEAVE: thread_id = cmd["room"] @@ -331,6 +326,9 @@ class MessengerBridge: elif ty == REP_OK and cmd["_id"] in self.cache_gets: self.cache_gets[cmd["_id"]].put(cmd["value"]) + elif ty == USER_COMMAND: + self.handleUserCommand(cmd["value"]) + else: return {"_type": REP_ERROR, "error": "Not implemented"} @@ -354,6 +352,51 @@ class MessengerBridge: def cache_put(self, key, value): self.write({"_type": CACHE_PUT, "key": key, "value": value}) + # ---- Process login (called from separate thread) ---- + + def processLogin(self): + self.init_backlog_length = int(self.config["initial_backlog"]) + + has_pickle = "client_pickle" in self.config and len(self.config["client_pickle"]) > 0 + if has_pickle: + data = base64.b64decode(self.config["client_pickle"]) + data = zlib.decompress(data) + self.client = pickle.loads(data) + else: + email, password = self.config["email"], self.config["password"] + self.client = MessengerBridgeClient(bridge=self, email=email, password=password, max_tries=1) + + if not self.client.isLoggedIn(): + self.system_message("Unable to login (invalid pickle? dunno)") + else: + self.system_message("Login complete, will now sync threads.") + + if not has_pickle: + self.client.setBridge(None) + data = pickle.dumps(self.client) + data = zlib.compress(data) + self.config["client_pickle"] = base64.b64encode(data).decode('ascii') + self.write({"_type": SAVE_CONFIG, "data": self.config}) + + self.client.setBridge(self) + + self.my_user_id = self.getUserIdFromUid(self.client.uid) + + threads = self.client.fetchThreadList(limit=10) + # ensure we have a correct mapping for bridged user IDs to fb uids + # (this should be fast) + for thread in threads: + if thread.type == ThreadType.USER: + self.getUserId(thread) + + SyncerThread(self, self.sync_thread_queue).start() + for thread in reversed(threads): + self.sync_thread_queue.put(thread) + + ClientListenThread(self.client).start() + + self.login_in_progress = None + # ---- Info sync ---- def ensure_i_joined(self, thread_id): @@ -566,6 +609,25 @@ class MessengerBridge: "data": {"name": new_title}, }) + def on2FACode(self, *args, **kwargs): + if self.login_in_progress is None: + self.system_message("Facebook messenger requests 2 factor authentication, but we have a bug so that won't work.") + return None + else: + self.system_message("Facebook messenger requests 2 factor authentication. Enter it by saying: cmd messenger 2fa <your code> (replace messenger by your account name if you have several messenger accounts)") + uc = self.login_in_progress.get(block=True) + return uc["2fa_code"] + + def handleUserCommand(self, cmd): + cmd = cmd.split(' ') + if cmd[0] == "2fa": + if self.login_in_progress is not None: + self.login_in_progress.put({"2fa_code": cmd[1]}) + else: + self.system_message("2FA code not required at this point.") + else: + self.system_message("Invalid user command.") + # ---- CLI ---- def createClientPickle(): |