Reworked old events, added deprecation warnings, and improved _send

- Added a new system to show an error if old events are used
- Removed `test_data.json`, since I don't want to risk that anyone
accidentally commits their username & password
- Finished work on `sendEmoji`
- Split `_send` into two parts:
`_getSendData` and `_doSendRequest`, which allows for an easier way of
adding new send requests
This commit is contained in:
Mads Marquart
2017-05-11 12:55:44 +02:00
parent 7a0c64bf9e
commit f63b9d7c4a
4 changed files with 143 additions and 130 deletions

View File

@@ -24,7 +24,7 @@ from .event_hook import *
import time
import sys
# Python 3 does not have raw_input, whereas Python 2 has and it's more secure
# Python 2's `input` executes the input, whereas `raw_input` just returns the input
try:
input = raw_input
except NameError:
@@ -95,6 +95,7 @@ class Client(object):
self.threads = []
self._setupEventHooks()
self._setupOldEventHooks()
if not user_agent:
user_agent = choice(USER_AGENTS)
@@ -141,6 +142,7 @@ class Client(object):
self.onFriendRequest = EventHook(from_id=str)
self.onUnknownMesssageType = EventHook(msg=dict)
self.onMessageError = EventHook(exception=Exception, msg=dict)
# Setup event handlers
self.onLoggingIn += lambda email: log.info("Logging in %s..." % email)
@@ -164,8 +166,51 @@ class Client(object):
self.onPersonRemoved += lambda removed_id, author_id, thread_id:\
log.info("%s removed: %s" % (author_id, removed_id))
self.onUnknownMesssageType += lambda msg:\
log.info("Unknown message type received: %s" % msg)
self.onUnknownMesssageType += lambda msg: log.info("Unknown message type received: %s" % msg)
self.onMessageError += lambda exception, msg: log.exception(exception)
def _checkOldEventHook(self, old_event, deprecated_in='0.10.2'):
if hasattr(type(self), old_event):
deprecation('Client.{}'.format(old_event), deprecated_in=deprecated_in, details='Use new event system instead')
return True
else:
return False
def _setupOldEventHooks(self):
if self._checkOldEventHook('on_message', deprecated_in='0.7.0'):
self.onMessage += lambda mid, author_id, message, thread_id, thread_type, ts, metadata:\
self.on_message(mid, author_id, None, message, metadata)
if self._checkOldEventHook('on_message_new'):
self.onMessage += lambda mid, author_id, message, thread_id, thread_type, ts, metadata:\
self.on_message_new(mid, author_id, message, metadata, thread_id, True if thread_type is ThreadType.USER else False)
if self._checkOldEventHook('on_friend_request'):
self.onFriendRequest += lambda from_id: self.on_friend_request(from_id)
if self._checkOldEventHook('on_typing'):
self.onTyping += lambda author_id, typing_status: self.on_typing(author_id)
if self._checkOldEventHook('on_read'):
self.onSeen += lambda seen_by, thread_id, timestamp: self.on_read(seen_by, thread_id, timestamp)
if self._checkOldEventHook('on_people_added'):
self.onPeopleAdded += lambda added_ids, author_id, thread_id: self.on_people_added(added_ids, author_id, thread_id)
if self._checkOldEventHook('on_person_removed'):
self.onPersonRemoved += lambda removed_id, author_id, thread_id: self.on_person_removed(removed_id, author_id, thread_id)
if self._checkOldEventHook('on_inbox'):
self.onInbox += lambda unseen, unread, recent_unread: self.on_inbox(None, unseen, unread, None, recent_unread, None)
if self._checkOldEventHook('on_qprimer'):
pass
if self._checkOldEventHook('on_message_error'):
self.onMessageError += lambda exception, msg: self.on_message_error(exception, msg)
if self._checkOldEventHook('on_unknown_type'):
self.onUnknownMesssageType += lambda msg: self.on_unknown_type(msg)
@deprecated(deprecated_in='0.6.0', details='Use log.<level> instead')
def _console(self, msg):
@@ -464,18 +509,8 @@ class Client(object):
SEND METHODS
"""
def _send(self, thread_id=None, message=None, thread_type=ThreadType.USER, emoji_size=None, image_id=None, add_user_ids=None, new_title=None):
# type: (str, str, ThreadType, EmojiSize, str, list, str) -> list
"""Send a message with given thread id
:param thread_id: the user id or thread id that you want to send a message to
:param message: a text that you want to send
:param thread_type: determines if the recipient_id is for user or thread
:param emoji_size: 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
:return: a list of message ids of the sent message(s)
"""
def _getSendData(self, thread_id=None, thread_type=ThreadType.USER):
"""Returns the data needed to send a request to `SendURL`"""
if thread_id is None:
if self.default_thread_id is not None:
@@ -522,36 +557,10 @@ class Client(object):
elif thread_type == ThreadType.GROUP:
data["thread_fbid"] = thread_id
# Set title
if new_title:
data['action_type'] = 'ma-type:log-message'
data['log_message_data[name]'] = new_title
data['log_message_type'] = 'log:thread-name'
# Set users to add
if add_user_ids:
data['action_type'] = 'ma-type:log-message'
# It's possible to add multiple users
for i, add_user_id in enumerate(add_user_ids):
data['log_message_data[added_participants][' + str(i) + ']'] = "fbid:" + str(add_user_id)
data['log_message_type'] = 'log:subscribe'
# Sending a simple message
if not add_user_ids and not new_title:
data['action_type'] = 'ma-type:user-generated-message'
data['body'] = message or ''
data['has_attachment'] = image_id is not None
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
# Set image to send
if image_id:
data['image_ids[0]'] = image_id
# Set emoji to send
if emoji_size:
data["sticker_id"] = emoji_size.value
return data
def _doSendRequest(self, data):
"""Sends the data to `SendURL`, and returns """
r = self._post(SendURL, data)
if not r.ok:
@@ -570,7 +579,7 @@ class Client(object):
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
message_ids[0] # Try accessing element
except (KeyError, IndexError) as e:
log.warning('Error when sending message: No message ids could be found')
return None
@@ -582,7 +591,14 @@ class Client(object):
@deprecated(deprecated_in='0.10.2', details='Use specific functions (eg. sendMessage()) instead')
def send(self, recipient_id=None, message=None, is_user=True, like=None, image_id=None, add_user_ids=None):
return self._send(thread_id=recipient_id, message=message, thread_type=isUserToThreadType(is_user), emoji_size=LIKES[like], image_id=image_id, add_user_ids=add_user_ids)
if add_user_ids:
return self.addUsersToChat(user_ids=add_user_ids, thread_id=recipient_id)
elif image_id:
return self.sendImage(image_id=image_id, message=message, thread_id=recipient_id, thread_type=isUserToThreadType(is_user))
elif like:
return self.sendEmoji(emoji=None, size=LIKES[like], thread_id=recipient_id, thread_type=isUserToThreadType(is_user))
else:
return self.sendMessage(message, thread_id=recipient_id, thread_type=isUserToThreadType(is_user))
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
# type: (str, str, ThreadType) -> list
@@ -594,23 +610,57 @@ class Client(object):
:param thread_type: specify whether thread_id is user or group chat
:return: a list of message ids of the sent message(s)
"""
return self._send(thread_id=thread_id, message=message, thread_type=thread_type)
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
def sendEmoji(self, emoji=None, size=EmojiSize.SMALL, thread_id=None, thread_type=ThreadType.USER):
data['action_type'] = 'ma-type:user-generated-message'
data['body'] = message or ''
data['has_attachment'] = False
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
return self._doSendRequest(data)
def sendEmoji(self, emoji=None, size=EmojiSize.LARGE, thread_id=None, thread_type=ThreadType.USER):
# type: (str, EmojiSize, str, ThreadType) -> list
"""
Sends an emoji to given (or default, if not) thread.
:param emoji: WIP
:param emoji: the chosen emoji to send
:param size: size of emoji to send
:param thread_id: user/group chat ID
:param thread_type: specify whether thread_id is user or group chat
:return: a list of message ids of the sent message(s)
"""
return self._send(thread_id=thread_id, thread_type=thread_type, emoji_size=size)
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
if emoji:
data['action_type'] = 'ma-type:user-generated-message'
data['body'] = emoji or ''
data['has_attachment'] = False
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
data['tags[0]'] = 'hot_emoji_size:' + size['name']
else:
data["sticker_id"] = size['value']
return self._doSendRequest(data)
def sendImage(self, image_id, message=None, thread_id=None, thread_type=ThreadType.USER):
"""Sends an already uploaded image with the id image_id to the thread"""
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
data['action_type'] = 'ma-type:user-generated-message'
data['body'] = message or ''
data['has_attachment'] = True
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
data['image_ids[0]'] = image_id
return self._doSendRequest(data)
def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER,
recipient_id=None, is_user=None, image=None):
recipient_id=None, is_user=None, image=None):
# type: (str, str, str, ThreadType) -> list
"""
Sends an image from given URL to given (or default, if not) thread.
@@ -632,7 +682,7 @@ class Client(object):
mimetype = guess_type(image_url)[0]
remote_image = requests.get(image_url).content
image_id = self._uploadImage({'file': (image_url, remote_image, mimetype)})
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type)
# Doesn't upload properly
def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER,
@@ -658,7 +708,7 @@ class Client(object):
image_path = image
mimetype = guess_type(image_path)[0]
image_id = self._uploadImage({'file': (image_path, open(image_path, 'rb'), mimetype)})
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type)
def addUsersToChat(self, user_ids, thread_id=None):
# type: (list, str) -> list
@@ -669,7 +719,16 @@ class Client(object):
:param thread_id: group chat ID
:return: a list of message ids of the sent message(s)
"""
return self._send(thread_id=thread_id, thread_type=ThreadType.GROUP, add_user_ids=user_ids)
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
data['action_type'] = 'ma-type:log-message'
data['log_message_type'] = 'log:subscribe'
for i, user_id in enumerate(user_ids):
data['log_message_data[added_participants][' + str(i) + ']'] = "fbid:" + str(user_id)
return self._doSendRequest(data)
def removeUserFromChat(self, user_id, thread_id=None):
# type: (str, str) -> bool
@@ -715,7 +774,14 @@ class Client(object):
:param thread_id: group chat ID
:return: a list of message ids of the sent message(s)
"""
return self._send(thread_id=thread_id, thread_type=ThreadType.GROUP, new_title=title)
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
data['action_type'] = 'ma-type:log-message'
data['log_message_data[name]'] = title
data['log_message_type'] = 'log:thread-name'
return self._doSendRequest(data)
"""
END SEND METHODS
@@ -1061,7 +1127,7 @@ class Client(object):
self.onUnknownMesssageType(msg=m)
except Exception as e:
log.debug(str(e))
self.onMessageError(exception=e, msg=msg)
@deprecated(deprecated_in='0.10.2', details='Use startListening() instead')
@@ -1140,64 +1206,3 @@ class Client(object):
if len(full_data)==1:
full_data=full_data[0]
return full_data
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
This version of on_message recieves recipient_id and thread_type.
For backwards compatability, this data is sent directly to the old on_message.
"""
self.on_message(mid, author_id, None, 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"""
self.markAsDelivered(author_id, mid)
self.markAsRead(author_id)
log.info("%s said: %s" % (author_name, message))
def on_friend_request(self, from_id):
"""subclass Client and override this method to add custom behavior on event"""
log.info("Friend request from %s." % from_id)
def on_typing(self, author_id):
"""subclass Client and override this method to add custom behavior on event"""
pass
def on_read(self, author, reader, time):
"""subclass Client and override this method to add custom behavior on event"""
pass
def on_people_added(self, user_ids, actor_id, thread_id):
"""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))
def on_person_removed(self, user_id, actor_id, thread_id):
"""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))
def on_inbox(self, viewer, unseen, unread, other_unseen, other_unread, timestamp):
"""subclass Client and override this method to add custom behavior on event"""
pass
def on_message_error(self, exception, message):
"""subclass Client and override this method to add custom behavior on event"""
log.warning("Exception:\n{}".format(exception))
def on_qprimer(self, timestamp):
pass
def on_unknown_type(self, m):
"""subclass Client and override this method to add custom behavior on event"""
log.debug("Unknown type {}".format(m))

View File

@@ -72,12 +72,19 @@ class TypingStatus(Enum):
DELETED = 0
TYPING = 1
# WIP
class EmojiSize(Enum):
LARGE = '369239383222810'
MEDIUM = '369239343222814'
SMALL = '369239263222822'
LARGE = {
'value': '369239383222810',
'name': 'large'
}
MEDIUM = {
'value': '369239343222814',
'name': 'medium'
}
SMALL = {
'value': '369239263222822',
'name': 'small'
}
LIKES = {
'l': EmojiSize.LARGE,

View File

@@ -1,6 +0,0 @@
{
"email": "",
"password": "",
"user_thread_id": "",
"group_thread_id": ""
}

View File

@@ -18,7 +18,14 @@ logging.basicConfig(level=logging.INFO)
Tests for fbchat
~~~~~~~~~~~~~~~~
To use these tests, fill in test_data.json or type this information manually in the terminal prompts.
To use these tests, make a json file called test_data.json, put this example in it, and fill in the gaps:
{
"email": "example@email.com",
"password": "example_password",
"group_thread_id": 0,
"user_thread_id": 0
}
or type this information manually in the terminal prompts.
- email: Your (or a test user's) email / phone number
- password: Your (or a test user's) password