Changed names with Chat to Group or Thread respectively, improved error handling, and changed _doSendRequest to return a single message id

This commit is contained in:
Mads Marquart
2017-05-21 23:12:30 +02:00
parent 76c2c65a7b
commit 83a45ebc03
5 changed files with 85 additions and 77 deletions

View File

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

View File

@@ -560,24 +560,33 @@ class Client(object):
return data return data
def _doSendRequest(self, data): def _checkRequest(self, r):
"""Sends the data to `SendURL`, and returns """
r = self._post(ReqUrl.SEND, data)
if not r.ok: 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 return None
j = get_json(r) j = get_json(r)
if 'error' in j: if 'error' in j:
# 'errorDescription' is in the users own language! # '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 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: try:
message_ids += [action['message_id'] for action in j['payload']['actions'] if 'message_id' in action] message_ids = [action['message_id'] for action in j['payload']['actions'] if 'message_id' in action]
message_ids[0] # Try accessing element 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: except (KeyError, IndexError) as e:
log.warning('Error when sending message: No message ids could be found') log.warning('Error when sending message: No message ids could be found')
return None return None
@@ -585,7 +594,8 @@ class Client(object):
log.info('Message sent.') log.info('Message sent.')
log.debug('Sending {}'.format(r)) log.debug('Sending {}'.format(r))
log.debug('With data {}'.format(data)) 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') @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): 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 message: message to send
:param thread_id: user/group chat ID :param thread_id: user/group chat ID
:param thread_type: specify whether thread_id is user or group chat :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) thread_id, thread_type = self._getThread(thread_id, thread_type)
data = self._getSendData(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): def sendEmoji(self, emoji=None, size=EmojiSize.SMALL, thread_id=None, thread_type=ThreadType.USER):
# type: (str, EmojiSize, str, ThreadType) -> list # 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 emoji: the chosen emoji to send. If not specified, default thread emoji is sent
:param size: size of emoji to send :param size: size of emoji to send
:param thread_id: user/group chat ID :param thread_id: user/group chat ID
:param thread_type: specify whether thread_id is user or group chat :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 emoji
""" """
thread_id, thread_type = self._getThread(thread_id, thread_type) thread_id, thread_type = self._getThread(thread_id, thread_type)
data = self._getSendData(thread_id, thread_type) data = self._getSendData(thread_id, thread_type)
@@ -672,7 +682,7 @@ class Client(object):
:param message: additional message :param message: additional message
:param thread_id: user/group chat ID :param thread_id: user/group chat ID
:param thread_type: specify whether thread_id is user or group chat :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: 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') 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 message: additional message
:param thread_id: user/group chat ID :param thread_id: user/group chat ID
:param thread_type: specify whether thread_id is user or group chat :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: 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') 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) 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) 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 # 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 user_ids: list of user ids to add
:param thread_id: group chat ID :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) thread_id, thread_type = self._getThread(thread_id, None)
data = self._getSendData(thread_id, ThreadType.GROUP) data = self._getSendData(thread_id, ThreadType.GROUP)
@@ -737,36 +747,36 @@ class Client(object):
return self._doSendRequest(data) return self._doSendRequest(data)
def removeUserFromChat(self, user_id, thread_id=None): def removeUserFromGroup(self, user_id, thread_id=None):
# type: (str, str) -> bool # 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 user_id: user ID to remove
:param thread_id: group chat ID :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 = { data = {
"uid": user_id, "uid": user_id,
"tid": thread_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 removeUserFromGroup() instead')
@deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use removeUserFromChat() instead')
def add_users_to_chat(self, threadID, userID): def add_users_to_chat(self, threadID, userID):
if not isinstance(userID, list): if not isinstance(userID, list):
userID = [userID] 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): 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') @deprecated(deprecated_in='0.10.2', removed_in='0.15.0', details='Use changeGroupTitle() instead')
def changeThreadTitle(self, threadID, newTitle): def changeThreadTitle(self, threadID, newTitle):
@@ -778,7 +788,7 @@ class Client(object):
:param title: new group chat title :param title: new group chat title
:param thread_id: group chat ID :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) thread_id, thread_type = self._getThread(thread_id, None)
data = self._getSendData(thread_id, ThreadType.GROUP) data = self._getSendData(thread_id, ThreadType.GROUP)
@@ -790,24 +800,24 @@ class Client(object):
return self._doSendRequest(data) return self._doSendRequest(data)
def changeThreadColor(self, new_color, thread_id=None): 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. Changes thread color to specified color. For more info about color names - see wiki.
:param new_color: new color name :param new_color: new color name
:param thread_id: user/group chat ID :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 = { data = {
"color_choice": new_color.value, "color_choice": new_color.value,
"thread_or_other_fbid": thread_id "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): def reactToMessage(self, message_id, reaction):
# type: (str, MessageReaction) -> bool # type: (str, MessageReaction) -> bool
@@ -816,7 +826,7 @@ class Client(object):
:param message_id: message ID to react to :param message_id: message ID to react to
:param reaction: reaction emoji to send :param reaction: reaction emoji to send
:return: true if reacted :return: whether the action was successful
""" """
full_data = { full_data = {
"doc_id": 1491398900900362, "doc_id": 1491398900900362,
@@ -832,8 +842,9 @@ class Client(object):
} }
} }
r = self._post(ReqUrl.MESSAGE_REACTION + "/?" + parse.urlencode(full_data)) j = self._checkRequest(self._post(ReqUrl.MESSAGE_REACTION + "/?" + parse.urlencode(full_data)))
return r.ok
return False if j is None else True
def setTypingStatus(self, status, thread_id=None, thread_type=None): def setTypingStatus(self, status, thread_id=None, thread_type=None):
# type: (TypingStatus, str, ThreadType) -> bool # type: (TypingStatus, str, ThreadType) -> bool
@@ -853,8 +864,9 @@ class Client(object):
"source": "mercury-chat" "source": "mercury-chat"
} }
r = self._post(ReqUrl.TYPING, data) j = self._checkRequest(self._post(ReqUrl.TYPING, data))
return r.ok
return False if j is None else True
""" """
END SEND METHODS END SEND METHODS

View File

@@ -86,7 +86,7 @@ LIKES['large'] = LIKES['l']
LIKES['medium'] =LIKES['m'] LIKES['medium'] =LIKES['m']
LIKES['small'] = LIKES['s'] LIKES['small'] = LIKES['s']
class ChatColor(Enum): class ThreadColor(Enum):
MESSENGER_BLUE = '' MESSENGER_BLUE = ''
VIKING = '#44bec7' VIKING = '#44bec7'
GOLDEN_POPPY = '#ffc300' GOLDEN_POPPY = '#ffc300'

View File

@@ -50,7 +50,7 @@ class ReqUrl:
ALL_USERS = "https://www.facebook.com/chat/user_info_all" ALL_USERS = "https://www.facebook.com/chat/user_info_all"
SAVE_DEVICE = "https://m.facebook.com/login/save-device/cancel/" SAVE_DEVICE = "https://m.facebook.com/login/save-device/cancel/"
CHECKPOINT = "https://m.facebook.com/login/checkpoint/" 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" MESSAGE_REACTION = "https://www.facebook.com/webgraphql/mutation"
TYPING = "https://www.facebook.com/ajax/messaging/typ.php" TYPING = "https://www.facebook.com/ajax/messaging/typ.php"

View File

@@ -1,18 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python
import time
import json import json
import logging import logging
import fbchat
from fbchat.models import *
import getpass
import unittest import unittest
import sys from getpass import getpass
from sys import argv
from os import path from os import path
from fbchat import Client
#Setup logging from fbchat.models import *
#logging.basicConfig(level=logging.INFO)
#fbchat.log.setLevel(1000)
logging_level = logging.ERROR 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'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 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): class TestFbchat(unittest.TestCase):
@@ -59,7 +58,7 @@ class TestFbchat(unittest.TestCase):
def test_sessions(self): def test_sessions(self):
global client global client
session_cookies = client.getSession() 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()) self.assertTrue(client.isLoggedIn())
@@ -101,8 +100,10 @@ class TestFbchat(unittest.TestCase):
self.assertTrue(client.sendEmoji("😆", EmojiSize.LARGE, group_uid, ThreadType.GROUP)) self.assertTrue(client.sendEmoji("😆", EmojiSize.LARGE, group_uid, ThreadType.GROUP))
def test_sendMessage(self): def test_sendMessage(self):
self.assertTrue(client.sendMessage('test_send_user', user_uid, ThreadType.USER)) self.assertIsNotNone(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_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): def test_sendImages(self):
image_url = 'https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-image-128.png' 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): def test_getThreadInfo(self):
client.sendMessage('test_user_getThreadInfo', user_uid, ThreadType.USER) client.sendMessage('test_user_getThreadInfo', user_uid, ThreadType.USER)
time.sleep(3)
info = client.getThreadInfo(20, user_uid, ThreadType.USER) info = client.getThreadInfo(20, user_uid, ThreadType.USER)
self.assertEqual(info[0].author, 'fbid:' + client.uid) self.assertEqual(info[0].author, 'fbid:' + client.uid)
self.assertEqual(info[0].body, 'test_user_getThreadInfo') self.assertEqual(info[0].body, 'test_user_getThreadInfo')
client.sendMessage('test_group_getThreadInfo', group_uid, ThreadType.GROUP) client.sendMessage('test_group_getThreadInfo', group_uid, ThreadType.GROUP)
time.sleep(3)
info = client.getThreadInfo(20, group_uid, ThreadType.GROUP) info = client.getThreadInfo(20, group_uid, ThreadType.GROUP)
self.assertEqual(info[0].author, 'fbid:' + client.uid) self.assertEqual(info[0].author, 'fbid:' + client.uid)
self.assertEqual(info[0].body, 'test_group_getThreadInfo') self.assertEqual(info[0].body, 'test_group_getThreadInfo')
@@ -137,29 +138,22 @@ class TestFbchat(unittest.TestCase):
info = client.getUserInfo(4) info = client.getUserInfo(4)
self.assertEqual(info['name'], 'Mark Zuckerberg') self.assertEqual(info['name'], 'Mark Zuckerberg')
def test_removeAddFromChat(self): def test_removeAddFromGroup(self):
self.assertTrue(client.removeUserFromChat(user_uid, group_uid)) self.assertTrue(client.removeUserFromGroup(user_uid, group_uid))
self.assertTrue(client.addUsersToChat([user_uid], group_uid)) self.assertTrue(client.addUsersToGroup([user_uid], group_uid))
def test_changeGroupTitle(self): def test_changeGroupTitle(self):
self.assertTrue(client.changeGroupTitle('test_changeGroupTitle', group_uid)) self.assertTrue(client.changeGroupTitle('test_changeGroupTitle', group_uid))
def test_changeThreadColor(self): def test_changeThreadColor(self):
self.assertTrue(client.changeThreadColor(ChatColor.BRILLIANT_ROSE, group_uid)) self.assertTrue(client.changeThreadColor(ThreadColor.BRILLIANT_ROSE, group_uid))
client.sendMessage(ChatColor.BRILLIANT_ROSE.name, group_uid, ThreadType.GROUP) self.assertTrue(client.changeThreadColor(ThreadColor.MESSENGER_BLUE, group_uid))
self.assertTrue(client.changeThreadColor(ThreadColor.BRILLIANT_ROSE, user_uid))
self.assertTrue(client.changeThreadColor(ChatColor.MESSENGER_BLUE, group_uid)) self.assertTrue(client.changeThreadColor(ThreadColor.MESSENGER_BLUE, user_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)
def test_reactToMessage(self): def test_reactToMessage(self):
mid = client.sendMessage("react_to_message", user_uid, ThreadType.USER)[0] mid = client.sendMessage("react_to_message", user_uid, ThreadType.USER)
self.assertTrue(client.reactToMessage(mid, fbchat.MessageReaction.LOVE)) self.assertTrue(client.reactToMessage(mid, MessageReaction.LOVE))
def start_test(param_client, param_group_uid, param_user_uid, tests=[]): 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 client = param_client
group_uid = param_group_uid group_uid = param_group_uid
user_uid = param_user_uid user_uid = param_user_uid
tests = ['test_' + test for test in tests]
if len(tests) == 0: if len(tests) == 0:
suite = unittest.TestLoader().loadTestsFromTestCase(TestFbchat) suite = unittest.TestLoader().loadTestsFromTestCase(TestFbchat)
@@ -197,13 +193,13 @@ if __name__ == '__main__':
group_uid = json['group_thread_id'] group_uid = json['group_thread_id']
except (IOError, IndexError) as e: except (IOError, IndexError) as e:
email = input('Email: ') email = input('Email: ')
password = getpass.getpass() password = getpass()
group_uid = input('Please enter a group thread id (To test group functionality): ') 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): ') user_uid = input('Please enter a user thread id (To test kicking/adding functionality): ')
print('Logging in...') 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! # 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:])