Merge branch 'master' into master

This commit is contained in:
Dainius
2017-05-08 16:54:32 +03:00
committed by GitHub
7 changed files with 254 additions and 71 deletions

View File

@@ -15,7 +15,7 @@ from .client import *
__copyright__ = 'Copyright 2015 by Taehoon Kim'
__version__ = '0.8.2'
__version__ = '0.9.0'
__license__ = 'BSD'
__author__ = 'Taehoon Kim; Moreels Pieter-Jan'
__email__ = 'carpedm20@gmail.com'

View File

@@ -57,6 +57,7 @@ facebookEncoding = 'UTF-8'
# Log settings
log = logging.getLogger("client")
log.setLevel(logging.DEBUG)
class Client(object):
@@ -66,39 +67,27 @@ class Client(object):
documentation for the API.
"""
def __init__(self, email, password, debug=True, info_log=True, user_agent=None, max_retries=5, do_login=True):
def __init__(self, email, password, debug=True, info_log=True, user_agent=None, max_retries=5, session_cookies=None):
"""A client for the Facebook Chat (Messenger).
:param email: Facebook `email` or `id` or `phone number`
:param password: Facebook account password
import fbchat
chat = fbchat.Client(email, password)
:param debug: Configures the logger to `debug` logging_level
:param info_log: Configures the logger to `info` logging_level
:param user_agent: Custom user agent to use when sending requests. If `None`, user agent will be chosen from a premade list (see utils.py)
:param max_retries: Maximum number of times to retry login
:param session_cookies: Cookie dict from a previous session (Will default to login if these are invalid)
"""
self.is_def_recipient_set = False
self.debug = debug
self.sticky, self.pool = (None, None)
self._session = requests.session()
self.req_counter = 1
self.seq = "0"
self.payloadDefault={}
self.payloadDefault = {}
self.client = 'mercury'
self.listening = False
self.GENDERS = {
0: 'unknown',
1: 'female_singular',
2: 'male_singular',
3: 'female_singular_guess',
4: 'male_singular_guess',
5: 'mixed',
6: 'neuter_singular',
7: 'unknown_singular',
8: 'female_plural',
9: 'male_plural',
10: 'neuter_plural',
11: 'unknown_plural',
}
self.threads = []
# Setup event hooks
self.onLoggingIn = EventHook()
@@ -156,7 +145,7 @@ class Client(object):
'Connection' : 'keep-alive',
}
# Configure the logger differently based on the 'debug' parameter
# Configure the logger differently based on the 'debug' and 'info_log' parameters
if debug:
logging_level = logging.DEBUG
elif info_log:
@@ -168,13 +157,11 @@ class Client(object):
handler = logging.StreamHandler()
handler.setLevel(logging_level)
log.addHandler(handler)
log.setLevel(logging.DEBUG)
if do_login:
# If session cookies aren't set, not properly loaded or gives us an invalid session, then do the login
if not session_cookies or not self.setSession(session_cookies) or not self.isLoggedIn():
self.login(email, password, max_retries)
self.threads = []
def _console(self, msg):
"""Assumes an INFO level and log it.
@@ -206,11 +193,11 @@ class Client(object):
return payload
def _get(self, url, query=None, timeout=30):
payload=self._generatePayload(query)
payload = self._generatePayload(query)
return self._session.get(url, headers=self._header, params=payload, timeout=timeout)
def _post(self, url, query=None, timeout=30):
payload=self._generatePayload(query)
payload = self._generatePayload(query)
return self._session.post(url, headers=self._header, data=payload, timeout=timeout)
def _cleanGet(self, url, query=None, timeout=30):
@@ -343,50 +330,46 @@ class Client(object):
r = self._cleanPost(CheckpointURL, data)
return r
def saveSession(self, sessionfile):
"""Dumps the session cookies to (sessionfile).
WILL OVERWRITE ANY EXISTING FILE
def isLoggedIn(self):
# Send a request to the login url, to see if we're directed to the home page.
r = self._cleanGet(LoginURL)
return 'home' in r.url:
:param sessionfile: location of saved session file
def getSession(self):
"""Returns the session cookies"""
return self._session.cookies.get_dict()
def setSession(self, session_cookies):
"""Loads session cookies
:param session_cookies: dictionary containing session cookies
Return false if session_cookies does not contain proper cookies
"""
log.info('Saving session')
with open(sessionfile, 'w') as f:
# Grab cookies from current session, and save them as JSON
f.write(json.dumps(self._session.cookies.get_dict(), ensure_ascii=False))
# Quick check to see if session_cookies is formatted properly
if not session_cookies or 'c_user' not in session_cookies:
return False
def loadSession(self, sessionfile):
"""Loads session cookies from (sessionfile)
:param sessionfile: location of saved session file
"""
log.info('Loading session')
with open(sessionfile, 'r') as f:
try:
j = json.load(f)
if not j or 'c_user' not in j:
return False
# Load cookies into current session
self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, j)
self._postLogin()
return True
except Exception as e:
raise Exception('Invalid json in {}, or bad merging of cookies'.format(sessionfile))
# Load cookies into current session
self._session.cookies = requests.cookies.merge_cookies(self._session.cookies, session_cookies)
self._postLogin()
return True
def login(self, email, password, max_retries=5):
self.onLoggingIn()
self.onLoggingIn(email)
if not (email and password):
raise Exception("Email and password not set.")
self.email = email
self.password = password
for i in range(1, max_retries+1):
if not self._login():
log.warning("Attempt #{} failed{}".format(i,{True:', retrying'}.get(i<5,'')))
log.warning("Attempt #{} failed{}".format(i, {True: ', retrying'}.get(i < 5, '')))
time.sleep(1)
continue
else:
self.onLoggedIn()
self.onLoggedIn(email)
break
else:
raise Exception("Login failed. Check email/password.")
@@ -474,6 +457,8 @@ class Client(object):
:param like: size of the like sticker you want to send
:param image_id: id for the image to send, gotten from the UploadURL
:param add_user_ids: a list of user ids to add to a chat
returns a list of message ids of the sent message(s)
"""
if self.is_def_recipient_set:
@@ -541,11 +526,10 @@ class Client(object):
data["sticker_id"] = like.value
r = self._post(SendURL, data)
if r.ok:
log.info('Message sent.')
else:
log.info('Message not sent.')
if not r.ok:
log.warning('Error when sending message: Got {} response'.format(r.status_code))
return False
if isinstance(r._content, str) is False:
r._content = r._content.decode(facebookEncoding)
@@ -554,10 +538,19 @@ class Client(object):
# 'errorDescription' is in the users own language!
log.warning('Error #{} when sending message: {}'.format(j['error'], j['errorDescription']))
return False
message_ids = []
try:
message_ids += [action['message_id'] for action in j['payload']['actions'] if 'message_id' in action]
message_ids[0] # Try accessing element
except (KeyError, IndexError) as e:
log.warning('Error when sending message: No message ids could be found')
return False
log.info('Message sent.')
log.debug("Sending {}".format(r))
log.debug("With data {}".format(data))
return True
return message_ids
def sendRemoteImage(self, recipient_id=None, message=None, is_user=True, image=''):
"""Send an image from a URL
@@ -616,7 +609,7 @@ class Client(object):
# `start` doesn't matter, always returns from the last
# data['messages[{}][{}][offset]'.format(key, userID)] = start
data = {'messages[{}][{}][offset]'.format(key, userID): 0,
'messages[{}][{}][limit]'.format(key, userID): last_n,
'messages[{}][{}][limit]'.format(key, userID): last_n - 1,
'messages[{}][{}][timestamp]'.format(key, userID): now()}
r = self._post(MessagesURL, query=data)
@@ -958,7 +951,7 @@ class Client(object):
if type(_fbid) == int:
return _fbid
if type(_fbid) == str and 'fbid:' in _fbid:
if type(_fbid) in [str, unicode] and 'fbid:' in _fbid:
return int(_fbid[5:])
user_ids = [fbidStrip(uid) for uid in user_ids]

View File

@@ -11,12 +11,29 @@ USER_AGENTS = [
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6"
]
GENDERS = {
0: 'unknown',
1: 'female_singular',
2: 'male_singular',
3: 'female_singular_guess',
4: 'male_singular_guess',
5: 'mixed',
6: 'neuter_singular',
7: 'unknown_singular',
8: 'female_plural',
9: 'male_plural',
10: 'neuter_plural',
11: 'unknown_plural',
}
def now():
return int(time()*1000)
def strip_to_json(text):
return text[text.index('{'):]
def get_json(text):
return json.loads(re.sub(r"^[^{]*", '', text, 1))
return json.loads(strip_to_json(text))
def digit_to_char(digit):
if digit < 10: