#!/usr/bin/env python3 import sys import json import signal import threading import hashlib import fbchat from fbchat.models import * # ---- MESSAGE TYPES ---- # ezbr -> external CONFIGURE = "configure" GET_USER = "get_user" SET_USER_INFO = "set_user_info" SET_ROOM_INFO = "set_room_info" JOIN = "join" INVITE = "invite" LEAVE = "leave" SEND = "send" CLOSE = "close" # external -> ezbr JOINED = "joined" LEFT = "left" USER_INFO_UPDATED = "user_info_updated" ROOM_INFO_UPDATED = "room_info_updated" EVENT = "event" CACHE_PUT = "cache_put" CACHE_GET = "cache_get" # reply messages # ezbr -> external: all must wait for a reply! # external -> ezbr: only CACHE_GET produces a reply REP_OK = "rep_ok" REP_ERROR = "rep_error" # Event types EVENT_JOIN = "join" EVENT_LEAVE = "leave" EVENT_MESSAGE = "message" EVENT_ACTION = "action" # ---- MESSENGER CLIENT CLASS THAT HANDLES EVENTS ---- def getUserId(user): if user.url is not None and not "?" in user.url: return user.url.split("/")[-1] else: return user.uid def mediaObjectOfURL(url): return { "filename": url.split("?")[0].split("/")[-1], "url": url, } class MessengerBridgeClient(fbchat.Client): def __init__(self, bridge, *args, **kwargs): self.bridge = bridge super(MessengerBridgeClient, self).__init__(*args, **kwargs) class InitialSyncThread(threading.Thread): def __init__(self, client, bridge, *args, **kwargs): super(InitialSyncThread, self).__init__(*args, **kwargs) self.client = client self.bridge = bridge def run(self): threads = self.client.fetchThreadList() sys.stderr.write("fb thread list: {}\n".format(threads)) for thread in threads: sys.stderr.write("fb thread: {}\n".format(thread)) if thread.type != ThreadType.GROUP: continue self.bridge.write({ "_type": JOINED, "room": thread.uid, }) room_info = { "name": thread.name, } if thread.photo is not None: room_info["picture"] = mediaObjectOfURL(thread.photo) self.bridge.write({ "_type": ROOM_INFO_UPDATED, "room": thread.uid, "data": room_info, }) members = self.client.fetchAllUsersFromThreads([thread]) for member in members: sys.stderr.write("fb thread member: {}\n".format(member)) self.bridge.write({ "_type": EVENT, "data": { "type": EVENT_JOIN, "author": getUserId(member), "room": thread.uid, } }) user_info = { "display_name": member.name, } if member.photo is not None: user_info["avatar"] = mediaObjectOfURL(member.photo) self.bridge.write({ "_type": USER_INFO_UPDATED, "user": getUserId(member), "data": user_info, }) # TODO: handle events # ---- MAIN LOOP THAT HANDLES REQUESTS FROM BRIDGE ---- class MessengerBridge: def __init__(self): pass def run(self): self.client = None self.keep_running = True while self.keep_running: line = sys.stdin.readline() sys.stderr.write("(python) reading {}\n".format(line.strip())) cmd = json.loads(line) try: rep = self.handle_cmd(cmd) if rep is None: rep = {} if "_type" not in rep: rep["_type"] = REP_OK except Exception as e: rep = { "_type": REP_ERROR, "error": "{}".format(e) } rep["_id"] = cmd["_id"] self.write(rep) def write(self, msg): msgstr = json.dumps(msg) sys.stderr.write("(python) writing {}\n".format(msgstr)) sys.stdout.write(msgstr + "\n") sys.stdout.flush() def handle_cmd(self, cmd): ty = cmd["_type"] if ty == CONFIGURE: cookies_file = "/tmp/cookies_" + hashlib.sha224(cmd["data"]["email"].encode("utf-8")).hexdigest() try: f = open(cookies_file, "r") cookies = json.load(f) f.close() sys.stderr.write("(python messenger) using previous cookies: {}\n".format(cookies)) except: cookies = None self.client = MessengerBridgeClient(self, cmd["data"]["email"], cmd["data"]["password"], session_cookies=cookies) if self.client.isLoggedIn(): cookies = self.client.getSession() try: f = open(cookies_file, "w") json.dump(cookies, f) f.close() except: pass InitialSyncThread(self.client, self).start() elif ty == CLOSE: self.client.logout() self.keep_running = False elif ty == GET_USER: return {"_type": REP_OK, "user": self.client.uid} else: return {"_type": REP_ERROR, "error": "Not implemented"} if __name__ == "__main__": bridge = MessengerBridge() bridge.run()