From 810e75a34dddd88279bea1cd2ea38816fe872d52 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Sun, 1 Mar 2020 13:08:32 +0100 Subject: Very primitive ability to send fb messages --- connector/connector.go | 2 +- connector/external/external.go | 7 +- external/messenger.py | 188 +++++++++++++++++++++++++++++------------ 3 files changed, 139 insertions(+), 58 deletions(-) diff --git a/connector/connector.go b/connector/connector.go index 210d4ad..48fc9e7 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -130,7 +130,7 @@ type Event struct { Room RoomID `json:"room"` // Message text or action text - Text string `json:"text` + Text string `json:"text"` // Attached files such as images Attachments []SMediaObject `json:"attachments"` diff --git a/connector/external/external.go b/connector/external/external.go index 26f3c40..741802c 100644 --- a/connector/external/external.go +++ b/connector/external/external.go @@ -162,7 +162,7 @@ func (ext *External) setupProc() error { } func (ext *External) restartLoop(generation int) { - for { + for i := 0; i < 2; i++ { if ext.proc == nil { break } @@ -178,6 +178,7 @@ func (ext *External) restartLoop(generation int) { break } } + log.Warnf("More than 3 attempts (%s); abandonning.", ext.command) } func (m *extMessageWithData) UnmarshalJSON(jj []byte) error { @@ -294,7 +295,7 @@ func (ext *External) cmd(msg extMessage, data interface{}) (*extMessageWithData, } else { return rep, nil } - case <-time.After(5 * time.Second): + case <-time.After(30 * time.Second): return nil, fmt.Errorf("(%s) timeout", msg.MsgType) } } @@ -379,7 +380,7 @@ func (ext *External) Join(room RoomID) error { func (ext *External) Invite(user UserID, room RoomID) error { _, err := ext.cmd(extMessage{ - MsgType: LEAVE, + MsgType: INVITE, User: user, Room: room, }, nil) diff --git a/external/messenger.py b/external/messenger.py index 8403a03..6ec6e9c 100755 --- a/external/messenger.py +++ b/external/messenger.py @@ -4,6 +4,8 @@ import sys import json import signal import threading +import queue +import pickle import hashlib @@ -47,11 +49,6 @@ 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 { @@ -60,11 +57,12 @@ def mediaObjectOfURL(url): } -class MessengerBridgeClient(fbchat.Client): - def __init__(self, bridge, *args, **kwargs): - self.bridge = bridge +# class MessengerBridgeClient(fbchat.Client): +# def __init__(self, bridge, *args, **kwargs): +# super(MessengerBridgeClient, self).__init__(*args, **kwargs) +# +# # TODO: handle events - super(MessengerBridgeClient, self).__init__(*args, **kwargs) class InitialSyncThread(threading.Thread): def __init__(self, client, bridge, *args, **kwargs): @@ -78,59 +76,107 @@ class InitialSyncThread(threading.Thread): 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 + if thread.type == ThreadType.GROUP: + members = self.client.fetchAllUsersFromThreads([thread]) + + self.bridge.write({ + "_type": JOINED, + "room": thread.uid, + }) + + self.send_room_info(thread, members) + self.send_room_members(thread, members) + elif thread.type == ThreadType.USER: + self.bridge.getUserId(thread) + + self.backlog_room(thread) + + + def send_room_info(self, thread, members): + room_info = {} + if thread.name is not None: + room_info["name"] = thread.name + else: + who = [m for m in members if m.uid != self.client.uid] + if len(who) > 3: + room_info["name"] = ", ".join([self.bridge.getUserShortName(m) for m in who[:3]] + ["..."]) + else: + room_info["name"] = ", ".join([self.bridge.getUserShortName(m) for m in who]) + + if thread.photo is not None: + room_info["picture"] = mediaObjectOfURL(thread.photo) + else: + for m in members: + if m.uid != self.client.uid and m.photo is not None: + room_info["picture"] = mediaObjectOfURL(m.photo) + break + + self.bridge.write({ + "_type": ROOM_INFO_UPDATED, + "room": thread.uid, + "data": room_info, + }) + + def send_room_members(self, thread, members): + for member in members: + sys.stderr.write("fb thread member: {}\n".format(member)) self.bridge.write({ - "_type": JOINED, - "room": thread.uid, + "_type": EVENT, + "data": { + "type": EVENT_JOIN, + "author": self.bridge.getUserId(member), + "room": thread.uid, + } }) - room_info = { - "name": thread.name, + user_info = { + "display_name": member.name, } - if thread.photo is not None: - room_info["picture"] = mediaObjectOfURL(thread.photo) + if member.photo is not None: + user_info["avatar"] = mediaObjectOfURL(member.photo) self.bridge.write({ - "_type": ROOM_INFO_UPDATED, - "room": thread.uid, - "data": room_info, + "_type": USER_INFO_UPDATED, + "user": self.bridge.getUserId(member), + "data": user_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, - } - }) + def backlog_room(self, thread): + pass # TODO - 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 + self.rev_uid = {} + + def getUserId(self, user): + if user.url is not None and not "?" in user.url: + user_id = user.url.split("/")[-1] + self.rev_uid[user_id] = user.uid + return user_id + else: + return user.uid + + def revUserId(self, user_id): + if user_id in self.rev_uid: + return self.rev_uid[user_id] + else: + return user_id + + + def getUserShortName(self, user): + if user.first_name != None: + return user.first_name + else: + return user.name def run(self): self.client = None self.keep_running = True + self.cache_gets = {} + self.num = 0 while self.keep_running: line = sys.stdin.readline() @@ -161,23 +207,24 @@ class MessengerBridge: def handle_cmd(self, cmd): ty = cmd["_type"] if ty == CONFIGURE: - cookies_file = "/tmp/cookies_" + hashlib.sha224(cmd["data"]["email"].encode("utf-8")).hexdigest() + client_file = "/tmp/fbclient_" + hashlib.sha224(cmd["data"]["email"].encode("utf-8")).hexdigest() try: - f = open(cookies_file, "r") - cookies = json.load(f) + f = open(client_file, "rb") + self.client = pickle.load(f) f.close() - sys.stderr.write("(python messenger) using previous cookies: {}\n".format(cookies)) + sys.stderr.write("(python messenger) using previous client: {}\n".format(client_file)) except: - cookies = None + self.client = None - self.client = MessengerBridgeClient(self, cmd["data"]["email"], cmd["data"]["password"], session_cookies=cookies) + if self.client is None: + email, password = cmd["data"]["email"], cmd["data"]["password"] + self.client = fbchat.Client(email=email, password=password, max_tries=1) if self.client.isLoggedIn(): - cookies = self.client.getSession() try: - f = open(cookies_file, "w") - json.dump(cookies, f) + f = open(client_file, "wb") + pickle.dump(self.client, f) f.close() except: pass @@ -185,15 +232,48 @@ class MessengerBridge: 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} + elif ty == INVITE and cmd["room"] == "": + return {"_type": REP_OK} + + elif ty == SEND: + event = cmd["data"] + if event["type"] in [EVENT_MESSAGE, EVENT_ACTION]: + # TODO: attachments + msg = Message(event["text"]) + if event["type"] == EVENT_ACTION: + msg.text = "* " + event["text"] + + if event["room"] != "": + msg_id = self.client.send(msg, thread_id=event["room"], thread_type=ThreadType.GROUP) + elif event["recipient"] != "": + uid = self.revUserId(event["recipient"]) + msg_id = self.client.send(msg, thread_id=uid, thread_type=ThreadType.USER) + else: + return {"_type": REP_ERROR, "error": "Invalid message"} + + return {"_type": REP_OK, "event_id": msg_id} + + elif ty == REP_OK and cmd["_id"] in self.cache_gets: + self.cache_gets[cmd["_id"]].put(cmd["value"]) + else: return {"_type": REP_ERROR, "error": "Not implemented"} + def cache_get(self, key): + self.num += 1 + num = self.num + q = queue.Queue(1) + self.cache_gets[num] = q + self.write({"_type": CACHE_GET, "_id": num, "key": key}) + rep = q.get(block=True, timeout=30) + del self.cache_gets[num] + return rep + if __name__ == "__main__": bridge = MessengerBridge() -- cgit v1.2.3