fix inconsistencies, remove some deprecated and unused things

This commit is contained in:
Dainius
2017-05-01 15:57:26 +03:00
parent 1475d8c4db
commit 3e327747bc

View File

@@ -12,11 +12,10 @@
""" """
import requests import requests
import json
import logging import logging
from uuid import uuid1 from uuid import uuid1
import warnings import warnings
from random import random, choice from random import choice
from datetime import datetime from datetime import datetime
from bs4 import BeautifulSoup as bs from bs4 import BeautifulSoup as bs
from mimetypes import guess_type from mimetypes import guess_type
@@ -52,15 +51,15 @@ facebookEncoding = 'UTF-8'
# Log settings # Log settings
log = logging.getLogger("client") log = logging.getLogger("client")
class Client(object): class Client(object):
"""A client for the Facebook Chat (Messenger). """A client for the Facebook Chat (Messenger).
See http://github.com/carpedm20/fbchat for complete See http://github.com/carpedm20/fbchat for complete
documentation for the API. documentation for the API.
""" """
def __init__(self, email, password, debug=True, user_agent=None , max_retries=5, do_login=True): def __init__(self, email, password, debug=True, user_agent=None, max_retries=5, do_login=True):
"""A client for the Facebook Chat (Messenger). """A client for the Facebook Chat (Messenger).
:param email: Facebook `email` or `id` or `phone number` :param email: Facebook `email` or `id` or `phone number`
@@ -68,7 +67,6 @@ class Client(object):
import fbchat import fbchat
chat = fbchat.Client(email, password) chat = fbchat.Client(email, password)
""" """
if do_login and not (email and password): if do_login and not (email and password):
@@ -119,7 +117,7 @@ class Client(object):
handler.setLevel(logging_level) handler.setLevel(logging_level)
log.addHandler(handler) log.addHandler(handler)
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
if do_login: if do_login:
self.login(email, password, max_retries) self.login(email, password, max_retries)
@@ -131,13 +129,12 @@ class Client(object):
This method shouldn't be used anymore. This method shouldn't be used anymore.
Use the log itself: Use the log itself:
>>> import logging >>> import logging
>>> from fbchat.client import Client, log >>> from fbchat.client import log
>>> log.setLevel(logging.DEBUG) >>> log.setLevel(logging.DEBUG)
You can do the same thing by adding the 'debug' argument: You can do the same thing by adding the 'debug' argument:
>>> from fbchat import Client >>> from fbchat import Client
>>> client = Client("...", "...", debug=True) >>> client = Client("...", "...", debug=True)
""" """
warnings.warn( warnings.warn(
"Client._console shouldn't be used. Use 'log.<level>'", "Client._console shouldn't be used. Use 'log.<level>'",
@@ -150,10 +147,9 @@ class Client(object):
self.ttstamp += '2' self.ttstamp += '2'
def _generatePayload(self, query): def _generatePayload(self, query):
''' """Adds the following defaults to the payload:
Adds the following defaults to the payload:
__rev, __user, __a, ttstamp, fb_dtsg, __req __rev, __user, __a, ttstamp, fb_dtsg, __req
''' """
payload = self.payloadDefault.copy() payload = self.payloadDefault.copy()
if query: if query:
payload.update(query) payload.update(query)
@@ -230,7 +226,7 @@ class Client(object):
data['login'] = 'Log In' data['login'] = 'Log In'
r = self._cleanPost(LoginURL, data) r = self._cleanPost(LoginURL, data)
# Usually, 'Checkpoint' will refer to 2FA # Usually, 'Checkpoint' will refer to 2FA
if 'checkpoint' in r.url and 'Enter Security Code to Continue' in r.text: if 'checkpoint' in r.url and 'Enter Security Code to Continue' in r.text:
r = self._2FA(r) r = self._2FA(r)
@@ -244,11 +240,11 @@ class Client(object):
return True return True
else: else:
return False return False
def _2FA(self,r): def _2FA(self,r):
soup = bs(r.text, "lxml") soup = bs(r.text, "lxml")
data = dict() data = dict()
s = raw_input('Please enter your 2FA code --> ') s = input('Please enter your 2FA code --> ')
data['approvals_code'] = s data['approvals_code'] = s
data['fb_dtsg'] = soup.find("input", {'name':'fb_dtsg'})['value'] data['fb_dtsg'] = soup.find("input", {'name':'fb_dtsg'})['value']
data['nh'] = soup.find("input", {'name':'nh'})['value'] data['nh'] = soup.find("input", {'name':'nh'})['value']
@@ -259,48 +255,48 @@ class Client(object):
if 'home' in r.url: if 'home' in r.url:
return r return r
del(data['approvals_code']) del(data['approvals_code'])
del(data['submit[Submit Code]']) del(data['submit[Submit Code]'])
del(data['codes_submitted']) del(data['codes_submitted'])
data['name_action_selected'] = 'save_device' data['name_action_selected'] = 'save_device'
data['submit[Continue]'] = 'Continue' data['submit[Continue]'] = 'Continue'
log.info('Saving browser') #At this stage, we have dtsg, nh, name_action_selected, submit[Continue] log.info('Saving browser') # At this stage, we have dtsg, nh, name_action_selected, submit[Continue]
r = self._cleanPost(CheckpointURL, data) r = self._cleanPost(CheckpointURL, data)
if 'home' in r.url: if 'home' in r.url:
return r return r
del(data['name_action_selected']) del(data['name_action_selected'])
log.info('Starting Facebook checkup flow') #At this stage, we have dtsg, nh, submit[Continue] log.info('Starting Facebook checkup flow') # At this stage, we have dtsg, nh, submit[Continue]
r = self._cleanPost(CheckpointURL, data) r = self._cleanPost(CheckpointURL, data)
if 'home' in r.url: if 'home' in r.url:
return r return r
del(data['submit[Continue]']) del(data['submit[Continue]'])
data['submit[This was me]'] = 'This Was Me' data['submit[This was me]'] = 'This Was Me'
log.info('Verifying login attempt') #At this stage, we have dtsg, nh, submit[This was me] log.info('Verifying login attempt') # At this stage, we have dtsg, nh, submit[This was me]
r = self._cleanPost(CheckpointURL, data) r = self._cleanPost(CheckpointURL, data)
if 'home' in r.url: if 'home' in r.url:
return r return r
del(data['submit[This was me]']) del(data['submit[This was me]'])
data['submit[Continue]'] = 'Continue' data['submit[Continue]'] = 'Continue'
data['name_action_selected'] = 'save_device' data['name_action_selected'] = 'save_device'
log.info('Saving device again') #At this stage, we have dtsg, nh, submit[Continue], name_action_selected log.info('Saving device again') # At this stage, we have dtsg, nh, submit[Continue], name_action_selected
r = self._cleanPost(CheckpointURL, data) r = self._cleanPost(CheckpointURL, data)
return r return r
def saveSession(self, sessionfile): def saveSession(self, sessionfile):
"""Dumps the session cookies to (sessionfile). """Dumps the session cookies to (sessionfile).
WILL OVERWRITE ANY EXISTING FILE WILL OVERWRITE ANY EXISTING FILE
:param sessionfile: location of saved session file :param sessionfile: location of saved session file
""" """
log.info('Saving session') log.info('Saving session')
with open(sessionfile, 'w') as f: with open(sessionfile, 'w') as f:
# Grab cookies from current session, and save them as JSON # Grab cookies from current session, and save them as JSON
@@ -308,10 +304,10 @@ class Client(object):
def loadSession(self, sessionfile): def loadSession(self, sessionfile):
"""Loads session cookies from (sessionfile) """Loads session cookies from (sessionfile)
:param sessionfile: location of saved session file :param sessionfile: location of saved session file
""" """
log.info('Loading session') log.info('Loading session')
with open(sessionfile, 'r') as f: with open(sessionfile, 'r') as f:
try: try:
@@ -328,11 +324,11 @@ class Client(object):
def login(self, email, password, max_retries=5): def login(self, email, password, max_retries=5):
# Logging in # Logging in
log.info("Logging in...") log.info("Logging in...")
self.email = email self.email = email
self.password = password self.password = password
for i in range(1,max_retries+1): for i in range(1, max_retries+1):
if not self._login(): 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) time.sleep(1)
@@ -356,16 +352,12 @@ class Client(object):
self.seq = "0" self.seq = "0"
return r return r
def listen(self):
pass
def _adapt_user_in_chat_to_user_model(self, user_in_chat): def _adapt_user_in_chat_to_user_model(self, user_in_chat):
""" Adapts user info from chat to User model acceptable initial dict
''' Adapts user info from chat to User model acceptable initial dict
:param user_in_chat: user info from chat :param user_in_chat: user info from chat
'dir': None, 'dir': None,
'mThumbSrcSmall': None, 'mThumbSrcSmall': None,
'is_friend': False, 'is_friend': False,
@@ -382,9 +374,7 @@ class Client(object):
'uri': 'https://www.facebook.com/profile.php?id=100014812758264', 'uri': 'https://www.facebook.com/profile.php?id=100014812758264',
'id': '100014812758264', 'id': '100014812758264',
'gender': 2 'gender': 2
"""
'''
return { return {
'type': 'user', 'type': 'user',
@@ -457,7 +447,7 @@ class Client(object):
:param add_user_ids: a list of user ids to add to a chat :param add_user_ids: a list of user ids to add to a chat
""" """
messageAndOTID=generateOfflineThreadingID() messageAndOTID = generateOfflineThreadingID()
timestamp = now() timestamp = now()
date = datetime.now() date = datetime.now()
data = { data = {
@@ -488,12 +478,12 @@ class Client(object):
'manual_retry_cnt' : '0', 'manual_retry_cnt' : '0',
'signatureID' : getSignatureID() 'signatureID' : getSignatureID()
} }
if message_type.lower() == 'group': if message_type.lower() == 'group':
data["thread_fbid"] = recipient_id data["thread_fbid"] = recipient_id
else: else:
data["other_user_fbid"] = recipient_id data["other_user_fbid"] = recipient_id
if add_user_ids: if add_user_ids:
data['action_type'] = 'ma-type:log-message' data['action_type'] = 'ma-type:log-message'
# It's possible to add multiple users # It's possible to add multiple users
@@ -503,7 +493,7 @@ class Client(object):
else: else:
data['action_type'] = 'ma-type:user-generated-message' data['action_type'] = 'ma-type:user-generated-message'
data['body'] = message data['body'] = message
data['has_attachment'] = image_id != None data['has_attachment'] = image_id is not None
data['specific_to_list[0]'] = 'fbid:' + str(recipient_id) data['specific_to_list[0]'] = 'fbid:' + str(recipient_id)
data['specific_to_list[1]'] = 'fbid:' + str(self.uid) data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
@@ -519,10 +509,10 @@ class Client(object):
data["sticker_id"] = sticker data["sticker_id"] = sticker
r = self._post(SendURL, data) r = self._post(SendURL, data)
if not r.ok: if not r.ok:
return False return False
if isinstance(r._content, str) is False: if isinstance(r._content, str) is False:
r._content = r._content.decode(facebookEncoding) r._content = r._content.decode(facebookEncoding)
j = get_json(r._content) j = get_json(r._content)
@@ -570,7 +560,7 @@ class Client(object):
r._content = r._content.decode(facebookEncoding) r._content = r._content.decode(facebookEncoding)
# Strip the start and parse out the returned image_id # Strip the start and parse out the returned image_id
return json.loads(r._content[9:])['payload']['metadata'][0]['image_id'] return json.loads(r._content[9:])['payload']['metadata'][0]['image_id']
def getThreadInfo(self, userID, last_n=20, start=None, thread_type='user'): def getThreadInfo(self, userID, last_n=20, start=None, thread_type='user'):
"""Get the info of one Thread """Get the info of one Thread
@@ -579,7 +569,7 @@ class Client(object):
:param start: (optional) the start index of a thread (Deprecated) :param start: (optional) the start index of a thread (Deprecated)
:param thread_type: (optional) change from 'user' for group threads :param thread_type: (optional) change from 'user' for group threads
""" """
assert last_n > 0, 'length must be positive integer, got %d' % last_n assert last_n > 0, 'length must be positive integer, got %d' % last_n
assert start is None, '`start` is deprecated, always 0 offset querry is returned' assert start is None, '`start` is deprecated, always 0 offset querry is returned'
data = {} data = {}
@@ -613,11 +603,11 @@ class Client(object):
"""Get thread list of your facebook account. """Get thread list of your facebook account.
:param start: the start index of a thread :param start: the start index of a thread
:param end: (optional) the last index of a thread :param length: (optional) the length of a thread
""" """
assert length < 21, '`length` is deprecated, max. last 20 threads are returned' assert length < 21, '`length` is deprecated, max. last 20 threads are returned'
timestamp = now() timestamp = now()
date = datetime.now() date = datetime.now()
data = { data = {
@@ -659,7 +649,7 @@ class Client(object):
'client': 'mercury_sync', 'client': 'mercury_sync',
'folders[0]': 'inbox', 'folders[0]': 'inbox',
'last_action_timestamp': now() - 60*1000 'last_action_timestamp': now() - 60*1000
#'last_action_timestamp': 0 # 'last_action_timestamp': 0
} }
r = self._post(ThreadSyncURL, form) r = self._post(ThreadSyncURL, form)
@@ -722,10 +712,9 @@ class Client(object):
def _getSticky(self): def _getSticky(self):
''' """Call pull api to get sticky and pool parameter,
Call pull api to get sticky and pool parameter,
newer api needs these parameter to work. newer api needs these parameter to work.
''' """
data = { data = {
"msgs_recv": 0, "msgs_recv": 0,
@@ -745,9 +734,7 @@ class Client(object):
def _pullMessage(self, sticky, pool): def _pullMessage(self, sticky, pool):
''' """Call pull api with seq value to get message data."""
Call pull api with seq value to get message data.
'''
data = { data = {
"msgs_recv": 0, "msgs_recv": 0,
@@ -765,10 +752,9 @@ class Client(object):
def _parseMessage(self, content): def _parseMessage(self, content):
''' """Get message and author name from content.
Get message and author name from content.
May contains multiple messages in the content. May contains multiple messages in the content.
''' """
if 'ms' not in content: return if 'ms' not in content: return
@@ -878,14 +864,14 @@ class Client(object):
def fbidStrip(_fbid): def fbidStrip(_fbid):
# Stripping of `fbid:` from author_id # Stripping of `fbid:` from author_id
if type(_fbid) == int: if type(_fbid) == int:
return _fbid return _fbid
if type(_fbid) == str and 'fbid:' in _fbid: if type(_fbid) == str and 'fbid:' in _fbid:
return int(_fbid[5:]) return int(_fbid[5:])
user_ids = [fbidStrip(uid) for uid in user_ids] user_ids = [fbidStrip(uid) for uid in user_ids]
data = {"ids[{}]".format(i):uid for i,uid in enumerate(user_ids)} data = {"ids[{}]".format(i):uid for i,uid in enumerate(user_ids)}
r = self._post(UserInfoURL, data) r = self._post(UserInfoURL, data)
@@ -898,39 +884,39 @@ class Client(object):
def remove_user_from_chat(self, threadID, userID): def remove_user_from_chat(self, threadID, userID):
"""Remove user (userID) from group chat (threadID) """Remove user (userID) from group chat (threadID)
:param threadID: group chat id :param threadID: group chat id
:param userID: user id to remove from chat :param userID: user id to remove from chat
""" """
data = { data = {
"uid" : userID, "uid" : userID,
"tid" : threadID "tid" : threadID
} }
r = self._post(RemoveUserURL, data) r = self._post(RemoveUserURL, data)
self._console(r) log.info(r)
self._console(data) log.info(data)
return r.ok return r.ok
def add_users_to_chat(self, threadID, userID): def add_users_to_chat(self, threadID, userID):
"""Add user (userID) to group chat (threadID) """Add user (userID) to group chat (threadID)
:param threadID: group chat id :param threadID: group chat id
:param userID: user id to add to chat :param userID: user id to add to chat
""" """
return self.send(threadID, message_type='group', add_user_ids=[userID]) return self.send(threadID, message_type='group', add_user_ids=[userID])
def changeThreadTitle(self, threadID, newTitle): def changeThreadTitle(self, threadID, newTitle):
"""Change title of a group conversation """Change title of a group conversation
:param threadID: group chat id :param threadID: group chat id
:param newTitle: new group chat title :param newTitle: new group chat title
""" """
messageAndOTID = generateOfflineThreadingID() messageAndOTID = generateOfflineThreadingID()
timestamp = now() timestamp = now()
date = datetime.now() date = datetime.now()
@@ -964,76 +950,61 @@ class Client(object):
r = self._post(SendURL, data) r = self._post(SendURL, data)
self._console(r) log.info(r)
self._console(data) log.info(data)
return r.ok return r.ok
def on_message_new(self, mid, author_id, message, metadata, recipient_id, thread_type): def on_message_new(self, mid, author_id, message, metadata, recipient_id, thread_type):
''' """subclass Client and override this method to add custom behavior on event
subclass Client and override this method to add custom behavior on event
This version of on_message recieves recipient_id and thread_type.
This version of on_message recieves recipient_id and thread_type. For backwards compatability, this data is sent directly to the old on_message. For backwards compatability, this data is sent directly to the old on_message.
''' """
self.on_message(mid, author_id, None, message, metadata) self.on_message(mid, author_id, None, message, metadata)
def on_message(self, mid, author_id, author_name, message, metadata): def on_message(self, mid, author_id, author_name, message, metadata):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event
'''
self.markAsDelivered(author_id, mid) self.markAsDelivered(author_id, mid)
self.markAsRead(author_id) self.markAsRead(author_id)
log.info("%s said: %s"%(author_name, message)) log.info("%s said: %s"%(author_name, message))
def on_friend_request(self, from_id): def on_friend_request(self, from_id):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event log.info("friend request from %s"%from_id)
'''
log.info("friend request from %s"%from_id)
def on_typing(self, author_id): def on_typing(self, author_id):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event
'''
pass pass
def on_read(self, author, reader, time): def on_read(self, author, reader, time):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event
'''
pass pass
def on_people_added(self, user_ids, actor_id, thread_id): def on_people_added(self, user_ids, actor_id, thread_id):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event
'''
log.info("User(s) {} was added to {} by {}".format(repr(user_ids), thread_id, actor_id)) log.info("User(s) {} was added to {} by {}".format(repr(user_ids), thread_id, actor_id))
def on_person_removed(self, user_id, actor_id, thread_id): def on_person_removed(self, user_id, actor_id, thread_id):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event
'''
log.info("User {} was removed from {} by {}".format(user_id, thread_id, actor_id)) log.info("User {} was removed from {} by {}".format(user_id, thread_id, actor_id))
def on_inbox(self, viewer, unseen, unread, other_unseen, other_unread, timestamp): def on_inbox(self, viewer, unseen, unread, other_unseen, other_unread, timestamp):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event
'''
pass pass
def on_message_error(self, exception, message): def on_message_error(self, exception, message):
''' """subclass Client and override this method to add custom behavior on event"""
subclass Client and override this method to add custom behavior on event
'''
log.warning("Exception:\n{}".format(exception)) log.warning("Exception:\n{}".format(exception))