aboutsummaryrefslogtreecommitdiff
path: root/external/messenger.py
diff options
context:
space:
mode:
Diffstat (limited to 'external/messenger.py')
-rwxr-xr-xexternal/messenger.py144
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():