From 83a45ebc03cf6d932768de595eb85a583f29178d Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 21 May 2017 23:12:30 +0200 Subject: [PATCH] Changed names with `Chat` to `Group` or `Thread` respectively, improved error handling, and changed `_doSendRequest` to return a single message id --- fbchat/__init__.py | 2 +- fbchat/client.py | 94 ++++++++++++++++++++++++++-------------------- fbchat/models.py | 2 +- fbchat/utils.py | 2 +- tests.py | 62 ++++++++++++++---------------- 5 files changed, 85 insertions(+), 77 deletions(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index a7e10cd..5113241 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -15,7 +15,7 @@ from .client import * __copyright__ = 'Copyright 2015 by Taehoon Kim' -__version__ = '0.10.3' +__version__ = '0.10.4' __license__ = 'BSD' __author__ = 'Taehoon Kim; Moreels Pieter-Jan' __email__ = 'carpedm20@gmail.com' diff --git a/fbchat/client.py b/fbchat/client.py index e5c80d5..50d0024 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -560,24 +560,33 @@ class Client(object): return data - def _doSendRequest(self, data): - """Sends the data to `SendURL`, and returns """ - r = self._post(ReqUrl.SEND, data) - + def _checkRequest(self, r): if not r.ok: - log.warning('Error when sending message: Got {} response'.format(r.status_code)) + log.warning('Error when sending request: Got {} response'.format(r.status_code)) return None j = get_json(r) if 'error' in j: # 'errorDescription' is in the users own language! - log.warning('Error #{} when sending message: {}'.format(j['error'], j['errorDescription'])) + log.warning('Error #{} when sending request: {}'.format(j['error'], j['errorDescription'])) return None - message_ids = [] + return j + + def _doSendRequest(self, data): + """Sends the data to `SendURL`, and returns the message id""" + r = self._post(ReqUrl.SEND, data) + + j = self._checkRequest(r) + + if j is None: + return None + 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 = [action['message_id'] for action in j['payload']['actions'] if 'message_id' in action] + if len(message_ids) != 1: + log.warning("Got multiple message ids' back: {}".format(message_ids)) + message_id = message_ids[0] except (KeyError, IndexError) as e: log.warning('Error when sending message: No message ids could be found') return None @@ -585,7 +594,8 @@ class Client(object): log.info('Message sent.') log.debug('Sending {}'.format(r)) log.debug('With data {}'.format(data)) - return message_ids + log.debug('Recieved message id {}'.format(message_id)) + return message_id @deprecated(deprecated_in='0.10.2', removed_in='0.15.0', 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): @@ -608,7 +618,7 @@ class Client(object): :param message: message 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: the message id of the message """ thread_id, thread_type = self._getThread(thread_id, thread_type) data = self._getSendData(thread_id, thread_type) @@ -624,13 +634,13 @@ class Client(object): def sendEmoji(self, emoji=None, size=EmojiSize.SMALL, thread_id=None, thread_type=ThreadType.USER): # type: (str, EmojiSize, str, ThreadType) -> list """ - Sends an emoji. If emoji and size are not specified a small like is sent. + Sends an emoji. If emoji and size are not specified a small like is sent. :param emoji: the chosen emoji to send. If not specified, default thread emoji is sent :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) + :param thread_type: specify whether thread_id is user or group chat + :return: the message id of the emoji """ thread_id, thread_type = self._getThread(thread_id, thread_type) data = self._getSendData(thread_id, thread_type) @@ -672,7 +682,7 @@ class Client(object): :param message: additional message :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: the message id of the message """ if recipient_id is not None: deprecation('sendRemoteImage(recipient_id)', deprecated_in='0.10.2', removed_in='0.15.0', details='Use sendRemoteImage(thread_id) instead') @@ -700,7 +710,7 @@ class Client(object): :param message: additional message :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: the message id of the message """ if recipient_id is not None: deprecation('sendLocalImage(recipient_id)', deprecated_in='0.10.2', removed_in='0.15.0', details='Use sendLocalImage(thread_id) instead') @@ -717,14 +727,14 @@ 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 addUsersToChat(self, user_ids, thread_id=None): + def addUsersToGroup(self, user_ids, thread_id=None): # type: (list, str) -> list """ - Adds users to given (or default, if not) thread. + Adds users to the given (or default, if not) group. :param user_ids: list of user ids to add :param thread_id: group chat ID - :return: a list of message ids of the sent message(s) + :return: the message id of the "message" """ thread_id, thread_type = self._getThread(thread_id, None) data = self._getSendData(thread_id, ThreadType.GROUP) @@ -737,36 +747,36 @@ class Client(object): return self._doSendRequest(data) - def removeUserFromChat(self, user_id, thread_id=None): + def removeUserFromGroup(self, user_id, thread_id=None): # type: (str, str) -> bool """ - Adds users to given (or default, if not) thread. + Adds users to the given (or default, if not) group. :param user_id: user ID to remove :param thread_id: group chat ID - :return: true if user was removed + :return: whether the action was successful """ - thread_id = self._getThread(thread_id, None) + thread_id, thread_type = self._getThread(thread_id, None) data = { "uid": user_id, "tid": thread_id } + + j = self._checkRequest(self._post(ReqUrl.REMOVE_USER, data)) - r = self._post(ReqUrl.REMOVE_USER, data) + return False if j is None else True - return r.ok - - @deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use removeUserFromChat() instead') + @deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use removeUserFromGroup() instead') def add_users_to_chat(self, threadID, userID): if not isinstance(userID, list): userID = [userID] - return self.addUsersToChat(userID, thread_id=threadID) + return self.addUsersToGroup(userID, thread_id=threadID) - @deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use removeUserFromChat() instead') + @deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use removeUserFromGroup() instead') def remove_user_from_chat(self, threadID, userID): - return self.removeUserFromChat(userID, thread_id=threadID) + 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): @@ -778,7 +788,7 @@ class Client(object): :param title: new group chat title :param thread_id: group chat ID - :return: a list of message ids of the sent message(s) + :return: the message id of the "message" """ thread_id, thread_type = self._getThread(thread_id, None) data = self._getSendData(thread_id, ThreadType.GROUP) @@ -790,24 +800,24 @@ class Client(object): return self._doSendRequest(data) def changeThreadColor(self, new_color, thread_id=None): - # type: (ChatColor, str, ThreadType) -> bool + # type: (ThreadColor, str, ThreadType) -> bool """ Changes thread color to specified color. For more info about color names - see wiki. :param new_color: new color name :param thread_id: user/group chat ID - :return: True if color was changed + :return: whether the action was successful """ - thread_id = self._getThread(thread_id, None) + thread_id, thread_type = self._getThread(thread_id, None) data = { "color_choice": new_color.value, "thread_or_other_fbid": thread_id } - r = self._post(ReqUrl.CHAT_COLOR, data) + j = self._checkRequest(self._post(ReqUrl.THREAD_COLOR, data)) - return r.ok + return False if j is None else True def reactToMessage(self, message_id, reaction): # type: (str, MessageReaction) -> bool @@ -816,7 +826,7 @@ class Client(object): :param message_id: message ID to react to :param reaction: reaction emoji to send - :return: true if reacted + :return: whether the action was successful """ full_data = { "doc_id": 1491398900900362, @@ -832,8 +842,9 @@ class Client(object): } } - r = self._post(ReqUrl.MESSAGE_REACTION + "/?" + parse.urlencode(full_data)) - return r.ok + j = self._checkRequest(self._post(ReqUrl.MESSAGE_REACTION + "/?" + parse.urlencode(full_data))) + + return False if j is None else True def setTypingStatus(self, status, thread_id=None, thread_type=None): # type: (TypingStatus, str, ThreadType) -> bool @@ -853,8 +864,9 @@ class Client(object): "source": "mercury-chat" } - r = self._post(ReqUrl.TYPING, data) - return r.ok + j = self._checkRequest(self._post(ReqUrl.TYPING, data)) + + return False if j is None else True """ END SEND METHODS diff --git a/fbchat/models.py b/fbchat/models.py index 68d0daf..4867d90 100644 --- a/fbchat/models.py +++ b/fbchat/models.py @@ -86,7 +86,7 @@ LIKES['large'] = LIKES['l'] LIKES['medium'] =LIKES['m'] LIKES['small'] = LIKES['s'] -class ChatColor(Enum): +class ThreadColor(Enum): MESSENGER_BLUE = '' VIKING = '#44bec7' GOLDEN_POPPY = '#ffc300' diff --git a/fbchat/utils.py b/fbchat/utils.py index d74dca2..0efe568 100644 --- a/fbchat/utils.py +++ b/fbchat/utils.py @@ -50,7 +50,7 @@ class ReqUrl: ALL_USERS = "https://www.facebook.com/chat/user_info_all" SAVE_DEVICE = "https://m.facebook.com/login/save-device/cancel/" CHECKPOINT = "https://m.facebook.com/login/checkpoint/" - CHAT_COLOR = "https://www.facebook.com/messaging/save_thread_color/?source=thread_settings&dpr=1" + THREAD_COLOR = "https://www.facebook.com/messaging/save_thread_color/?source=thread_settings&dpr=1" MESSAGE_REACTION = "https://www.facebook.com/webgraphql/mutation" TYPING = "https://www.facebook.com/ajax/messaging/typ.php" diff --git a/tests.py b/tests.py index b80a467..c077920 100644 --- a/tests.py +++ b/tests.py @@ -1,18 +1,13 @@ #!/usr/bin/env python -import time import json import logging -import fbchat -from fbchat.models import * -import getpass import unittest -import sys +from getpass import getpass +from sys import argv from os import path - -#Setup logging -#logging.basicConfig(level=logging.INFO) -#fbchat.log.setLevel(1000) +from fbchat import Client +from fbchat.models import * logging_level = logging.ERROR @@ -33,6 +28,10 @@ Please remember to test both python v. 2.7 and python v. 3.6! If you've made any changes to the 2FA functionality, test it with a 2FA enabled account If you only want to execute specific tests, pass the function names in the commandline +WARNING: +Do not execute the full set of tests in too quick succession. This can get you temporarily blocked for spam! +(You should execute the script at max about 10 times a day) + """ class TestFbchat(unittest.TestCase): @@ -59,7 +58,7 @@ class TestFbchat(unittest.TestCase): def test_sessions(self): global client session_cookies = client.getSession() - client = fbchat.Client(email, password, session_cookies=session_cookies, logging_level=logging_level) + client = Client(email, password, session_cookies=session_cookies, logging_level=logging_level) self.assertTrue(client.isLoggedIn()) @@ -101,8 +100,10 @@ class TestFbchat(unittest.TestCase): self.assertTrue(client.sendEmoji("😆", EmojiSize.LARGE, group_uid, ThreadType.GROUP)) def test_sendMessage(self): - self.assertTrue(client.sendMessage('test_send_user', user_uid, ThreadType.USER)) - self.assertTrue(client.sendMessage('test_send_group', group_uid, ThreadType.GROUP)) + self.assertIsNotNone(client.sendMessage('test_send_user', user_uid, ThreadType.USER)) + self.assertIsNotNone(client.sendMessage('test_send_group', group_uid, ThreadType.GROUP)) + self.assertIsNone(client.sendMessage('test_send_user_should_fail', user_uid, ThreadType.GROUP)) + self.assertIsNone(client.sendMessage('test_send_group_should_fail', group_uid, ThreadType.USER)) def test_sendImages(self): image_url = 'https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-image-128.png' @@ -115,13 +116,13 @@ class TestFbchat(unittest.TestCase): def test_getThreadInfo(self): client.sendMessage('test_user_getThreadInfo', user_uid, ThreadType.USER) - time.sleep(3) + info = client.getThreadInfo(20, user_uid, ThreadType.USER) self.assertEqual(info[0].author, 'fbid:' + client.uid) self.assertEqual(info[0].body, 'test_user_getThreadInfo') client.sendMessage('test_group_getThreadInfo', group_uid, ThreadType.GROUP) - time.sleep(3) + info = client.getThreadInfo(20, group_uid, ThreadType.GROUP) self.assertEqual(info[0].author, 'fbid:' + client.uid) self.assertEqual(info[0].body, 'test_group_getThreadInfo') @@ -137,29 +138,22 @@ class TestFbchat(unittest.TestCase): info = client.getUserInfo(4) self.assertEqual(info['name'], 'Mark Zuckerberg') - def test_removeAddFromChat(self): - self.assertTrue(client.removeUserFromChat(user_uid, group_uid)) - self.assertTrue(client.addUsersToChat([user_uid], group_uid)) + def test_removeAddFromGroup(self): + self.assertTrue(client.removeUserFromGroup(user_uid, group_uid)) + self.assertTrue(client.addUsersToGroup([user_uid], group_uid)) def test_changeGroupTitle(self): self.assertTrue(client.changeGroupTitle('test_changeGroupTitle', group_uid)) def test_changeThreadColor(self): - self.assertTrue(client.changeThreadColor(ChatColor.BRILLIANT_ROSE, group_uid)) - client.sendMessage(ChatColor.BRILLIANT_ROSE.name, group_uid, ThreadType.GROUP) - - self.assertTrue(client.changeThreadColor(ChatColor.MESSENGER_BLUE, group_uid)) - client.sendMessage(ChatColor.MESSENGER_BLUE.name, group_uid, ThreadType.GROUP) - - self.assertTrue(client.changeThreadColor(ChatColor.BRILLIANT_ROSE, user_uid)) - client.sendMessage(ChatColor.BRILLIANT_ROSE.name, user_uid, ThreadType.USER) - - self.assertTrue(client.changeThreadColor(ChatColor.MESSENGER_BLUE, user_uid)) - client.sendMessage(ChatColor.MESSENGER_BLUE.name, user_uid, ThreadType.USER) + self.assertTrue(client.changeThreadColor(ThreadColor.BRILLIANT_ROSE, group_uid)) + self.assertTrue(client.changeThreadColor(ThreadColor.MESSENGER_BLUE, group_uid)) + self.assertTrue(client.changeThreadColor(ThreadColor.BRILLIANT_ROSE, user_uid)) + self.assertTrue(client.changeThreadColor(ThreadColor.MESSENGER_BLUE, user_uid)) def test_reactToMessage(self): - mid = client.sendMessage("react_to_message", user_uid, ThreadType.USER)[0] - self.assertTrue(client.reactToMessage(mid, fbchat.MessageReaction.LOVE)) + mid = client.sendMessage("react_to_message", user_uid, ThreadType.USER) + self.assertTrue(client.reactToMessage(mid, MessageReaction.LOVE)) def start_test(param_client, param_group_uid, param_user_uid, tests=[]): @@ -170,6 +164,8 @@ def start_test(param_client, param_group_uid, param_user_uid, tests=[]): client = param_client group_uid = param_group_uid user_uid = param_user_uid + + tests = ['test_' + test for test in tests] if len(tests) == 0: suite = unittest.TestLoader().loadTestsFromTestCase(TestFbchat) @@ -197,13 +193,13 @@ if __name__ == '__main__': group_uid = json['group_thread_id'] except (IOError, IndexError) as e: email = input('Email: ') - password = getpass.getpass() + password = getpass() group_uid = input('Please enter a group thread id (To test group functionality): ') user_uid = input('Please enter a user thread id (To test kicking/adding functionality): ') print('Logging in...') - client = fbchat.Client(email, password, logging_level=logging_level) + client = Client(email, password, logging_level=logging_level) # Warning! Taking user input directly like this could be dangerous! Use only for testing purposes! - start_test(client, group_uid, user_uid, sys.argv[1:]) + start_test(client, group_uid, user_uid, argv[1:])