2020-02-29 17:30:43 +00:00
#!/usr/bin/env python3
import sys
import json
2020-02-29 19:47:44 +00:00
import signal
import threading
2020-03-01 12:08:32 +00:00
import queue
import pickle
2020-03-01 13:14:18 +00:00
import time
2020-03-01 14:19:09 +00:00
import traceback
from urllib . parse import unquote as UrlUnquote
2020-02-29 17:30:43 +00:00
2020-03-05 15:59:37 +00:00
import base64
import getpass
import zlib
2020-02-29 19:47:44 +00:00
2020-02-29 17:30:43 +00:00
import fbchat
2020-02-29 19:47:44 +00:00
from fbchat . models import *
2020-02-29 17:30:43 +00:00
# ---- 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 "
2020-03-09 16:41:53 +00:00
SEARCH = " search "
2020-02-29 17:30:43 +00:00
SEND = " send "
2020-10-04 19:20:22 +00:00
USER_COMMAND = " user_command "
2020-02-29 17:30:43 +00:00
CLOSE = " close "
# external -> ezbr
2020-03-09 17:02:13 +00:00
SAVE_CONFIG = " save_config "
2020-10-04 19:20:22 +00:00
SYSTEM_MESSAGE = " system_message "
2020-02-29 17:30:43 +00:00
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 "
2020-03-09 16:41:53 +00:00
REP_SEARCH_RESULTS = " rep_search_results "
2020-02-29 17:30:43 +00:00
REP_ERROR = " rep_error "
2020-02-29 19:47:44 +00:00
# Event types
EVENT_JOIN = " join "
EVENT_LEAVE = " leave "
EVENT_MESSAGE = " message "
EVENT_ACTION = " action "
2020-02-29 17:30:43 +00:00
2020-02-29 19:47:44 +00:00
def mediaObjectOfURL ( url ) :
return {
" filename " : url . split ( " ? " ) [ 0 ] . split ( " / " ) [ - 1 ] ,
" url " : url ,
}
2020-03-01 14:19:09 +00:00
def stripFbLinkPrefix ( url ) :
PREFIX = " https://l.facebook.com/l.php?u= "
if url [ : len ( PREFIX ) ] == PREFIX :
return UrlUnquote ( url [ len ( PREFIX ) : ] . split ( ' & ' ) [ 0 ] )
else :
return url
2020-02-29 19:47:44 +00:00
2020-03-01 13:14:18 +00:00
# ---- MESSENGER CLIENT CLASS THAT HANDLES EVENTS ----
class MessengerBridgeClient ( fbchat . Client ) :
2020-10-04 19:20:22 +00:00
def __init__ ( self , bridge , * args , * * kwargs ) :
self . bridge = bridge
2020-03-01 13:14:18 +00:00
super ( MessengerBridgeClient , self ) . __init__ ( * args , * * kwargs )
def setBridge ( self , bridge ) :
self . bridge = bridge
## Redirect all interesting events to Bridge
def onMessage ( self , * args , * * kwargs ) :
self . bridge . onMessage ( * args , * * kwargs )
def onPeopleAdded ( self , * args , * * kwargs ) :
self . bridge . onPeopleAdded ( * args , * * kwargs )
def onPersonRemoved ( self , * args , * * kwargs ) :
self . bridge . onPersonRemoved ( * args , * * kwargs )
def onTitleChange ( self , * args , * * kwargs ) :
self . bridge . onTitleChange ( * args , * * kwargs )
2020-10-04 19:20:22 +00:00
def on2FACode ( self , * args , * * kwargs ) :
return self . bridge . on2FACode ( * args , * * kwargs )
2020-02-29 17:30:43 +00:00
2020-03-01 13:14:18 +00:00
# ---- SEPARATE THREADS FOR INITIAL SYNC & CLIENT LISTEN ----
2020-02-29 17:30:43 +00:00
2020-10-04 19:20:22 +00:00
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 ( )
2020-03-04 21:52:46 +00:00
class SyncerThread ( threading . Thread ) :
2020-03-05 22:27:41 +00:00
def __init__ ( self , bridge , thread_queue , * args , * * kwargs ) :
2020-03-04 21:52:46 +00:00
super ( SyncerThread , self ) . __init__ ( * args , * * kwargs )
2020-02-29 19:47:44 +00:00
self . bridge = bridge
2020-03-04 21:52:46 +00:00
self . thread_queue = thread_queue
2020-02-29 19:47:44 +00:00
def run ( self ) :
2020-03-04 21:52:46 +00:00
while True :
thread = self . thread_queue . get ( block = True )
2020-03-01 19:38:00 +00:00
sys . stderr . write ( " (python) fb thread: {} \n " . format ( thread ) )
self . bridge . setup_joined_thread ( thread )
2020-03-01 12:08:32 +00:00
2020-03-01 13:14:18 +00:00
class ClientListenThread ( threading . Thread ) :
def __init__ ( self , client , * args , * * kwargs ) :
super ( ClientListenThread , self ) . __init__ ( * args , * * kwargs )
2020-02-29 19:47:44 +00:00
2020-03-01 13:14:18 +00:00
self . client = client
def run ( self ) :
2020-03-01 19:38:00 +00:00
sys . stderr . write ( " (python messenger) Start client.listen() \n " )
2020-03-01 13:14:18 +00:00
self . client . listen ( )
2020-02-29 17:30:43 +00:00
# ---- MAIN LOOP THAT HANDLES REQUESTS FROM BRIDGE ----
class MessengerBridge :
2020-02-29 19:47:44 +00:00
def __init__ ( self ) :
2020-03-01 14:48:58 +00:00
self . init_backlog_length = 100
2020-03-01 12:08:32 +00:00
2020-10-04 19:20:22 +00:00
self . config = None
self . login_in_progress = None
2020-03-05 22:27:41 +00:00
# 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)
# Generally speaking, the first is referred to as uid whereas the second is just id
# THESE MAPS SHOULD NOT BE USED DIRECTLY, instead functions getUserId, getUserIdFromUid and revUserId should be used
self . uid_map = { } # map from fb user uid to bridge id
self . rev_uid = { } # map fro bridge id to fb user uid
# caches the room we (the user of the bridge) have joined (map keys = room uid)
self . my_joined_rooms = { }
# 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 = { }
2020-10-04 19:20:22 +00:00
# queue for thread syncing
self . sync_thread_queue = queue . Queue ( 100 )
2020-03-01 12:08:32 +00:00
def getUserId ( self , user ) :
2020-03-01 13:30:51 +00:00
retval = None
2020-03-01 12:08:32 +00:00
if user . url is not None and not " ? " in user . url :
2020-03-01 14:19:09 +00:00
retval = user . url . split ( " / " ) [ - 1 ]
2020-03-01 12:08:32 +00:00
else :
2020-03-01 13:30:51 +00:00
retval = user . uid
2020-03-01 13:14:18 +00:00
2020-03-01 13:30:51 +00:00
if user . uid not in self . uid_map :
self . uid_map [ user . uid ] = retval
2020-03-01 14:19:09 +00:00
self . rev_uid [ retval ] = user . uid
2020-03-01 13:30:51 +00:00
user_info = {
" display_name " : user . name ,
}
if user . photo is not None :
user_info [ " avatar " ] = mediaObjectOfURL ( user . photo )
2020-03-01 13:48:42 +00:00
self . write ( {
2020-03-01 13:30:51 +00:00
" _type " : USER_INFO_UPDATED ,
2020-03-05 22:27:41 +00:00
" user " : retval ,
2020-03-01 13:30:51 +00:00
" data " : user_info ,
} )
return retval
2020-03-01 13:14:18 +00:00
def getUserIdFromUid ( self , uid ) :
if uid in self . uid_map :
return self . uid_map [ uid ]
else :
user = self . client . fetchUserInfo ( uid ) [ uid ]
return self . getUserId ( user )
2020-03-01 12:08:32 +00:00
def revUserId ( self , user_id ) :
2020-03-09 16:41:53 +00:00
if user_id not in self . rev_uid :
for user in self . client . searchForUsers ( user_id ) :
self . getUserId ( user )
if user_id not in self . rev_uid :
raise ValueError ( " User not found: {} " . format ( user_id ) )
return self . rev_uid [ user_id ]
2020-03-01 12:08:32 +00:00
def getUserShortName ( self , user ) :
if user . first_name != None :
return user . first_name
else :
return user . name
2020-02-29 19:47:44 +00:00
2020-02-29 17:30:43 +00:00
def run ( self ) :
self . client = None
self . keep_running = True
2020-03-01 12:08:32 +00:00
self . cache_gets = { }
self . num = 0
2020-03-05 22:27:41 +00:00
self . my_user_id = " "
2020-02-29 17:30:43 +00:00
while self . keep_running :
2020-03-01 13:14:18 +00:00
try :
line = sys . stdin . readline ( )
except KeyboardInterrupt :
2020-03-01 19:38:00 +00:00
sys . stderr . write ( " (python messenger) shutting down " )
2020-03-01 13:14:18 +00:00
self . close ( )
2020-03-05 22:27:41 +00:00
break
2020-03-01 13:14:18 +00:00
2020-02-29 17:30:43 +00:00
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 :
2020-03-01 19:38:00 +00:00
sys . stderr . write ( " (python) {} \n " . format ( traceback . format_exc ( ) ) )
2020-02-29 17:30:43 +00:00
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 ( )
2020-10-04 19:20:22 +00:00
def system_message ( self , msg ) :
self . write ( {
" _type " : SYSTEM_MESSAGE ,
" value " : msg ,
} )
2020-02-29 17:30:43 +00:00
def handle_cmd ( self , cmd ) :
ty = cmd [ " _type " ]
if ty == CONFIGURE :
2020-10-04 19:20:22 +00:00
if self . login_in_progress is None :
self . config = cmd [ " data " ]
self . login_in_progress = queue . Queue ( 1 )
LoginThread ( self ) . start ( )
2020-03-05 15:59:37 +00:00
else :
2020-10-04 19:20:22 +00:00
return { " _type " : REP_ERROR , " error " : " Already logging in (CONFIGURE sent twice) " }
2020-02-29 19:47:44 +00:00
2020-02-29 17:30:43 +00:00
elif ty == CLOSE :
2020-03-01 13:14:18 +00:00
self . close ( )
2020-02-29 17:30:43 +00:00
2020-02-29 19:47:44 +00:00
elif ty == GET_USER :
2020-03-04 22:04:01 +00:00
return { " _type " : REP_OK , " user " : self . my_user_id }
2020-02-29 19:47:44 +00:00
2020-03-01 19:38:00 +00:00
elif ty == JOIN :
2020-10-04 19:20:22 +00:00
if self . client is None :
pass
else :
self . ensure_i_joined ( cmd [ " room " ] )
2020-03-01 19:38:00 +00:00
elif ty == LEAVE :
thread_id = cmd [ " room " ]
self . client . removeUserFromGroup ( self . client . uid , thread_id )
if thread_id in self . my_joined_rooms :
del self . my_joined_rooms [ thread_id ]
elif ty == INVITE :
if cmd [ " room " ] != " " :
uid = self . revUserId ( cmd [ " user " ] )
self . client . addUsersToGroup ( [ uid ] , cmd [ " room " ] )
2020-03-01 12:08:32 +00:00
2020-03-09 16:41:53 +00:00
elif ty == SEARCH :
users = self . client . searchForUsers ( cmd [ " data " ] )
rep = [ ]
for user in users :
rep . append ( {
" id " : self . getUserId ( user ) ,
" display_name " : user . name ,
} )
return { " _type " : REP_SEARCH_RESULTS , " data " : rep }
2020-03-01 12:08:32 +00:00
elif ty == SEND :
event = cmd [ " data " ]
if event [ " type " ] in [ EVENT_MESSAGE , EVENT_ACTION ] :
2020-03-01 14:19:09 +00:00
attachments = [ ]
if " attachments " in event and isinstance ( event [ " attachments " ] , list ) :
for at in event [ " attachments " ] :
if " url " in at :
attachments . append ( at [ " url " ] )
else :
# TODO
sys . stdout . write ( " Unhandled: attachment without URL " )
2020-03-01 12:08:32 +00:00
msg = Message ( event [ " text " ] )
if event [ " type " ] == EVENT_ACTION :
msg . text = " * " + event [ " text " ]
if event [ " room " ] != " " :
2020-03-01 14:19:09 +00:00
if len ( attachments ) > 0 :
msg_id = self . client . sendRemoteFiles ( attachments , message = msg , thread_id = event [ " room " ] , thread_type = ThreadType . GROUP )
else :
msg_id = self . client . send ( msg , thread_id = event [ " room " ] , thread_type = ThreadType . GROUP )
2020-03-01 12:08:32 +00:00
elif event [ " recipient " ] != " " :
uid = self . revUserId ( event [ " recipient " ] )
2020-03-01 19:38:00 +00:00
sys . stderr . write ( " (python) Sending to {} \n " . format ( uid ) )
2020-03-01 14:19:09 +00:00
if len ( attachments ) > 0 :
msg_id = self . client . sendRemoteFiles ( attachments , message = msg , thread_id = uid , thread_type = ThreadType . USER )
else :
msg_id = self . client . send ( msg , thread_id = uid , thread_type = ThreadType . USER )
2020-03-01 12:08:32 +00:00
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 " ] )
2020-10-04 19:20:22 +00:00
elif ty == USER_COMMAND :
self . handleUserCommand ( cmd [ " value " ] )
2020-02-29 17:30:43 +00:00
else :
return { " _type " : REP_ERROR , " error " : " Not implemented " }
2020-03-01 13:14:18 +00:00
def close ( self ) :
self . keep_running = False
self . client . stopListening ( )
2020-03-01 12:08:32 +00:00
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 } )
2020-03-01 19:38:00 +00:00
try :
rep = q . get ( block = True , timeout = 30 )
except queue . Empty :
rep = " "
2020-03-01 12:08:32 +00:00
del self . cache_gets [ num ]
return rep
2020-03-01 13:14:18 +00:00
def cache_put ( self , key , value ) :
self . write ( { " _type " : CACHE_PUT , " key " : key , " value " : value } )
2020-03-01 12:27:29 +00:00
2020-10-04 19:20:22 +00:00
# ---- 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
2020-03-01 19:38:00 +00:00
# ---- Info sync ----
def ensure_i_joined ( self , thread_id ) :
if thread_id not in self . my_joined_rooms :
2020-03-01 21:12:43 +00:00
self . my_joined_rooms [ thread_id ] = True
2020-03-01 19:38:00 +00:00
thread = self . client . fetchThreadInfo ( thread_id ) [ thread_id ]
2020-03-04 21:52:46 +00:00
self . sync_thread_queue . put ( thread )
2020-03-01 19:38:00 +00:00
def setup_joined_thread ( self , thread ) :
2020-03-04 17:12:00 +00:00
sys . stderr . write ( " (python) setup_joined_thread {} " . format ( thread ) )
2020-03-01 19:38:00 +00:00
if thread . type == ThreadType . GROUP :
members = self . client . fetchAllUsersFromThreads ( [ thread ] )
self . write ( {
" _type " : JOINED ,
" room " : thread . uid ,
} )
self . send_room_info ( thread , members )
self . send_room_members ( thread , members )
self . backlog_room ( thread )
2020-03-01 14:48:58 +00:00
def send_room_info ( self , thread , members ) :
2020-03-01 21:12:43 +00:00
members . sort ( key = lambda m : m . uid )
2020-03-01 14:48:58 +00:00
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 . getUserShortName ( m ) for m in who [ : 3 ] ] + [ " ... " ] )
else :
room_info [ " name " ] = " , " . join ( [ self . 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 . write ( {
" _type " : ROOM_INFO_UPDATED ,
" room " : thread . uid ,
" data " : room_info ,
} )
def send_room_members ( self , thread , members ) :
for member in members :
2020-03-01 19:38:00 +00:00
sys . stderr . write ( " (python) fb thread member: {} \n " . format ( member ) )
2020-03-01 14:48:58 +00:00
self . ensureJoined ( self . getUserId ( member ) , thread . uid )
2020-03-01 19:38:00 +00:00
def backlog_room ( self , thread ) :
prev_last_seen = self . cache_get ( " last_seen_ %s " % thread . uid )
if prev_last_seen == " " :
prev_last_seen = None
messages = [ ]
found = False
while not found :
before = None
if len ( messages ) > 0 :
before = messages [ - 1 ] . timestamp
page = self . client . fetchThreadMessages ( thread . uid , before = before , limit = 20 )
for m in page :
if m . uid == prev_last_seen or len ( messages ) > self . init_backlog_length :
found = True
break
else :
messages . append ( m )
for m in reversed ( messages ) :
if m . text is None :
m . text = " "
m . text = " [ {} ] {} " . format (
time . strftime ( " % Y- % m- %d % H: % M % Z " , time . localtime ( float ( m . timestamp ) / 1000 ) ) . strip ( ) ,
m . text )
self . onMessage ( thread_id = thread . uid ,
thread_type = thread . type ,
message_object = m )
2020-03-01 13:30:51 +00:00
def ensureJoined ( self , userId , room ) :
key = " {} -- {} " . format ( userId , room )
2020-03-01 19:38:00 +00:00
if not key in self . others_joined_map :
2020-03-01 13:48:42 +00:00
self . write ( {
2020-03-01 13:30:51 +00:00
" _type " : EVENT ,
" data " : {
" type " : EVENT_JOIN ,
" author " : userId ,
" room " : room ,
}
} )
2020-03-01 19:38:00 +00:00
self . others_joined_map [ key ] = True
# ---- Event handlers ----
2020-03-01 13:30:51 +00:00
2020-03-01 13:14:18 +00:00
def onMessage ( self , thread_id , thread_type , message_object , * * kwargs ) :
2020-03-01 19:38:00 +00:00
self . ensure_i_joined ( thread_id )
2020-03-01 13:14:18 +00:00
if message_object . author == self . client . uid :
# Ignore our own messages
return
2020-03-01 13:48:42 +00:00
sys . stderr . write ( " (python messenger) Got message: {} \n " . format ( message_object ) )
2020-03-01 13:14:18 +00:00
author = self . getUserIdFromUid ( message_object . author )
event = {
2020-03-01 21:12:43 +00:00
" id " : message_object . uid ,
2020-03-01 13:14:18 +00:00
" type " : EVENT_MESSAGE ,
" author " : author ,
" text " : message_object . text ,
2020-03-01 13:48:42 +00:00
" attachments " : [ ]
2020-03-01 13:14:18 +00:00
}
2020-03-01 14:19:09 +00:00
if event [ " text " ] is None :
event [ " text " ] = " "
2020-03-01 13:48:42 +00:00
for at in message_object . attachments :
if isinstance ( at , ImageAttachment ) :
2020-10-04 17:19:58 +00:00
try :
full_url = self . client . fetchImageUrl ( at . uid )
except :
time . sleep ( 1 )
full_url = self . client . fetchImageUrl ( at . uid )
2020-03-01 13:48:42 +00:00
event [ " attachments " ] . append ( {
" filename " : full_url . split ( " ? " ) [ 0 ] . split ( " / " ) [ - 1 ] ,
" url " : full_url ,
" image_size " : {
" width " : at . width ,
" height " : at . height ,
} ,
} )
2020-03-01 14:19:09 +00:00
elif isinstance ( at , FileAttachment ) :
url = stripFbLinkPrefix ( at . url )
event [ " attachments " ] . append ( {
" filename " : at . name ,
" url " : url ,
} )
elif isinstance ( at , AudioAttachment ) :
url = stripFbLinkPrefix ( at . url )
2020-03-01 13:48:42 +00:00
event [ " attachments " ] . append ( {
2020-03-01 14:19:09 +00:00
" filename " : at . filename ,
" url " : url ,
2020-03-01 13:48:42 +00:00
} )
2020-10-04 17:19:58 +00:00
elif isinstance ( at , ShareAttachment ) :
event [ " text " ] + = " \n {} \n {} " . format ( at . description , at . url )
2020-03-01 13:48:42 +00:00
else :
event [ " text " ] + = " \n Unhandled attachment: {} " . format ( at )
2020-03-13 09:13:31 +00:00
if isinstance ( message_object . sticker , Sticker ) :
stk = message_object . sticker
event [ " attachments " ] . append ( {
" filename " : stk . label ,
" url " : stk . url ,
" image_size " : {
" width " : stk . width ,
" height " : stk . height ,
} ,
} )
2020-03-01 13:14:18 +00:00
if thread_type == ThreadType . GROUP :
event [ " room " ] = thread_id
2020-03-01 13:30:51 +00:00
self . ensureJoined ( author , thread_id )
2020-03-01 19:38:00 +00:00
if event [ " text " ] != " " or len ( event [ " attachments " ] ) > 0 :
self . write ( { " _type " : EVENT , " data " : event } )
2020-03-01 13:14:18 +00:00
self . cache_put ( " last_seen_ %s " % thread_id , message_object . uid )
2020-03-01 14:38:54 +00:00
def onPeopleAdded ( self , added_ids , thread_id , * args , * * kwargs ) :
for user_id in added_ids :
2020-03-01 19:38:00 +00:00
if user_id == self . client . uid :
self . ensure_i_joined ( thread_id )
else :
self . ensureJoined ( self . getUserIdFromUid ( user_id ) , thread_id )
2020-03-01 14:38:54 +00:00
def onPersonRemoved ( self , removed_id , thread_id , * args , * * kwargs ) :
2020-03-01 19:38:00 +00:00
if removed_id == self . client . uid :
self . write ( {
" _type " : LEFT ,
2020-03-01 14:38:54 +00:00
" room " : thread_id ,
2020-03-01 19:38:00 +00:00
} )
if thread_id in self . my_joined_rooms :
del self . my_joined_rooms [ thread_id ]
else :
userId = self . getUserIdFromUid ( removed_id ) ,
self . write ( {
" _type " : EVENT ,
" data " : {
" type " : EVENT_JOIN ,
" author " : userId ,
" room " : thread_id ,
}
} )
map_key = " {} -- {} " . format ( userId , thread_id )
if map_key in self . others_joined_map :
del self . others_joined_map [ map_key ]
2020-03-01 14:38:54 +00:00
def onTitleChange ( self , author_id , new_title , thread_id , thread_type , * args , * * kwargs ) :
2020-03-01 19:38:00 +00:00
self . ensure_i_joined ( thread_id )
2020-03-01 14:38:54 +00:00
if thread_type == ThreadType . GROUP :
2020-03-01 19:38:00 +00:00
self . write ( {
2020-03-01 14:38:54 +00:00
" _type " : ROOM_INFO_UPDATED ,
" room " : thread_id ,
" data " : { " name " : new_title } ,
} )
2020-03-01 12:27:29 +00:00
2020-10-04 19:20:22 +00:00
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. " )
2020-03-05 15:59:37 +00:00
# ---- CLI ----
def createClientPickle ( ) :
email = input ( " Email address of Facebook account: " )
password = getpass . getpass ( )
client = MessengerBridgeClient ( email , password , max_tries = 1 )
if not client . isLoggedIn ( ) :
print ( " Could not log in (why???) " )
2020-03-06 20:15:11 +00:00
print ( " Still creating pickle though, maybe it will work after login was authorized? " )
2020-03-05 15:59:37 +00:00
print ( " " )
data = pickle . dumps ( client )
data = zlib . compress ( data )
data = base64 . b64encode ( data ) . decode ( ' ascii ' )
print ( data )
2020-02-29 17:30:43 +00:00
if __name__ == " __main__ " :
2020-03-05 15:59:37 +00:00
if " create_client_pickle " in sys . argv :
createClientPickle ( )
else :
bridge = MessengerBridge ( )
bridge . run ( )
2020-02-29 17:30:43 +00:00