Fixed examples, added changeNickname and changeThreadEmoji, changed changeGroupTitle back to changeThreadTitle

I also removed the parameter `set_default_events` from __init__, since
it's not really necessary
Also added testing of examples and simple testing of listen functions
This commit is contained in:
Mads Marquart
2017-05-26 18:48:37 +02:00
parent d2741ca419
commit 39eafa5a3e
6 changed files with 173 additions and 87 deletions

View File

@@ -35,7 +35,7 @@ class Client(object):
"""
def __init__(self, email, password, debug=False, info_log=False, user_agent=None, max_retries=5,
session_cookies=None, logging_level=logging.INFO, set_default_events=True):
session_cookies=None, logging_level=logging.INFO):
"""Initializes and logs in the client
:param email: Facebook `email`, `id` or `phone number`
@@ -44,11 +44,9 @@ class Client(object):
:param max_retries: Maximum number of times to retry login
:param session_cookies: Cookies from a previous session (Will default to login if these are invalid)
:param logging_level: Configures the `logging level <https://docs.python.org/3/library/logging.html#logging-levels>`_. Defaults to `INFO`
:param set_default_events: Specifies whether the default `logging.info` events should be initialized
:type max_retries: int
:type session_cookies: dict
:type logging_level: int
:type set_default_events: bool
:raises: Exception on failed login
"""
@@ -62,7 +60,6 @@ class Client(object):
self.default_thread_id = None
self.default_thread_type = None
self.threads = []
self.set_default_events = set_default_events
self._setupEventHooks()
self._setupOldEventHooks()
@@ -94,10 +91,7 @@ class Client(object):
def _setEventHook(self, event_name, *functions):
if not hasattr(type(self), event_name):
if self.set_default_events:
eventhook = EventHook(*functions)
else:
eventhook = EventHook()
eventhook = EventHook(*functions)
setattr(self, event_name, eventhook)
def _setupEventHooks(self):
@@ -138,16 +132,18 @@ class Client(object):
log.info("Marked messages as seen in threads {} at {}s".format([(x[0], x[1].name) for x in threads], seen_ts/1000)))
self._setEventHook('onPeopleAdded', lambda added_ids, author_id, thread_id, msg:\
self._setEventHook('onPeopleAdded', lambda mid, added_ids, author_id, thread_id, ts, msg:\
log.info("{} added: {}".format(author_id, ', '.join(added_ids))))
self._setEventHook('onPersonRemoved', lambda removed_id, author_id, thread_id, msg:\
self._setEventHook('onPersonRemoved', lambda mid, removed_id, author_id, thread_id, ts, msg:\
log.info("{} removed: {}".format(author_id, removed_id)))
self._setEventHook('onFriendRequest', lambda from_id, msg: log.info("Friend request from {}".format(from_id)))
self._setEventHook('onInbox', lambda unseen, unread, recent_unread, msg: log.info('Inbox event: {}, {}, {}'.format(unseen, unread, recent_unread)))
self._setEventHook('onQprimer', lambda made, msg: None)
self._setEventHook('onUnknownMesssageType', lambda msg: log.debug('Unknown message received: {}'.format(msg)))
@@ -447,12 +443,9 @@ class Client(object):
"""
Safely logs out the client
.. todo::
Possibly check return parameter with _checkRequest, and the write documentation about the return
:param timeout: See `requests timeout <http://docs.python-requests.org/en/master/user/advanced/#timeouts>`_
:return:
:rtype:
:return: True if the action was successful
:rtype: bool
"""
data = {
'ref': "mb",
@@ -466,7 +459,8 @@ class Client(object):
self._session = requests.session()
self.req_counter = 1
self.seq = "0"
return r
return r.ok
@deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use setDefaultThread instead')
def setDefaultRecipient(self, recipient_id, is_user=True):
@@ -928,13 +922,13 @@ class Client(object):
image_id = self._uploadImage(image_path, open(image_path, 'rb'), mimetype)
return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type)
def addUsersToGroup(self, user_ids, thread_id=None):
def addUsersToGroup(self, *user_ids, thread_id=None):
"""
Adds users to a group.
:param user_ids: User ids to add
:param thread_id: Group ID to add people to. See :ref:`intro_thread_id`
:type user_ids: list
:type user_ids: list of positional arguments
:return: :ref:`Message ID <intro_message_ids>` of the sent "message"
"""
thread_id, thread_type = self._getThread(thread_id, None)
@@ -986,29 +980,61 @@ class Client(object):
def remove_user_from_chat(self, threadID, userID):
return self.removeUserFromGroup(userID, thread_id=threadID)
@deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use changeGroupTitle() instead')
def changeThreadTitle(self, threadID, newTitle):
return self.changeGroupTitle(newTitle, thread_id=threadID)
def changeGroupTitle(self, title, thread_id=None):
def changeThreadTitle(self, title, thread_id=None, thread_type=ThreadType.USER):
"""
Changes title of a group conversation.
.. todo::
Check whether this can work on group threads, and if it does, change it (back) to changeThreadTitle
Changes title of a thread
:param title: New group chat title
:param thread_id: Group ID to change title of. See :ref:`intro_thread_id`
:return: :ref:`Message ID <intro_message_ids>` of the sent "message"
:param thread_type: See :ref:`intro_thread_type`
:type thread_type: models.ThreadType
:return: True if the action was successful
:rtype: bool
"""
thread_id, thread_type = self._getThread(thread_id, None)
data = self._getSendData(thread_id, ThreadType.GROUP)
# For backwards compatability. Previously `thread_id` and `title` were swapped around
try:
int(thread_id)
except ValueError:
deprecation('changeThreadTitle(threadID, newTitle)', deprecated_in='0.10.2', removed_in='0.15.0', details='Use changeThreadTitle(title, thread_id, thread_type) instead')
title, thread_id, thread_type = thread_id, title, ThreadType.GROUP
data['action_type'] = 'ma-type:log-message'
data['log_message_data[name]'] = title
data['log_message_type'] = 'log:thread-name'
thread_id, thread_type = self._getThread(thread_id, thread_type)
return self._doSendRequest(data)
if thread_type == ThreadType.USER:
# The thread is a user, so we change the user's nickname
return self.changeNickname(title, thread_id, thread_id=thread_id, thread_type=thread_type)
else:
data = self._getSendData(thread_id, thread_type)
data['action_type'] = 'ma-type:log-message'
data['log_message_data[name]'] = title
data['log_message_type'] = 'log:thread-name'
return False if self._doSendRequest(data) is None else True
def changeNickname(self, nickname, user_id, thread_id=None, thread_type=ThreadType.USER):
"""
Changes the nickname of a user in a thread
:param nickname: New nickname
:param user_id: User that will have their nickname changed
:param thread_id: User/Group ID to change color of. See :ref:`intro_thread_id`
:param thread_type: See :ref:`intro_thread_type`
:type thread_type: models.ThreadType
:return: True if the action was successful
:rtype: bool
"""
thread_id, thread_type = self._getThread(thread_id, thread_type)
data = {
'nickname': nickname,
'participant_id': user_id,
'thread_or_other_fbid': thread_id
}
j = self._checkRequest(self._post(ReqUrl.THREAD_NICKNAME, data))
return False if j is None else True
def changeThreadColor(self, color, thread_id=None):
"""
@@ -1017,19 +1043,42 @@ class Client(object):
:param color: New thread color
:param thread_id: User/Group ID to change color of. See :ref:`intro_thread_id`
:type color: models.ThreadColor
:return: (*bool*) True if the action was successful
:return: True if the action was successful
:rtype: bool
"""
thread_id, thread_type = self._getThread(thread_id, None)
data = {
"color_choice": color.value,
"thread_or_other_fbid": thread_id
'color_choice': color.value,
'thread_or_other_fbid': thread_id
}
j = self._checkRequest(self._post(ReqUrl.THREAD_COLOR, data))
return False if j is None else True
def changeThreadEmoji(self, emoji, thread_id=None):
"""
Changes thread color
Trivia: While changing the emoji, the Facebook web client actually sends multiple different requests, though only this one is required to make the change
:param color: New thread emoji
:param thread_id: User/Group ID to change emoji of. See :ref:`intro_thread_id`
:return: True if the action was successful
:rtype: bool
"""
thread_id, thread_type = self._getThread(thread_id, None)
data = {
'emoji_choice': emoji,
'thread_or_other_fbid': thread_id
}
j = self._checkRequest(self._post(ReqUrl.THREAD_EMOJI, data))
return False if j is None else True
def reactToMessage(self, message_id, reaction):
"""
Reacts to a message.
@@ -1280,7 +1329,7 @@ class Client(object):
thread_id, thread_type = getThreadIdAndThreadType(metadata)
self.onNicknameChange(mid=mid, author_id=author_id, changed_for=changed_for,
new_nickname=new_nickname,
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata)
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
continue
# Message delivered
@@ -1347,7 +1396,7 @@ class Client(object):
# Happens on every login
elif mtype == "qprimer":
pass
self.onQprimer(made=m.get("made"), msg=m)
# Is sent before any other message
elif mtype == "deltaflow":

View File

@@ -57,6 +57,8 @@ class ReqUrl(object):
SAVE_DEVICE = "https://m.facebook.com/login/save-device/cancel/"
CHECKPOINT = "https://m.facebook.com/login/checkpoint/"
THREAD_COLOR = "https://www.facebook.com/messaging/save_thread_color/?source=thread_settings&dpr=1"
THREAD_NICKNAME = "https://www.facebook.com/messaging/save_thread_nickname/?source=thread_settings&dpr=1"
THREAD_EMOJI = "https://www.facebook.com/messaging/save_thread_emoji/?source=thread_settings&dpr=1"
MESSAGE_REACTION = "https://www.facebook.com/webgraphql/mutation"
TYPING = "https://www.facebook.com/ajax/messaging/typ.php"