aboutsummaryrefslogblamecommitdiff
path: root/external/messenger.py
blob: 8403a036b1d825f7e52d49f764bd71d4a18a4cad (plain) (tree)
1
2
3
4
5
6
7
8
9



                      

                

              
 
             
                           




























                                                   





                         


                                                      












                                                         





                                                                    




















































                                                                         





                                                       


                       





















































                                                                                                                             

                                                            



                                     


                                                             







                                                                   
#!/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()