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