Merge pull request #145 from Dainius14/dev
Add more events and send methods, some fixes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,5 +25,5 @@ develop-eggs
|
|||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# Data for tests
|
# Data for tests
|
||||||
test_data.json
|
my_test_data.json
|
||||||
tests.data
|
tests.data
|
306
fbchat/client.py
306
fbchat/client.py
@@ -11,6 +11,7 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from urllib import parse
|
||||||
import requests
|
import requests
|
||||||
import logging
|
import logging
|
||||||
from uuid import uuid1
|
from uuid import uuid1
|
||||||
@@ -30,29 +31,7 @@ try:
|
|||||||
except NameError:
|
except NameError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# URLs
|
|
||||||
LoginURL ="https://m.facebook.com/login.php?login_attempt=1"
|
|
||||||
SearchURL ="https://www.facebook.com/ajax/typeahead/search.php"
|
|
||||||
SendURL ="https://www.facebook.com/messaging/send/"
|
|
||||||
ThreadsURL ="https://www.facebook.com/ajax/mercury/threadlist_info.php"
|
|
||||||
ThreadSyncURL="https://www.facebook.com/ajax/mercury/thread_sync.php"
|
|
||||||
MessagesURL ="https://www.facebook.com/ajax/mercury/thread_info.php"
|
|
||||||
ReadStatusURL="https://www.facebook.com/ajax/mercury/change_read_status.php"
|
|
||||||
DeliveredURL ="https://www.facebook.com/ajax/mercury/delivery_receipts.php"
|
|
||||||
MarkSeenURL ="https://www.facebook.com/ajax/mercury/mark_seen.php"
|
|
||||||
BaseURL ="https://www.facebook.com"
|
|
||||||
MobileURL ="https://m.facebook.com/"
|
|
||||||
StickyURL ="https://0-edge-chat.facebook.com/pull"
|
|
||||||
PingURL ="https://0-channel-proxy-06-ash2.facebook.com/active_ping"
|
|
||||||
UploadURL ="https://upload.facebook.com/ajax/mercury/upload.php"
|
|
||||||
UserInfoURL ="https://www.facebook.com/chat/user_info/"
|
|
||||||
ConnectURL ="https://www.facebook.com/ajax/add_friend/action.php?dpr=1"
|
|
||||||
RemoveUserURL="https://www.facebook.com/chat/remove_participants/"
|
|
||||||
LogoutURL ="https://www.facebook.com/logout.php"
|
|
||||||
AllUsersURL ="https://www.facebook.com/chat/user_info_all"
|
|
||||||
SaveDeviceURL="https://m.facebook.com/login/save-device/cancel/"
|
|
||||||
CheckpointURL="https://m.facebook.com/login/checkpoint/"
|
|
||||||
facebookEncoding = 'UTF-8'
|
|
||||||
|
|
||||||
# Log settings
|
# Log settings
|
||||||
log = logging.getLogger("client")
|
log = logging.getLogger("client")
|
||||||
@@ -102,8 +81,8 @@ class Client(object):
|
|||||||
|
|
||||||
self._header = {
|
self._header = {
|
||||||
'Content-Type' : 'application/x-www-form-urlencoded',
|
'Content-Type' : 'application/x-www-form-urlencoded',
|
||||||
'Referer' : BaseURL,
|
'Referer' : ReqUrl.BASE,
|
||||||
'Origin' : BaseURL,
|
'Origin' : ReqUrl.BASE,
|
||||||
'User-Agent' : user_agent,
|
'User-Agent' : user_agent,
|
||||||
'Connection' : 'keep-alive',
|
'Connection' : 'keep-alive',
|
||||||
}
|
}
|
||||||
@@ -133,8 +112,11 @@ class Client(object):
|
|||||||
self.onEmojiChange = EventHook(mid=str, author_id=str, new_emoji=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
self.onEmojiChange = EventHook(mid=str, author_id=str, new_emoji=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
self.onTitleChange = EventHook(mid=str, author_id=str, new_title=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
self.onTitleChange = EventHook(mid=str, author_id=str, new_title=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
self.onNicknameChange = EventHook(mid=str, author_id=str, changed_for=str, new_title=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
self.onNicknameChange = EventHook(mid=str, author_id=str, changed_for=str, new_title=str, thread_id=str, thread_type=ThreadType, ts=str, metadata=dict)
|
||||||
# self.onTyping = EventHook(author_id=int, typing_status=TypingStatus)
|
|
||||||
# self.onSeen = EventHook(seen_by=str, thread_id=str, timestamp=str)
|
|
||||||
|
self.onMessageSeen = EventHook(seen_by=str, thread_id=str, thread_type=ThreadType, seen_ts=int, delivered_ts=int, metadata=dict)
|
||||||
|
self.onMessageDelivered = EventHook(msg_ids=list, delivered_for=str, thread_id=str, thread_type=ThreadType, ts=int, metadata=dict)
|
||||||
|
self.onMarkedSeen = EventHook(threads=list, seen_ts=int, delivered_ts=int, metadata=dict)
|
||||||
|
|
||||||
self.onInbox = EventHook(unseen=int, unread=int, recent_unread=int)
|
self.onInbox = EventHook(unseen=int, unread=int, recent_unread=int)
|
||||||
self.onPeopleAdded = EventHook(added_ids=list, author_id=str, thread_id=str)
|
self.onPeopleAdded = EventHook(added_ids=list, author_id=str, thread_id=str)
|
||||||
@@ -166,6 +148,14 @@ class Client(object):
|
|||||||
self.onPersonRemoved += lambda removed_id, author_id, thread_id:\
|
self.onPersonRemoved += lambda removed_id, author_id, thread_id:\
|
||||||
log.info("%s removed: %s" % (author_id, removed_id))
|
log.info("%s removed: %s" % (author_id, removed_id))
|
||||||
|
|
||||||
|
|
||||||
|
self.onMessageSeen += lambda seen_by, thread_id, thread_type, seen_ts, delivered_ts, metadata:\
|
||||||
|
log.info("Messages seen by %s in %s (%s) at %ss", seen_by, thread_id, thread_type.name, seen_ts/1000)
|
||||||
|
self.onMessageDelivered += lambda msg_ids, delivered_for, thread_id, thread_type, ts, metadata:\
|
||||||
|
log.info("Messages %s delivered to %s in %s (%s) at %ss", msg_ids, delivered_for, thread_id, thread_type.name, ts/1000)
|
||||||
|
self.onMarkedSeen += lambda threads, seen_ts, delivered_ts, metadata:\
|
||||||
|
log.info("Marked messages as seen in threads %s at %ss", [(x[0], x[1].name) for x in threads], seen_ts/1000)
|
||||||
|
|
||||||
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)
|
self.onMessageError += lambda exception, msg: log.exception(exception)
|
||||||
|
|
||||||
@@ -263,11 +253,11 @@ class Client(object):
|
|||||||
self.payloadDefault = {}
|
self.payloadDefault = {}
|
||||||
self.client_id = hex(int(random()*2147483648))[2:]
|
self.client_id = hex(int(random()*2147483648))[2:]
|
||||||
self.start_time = now()
|
self.start_time = now()
|
||||||
self.uid = int(self._session.cookies['c_user'])
|
self.uid = str(self._session.cookies['c_user'])
|
||||||
self.user_channel = "p_" + str(self.uid)
|
self.user_channel = "p_" + self.uid
|
||||||
self.ttstamp = ''
|
self.ttstamp = ''
|
||||||
|
|
||||||
r = self._get(BaseURL)
|
r = self._get(ReqUrl.BASE)
|
||||||
soup = bs(r.text, "lxml")
|
soup = bs(r.text, "lxml")
|
||||||
log.debug(r.text)
|
log.debug(r.text)
|
||||||
log.debug(r.url)
|
log.debug(r.url)
|
||||||
@@ -303,13 +293,13 @@ class Client(object):
|
|||||||
if not (self.email and self.password):
|
if not (self.email and self.password):
|
||||||
raise Exception("Email and password not found.")
|
raise Exception("Email and password not found.")
|
||||||
|
|
||||||
soup = bs(self._get(MobileURL).text, "lxml")
|
soup = bs(self._get(ReqUrl.MOBILE).text, "lxml")
|
||||||
data = dict((elem['name'], elem['value']) for elem in soup.findAll("input") if elem.has_attr('value') and elem.has_attr('name'))
|
data = dict((elem['name'], elem['value']) for elem in soup.findAll("input") if elem.has_attr('value') and elem.has_attr('name'))
|
||||||
data['email'] = self.email
|
data['email'] = self.email
|
||||||
data['pass'] = self.password
|
data['pass'] = self.password
|
||||||
data['login'] = 'Log In'
|
data['login'] = 'Log In'
|
||||||
|
|
||||||
r = self._cleanPost(LoginURL, data)
|
r = self._cleanPost(ReqUrl.LOGIN, 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:
|
||||||
@@ -317,7 +307,7 @@ class Client(object):
|
|||||||
|
|
||||||
# Sometimes Facebook tries to show the user a "Save Device" dialog
|
# Sometimes Facebook tries to show the user a "Save Device" dialog
|
||||||
if 'save-device' in r.url:
|
if 'save-device' in r.url:
|
||||||
r = self._cleanGet(SaveDeviceURL)
|
r = self._cleanGet(ReqUrl.SAVE_DEVICE)
|
||||||
|
|
||||||
if 'home' in r.url:
|
if 'home' in r.url:
|
||||||
self._postLogin()
|
self._postLogin()
|
||||||
@@ -337,7 +327,7 @@ class Client(object):
|
|||||||
data['codes_submitted'] = 0
|
data['codes_submitted'] = 0
|
||||||
log.info('Submitting 2FA code.')
|
log.info('Submitting 2FA code.')
|
||||||
|
|
||||||
r = self._cleanPost(CheckpointURL, data)
|
r = self._cleanPost(ReqUrl.CHECKPOINT, data)
|
||||||
|
|
||||||
if 'home' in r.url:
|
if 'home' in r.url:
|
||||||
return r
|
return r
|
||||||
@@ -349,14 +339,14 @@ class Client(object):
|
|||||||
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(ReqUrl.CHECKPOINT, 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(ReqUrl.CHECKPOINT, data)
|
||||||
|
|
||||||
if 'home' in r.url:
|
if 'home' in r.url:
|
||||||
return r
|
return r
|
||||||
@@ -364,7 +354,7 @@ class Client(object):
|
|||||||
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(ReqUrl.CHECKPOINT, data)
|
||||||
|
|
||||||
if 'home' in r.url:
|
if 'home' in r.url:
|
||||||
return r
|
return r
|
||||||
@@ -373,12 +363,12 @@ class Client(object):
|
|||||||
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(ReqUrl.CHECKPOINT, data)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def isLoggedIn(self):
|
def isLoggedIn(self):
|
||||||
# Send a request to the login url, to see if we're directed to the home page.
|
# Send a request to the login url, to see if we're directed to the home page.
|
||||||
r = self._cleanGet(LoginURL)
|
r = self._cleanGet(ReqUrl.LOGIN)
|
||||||
return 'home' in r.url
|
return 'home' in r.url
|
||||||
|
|
||||||
def getSession(self):
|
def getSession(self):
|
||||||
@@ -428,7 +418,7 @@ class Client(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
payload=self._generatePayload(data)
|
payload=self._generatePayload(data)
|
||||||
r = self._session.get(LogoutURL, headers=self._header, params=payload, timeout=timeout)
|
r = self._session.get(ReqUrl.LOGOUT, headers=self._header, params=payload, timeout=timeout)
|
||||||
# reset value
|
# reset value
|
||||||
self.payloadDefault={}
|
self.payloadDefault={}
|
||||||
self._session = requests.session()
|
self._session = requests.session()
|
||||||
@@ -456,13 +446,29 @@ class Client(object):
|
|||||||
self.default_thread_id = None
|
self.default_thread_id = None
|
||||||
self.default_thread_type = None
|
self.default_thread_type = None
|
||||||
|
|
||||||
|
def _getThreadId(self, given_thread_id, given_thread_type):
|
||||||
|
# type: (str, ThreadType) -> (str, ThreadType)
|
||||||
|
"""
|
||||||
|
Checks if thread ID is given, checks if default is set and returns correct values
|
||||||
|
|
||||||
|
:raises ValueError: if thread ID is not given and there is no default
|
||||||
|
:return: tuple of thread ID and thread type
|
||||||
|
"""
|
||||||
|
if given_thread_id is None:
|
||||||
|
if self.default_thread_id is not None:
|
||||||
|
return self.default_thread_id, self.default_thread_type
|
||||||
|
else:
|
||||||
|
raise ValueError('Thread ID is not set.')
|
||||||
|
else:
|
||||||
|
return given_thread_id, given_thread_type
|
||||||
|
|
||||||
def getAllUsers(self):
|
def getAllUsers(self):
|
||||||
""" Gets all users from chat with info included """
|
""" Gets all users from chat with info included """
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'viewer': self.uid,
|
'viewer': self.uid,
|
||||||
}
|
}
|
||||||
r = self._post(AllUsersURL, query=data)
|
r = self._post(ReqUrl.ALL_USERS, query=data)
|
||||||
if not r.ok or len(r.text) == 0:
|
if not r.ok or len(r.text) == 0:
|
||||||
return None
|
return None
|
||||||
j = get_json(r.text)
|
j = get_json(r.text)
|
||||||
@@ -496,7 +502,7 @@ class Client(object):
|
|||||||
'request_id' : str(uuid1()),
|
'request_id' : str(uuid1()),
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._get(SearchURL, payload)
|
r = self._get(ReqUrl.SEARCH, payload)
|
||||||
self.j = j = get_json(r.text)
|
self.j = j = get_json(r.text)
|
||||||
|
|
||||||
users = []
|
users = []
|
||||||
@@ -505,20 +511,13 @@ class Client(object):
|
|||||||
users.append(User(entry))
|
users.append(User(entry))
|
||||||
return users # have bug TypeError: __repr__ returned non-string (type bytes)
|
return users # have bug TypeError: __repr__ returned non-string (type bytes)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SEND METHODS
|
SEND METHODS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _getSendData(self, thread_id=None, thread_type=ThreadType.USER):
|
def _getSendData(self, thread_id=None, thread_type=ThreadType.USER):
|
||||||
"""Returns the data needed to send a request to `SendURL`"""
|
"""Returns the data needed to send a request to `SendURL`"""
|
||||||
|
|
||||||
if thread_id is None:
|
|
||||||
if self.default_thread_id is not None:
|
|
||||||
thread_id = self.default_thread_id
|
|
||||||
thread_type = self.default_thread_type
|
|
||||||
else:
|
|
||||||
raise ValueError('Thread ID is not set.')
|
|
||||||
|
|
||||||
messageAndOTID = generateOfflineThreadingID()
|
messageAndOTID = generateOfflineThreadingID()
|
||||||
timestamp = now()
|
timestamp = now()
|
||||||
date = datetime.now()
|
date = datetime.now()
|
||||||
@@ -543,7 +542,7 @@ class Client(object):
|
|||||||
'html_body' : False,
|
'html_body' : False,
|
||||||
'ui_push_phase' : 'V3',
|
'ui_push_phase' : 'V3',
|
||||||
'status' : '0',
|
'status' : '0',
|
||||||
'offline_threading_id':messageAndOTID,
|
'offline_threading_id': messageAndOTID,
|
||||||
'message_id' : messageAndOTID,
|
'message_id' : messageAndOTID,
|
||||||
'threading_id': generateMessageID(self.client_id),
|
'threading_id': generateMessageID(self.client_id),
|
||||||
'ephemeral_ttl_mode:': '0',
|
'ephemeral_ttl_mode:': '0',
|
||||||
@@ -561,7 +560,7 @@ class Client(object):
|
|||||||
|
|
||||||
def _doSendRequest(self, data):
|
def _doSendRequest(self, data):
|
||||||
"""Sends the data to `SendURL`, and returns """
|
"""Sends the data to `SendURL`, and returns """
|
||||||
r = self._post(SendURL, data)
|
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 message: Got {} response'.format(r.status_code))
|
||||||
@@ -612,44 +611,47 @@ class Client(object):
|
|||||||
: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: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
thread_id, thread_type = self._getThreadId(thread_id, thread_type)
|
||||||
|
data = self._getSendData(thread_id, thread_type)
|
||||||
|
|
||||||
data['action_type'] = 'ma-type:user-generated-message'
|
data['action_type'] = 'ma-type:user-generated-message'
|
||||||
data['body'] = message or ''
|
data['body'] = message or ''
|
||||||
data['has_attachment'] = False
|
data['has_attachment'] = False
|
||||||
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
|
data['specific_to_list[0]'] = 'fbid:' + thread_id
|
||||||
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
|
data['specific_to_list[1]'] = 'fbid:' + self.uid
|
||||||
|
|
||||||
return self._doSendRequest(data)
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
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 to given (or default, if not) thread.
|
Sends an emoji. If emoji and size are not specified a small like is sent.
|
||||||
|
|
||||||
:param emoji: the chosen emoji to send
|
: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: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
thread_id, thread_type = self._getThreadId(thread_id, thread_type)
|
||||||
|
data = self._getSendData(thread_id, thread_type)
|
||||||
|
data['action_type'] = 'ma-type:user-generated-message'
|
||||||
|
data['has_attachment'] = False
|
||||||
|
data['specific_to_list[0]'] = 'fbid:' + thread_id
|
||||||
|
data['specific_to_list[1]'] = 'fbid:' + self.uid
|
||||||
|
|
||||||
if emoji:
|
if emoji:
|
||||||
data['action_type'] = 'ma-type:user-generated-message'
|
data['body'] = emoji
|
||||||
data['body'] = emoji or ''
|
data['tags[0]'] = 'hot_emoji_size:' + size.name.lower()
|
||||||
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:
|
else:
|
||||||
data["sticker_id"] = size['value']
|
data["sticker_id"] = size.value
|
||||||
|
|
||||||
return self._doSendRequest(data)
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
def sendImage(self, image_id, message=None, thread_id=None, thread_type=ThreadType.USER):
|
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"""
|
"""Sends an already uploaded image with the id image_id to the thread"""
|
||||||
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
thread_id, thread_type = self._getThreadId(thread_id, thread_type)
|
||||||
|
data = self._getSendData(thread_id, thread_type)
|
||||||
|
|
||||||
data['action_type'] = 'ma-type:user-generated-message'
|
data['action_type'] = 'ma-type:user-generated-message'
|
||||||
data['body'] = message or ''
|
data['body'] = message or ''
|
||||||
@@ -708,6 +710,8 @@ class Client(object):
|
|||||||
if image is not None:
|
if image is not None:
|
||||||
deprecation('sendRemoteImage(image)', deprecated_in='0.10.2', details='Use sendLocalImage(image_path) instead')
|
deprecation('sendRemoteImage(image)', deprecated_in='0.10.2', details='Use sendLocalImage(image_path) instead')
|
||||||
image_path = image
|
image_path = image
|
||||||
|
|
||||||
|
thread_id, thread_type = self._getThreadId(thread_id, None)
|
||||||
mimetype = guess_type(image_path)[0]
|
mimetype = guess_type(image_path)[0]
|
||||||
image_id = self._uploadImage({'file': (image_path, open(image_path, 'rb'), mimetype)})
|
image_id = self._uploadImage({'file': (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)
|
||||||
@@ -721,8 +725,8 @@ class Client(object):
|
|||||||
:param thread_id: group chat ID
|
:param thread_id: group chat ID
|
||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
|
thread_id, thread_type = self._getThreadId(thread_id, None)
|
||||||
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
data = self._getSendData(thread_id, ThreadType.GROUP)
|
||||||
|
|
||||||
data['action_type'] = 'ma-type:log-message'
|
data['action_type'] = 'ma-type:log-message'
|
||||||
data['log_message_type'] = 'log:subscribe'
|
data['log_message_type'] = 'log:subscribe'
|
||||||
@@ -736,22 +740,20 @@ class Client(object):
|
|||||||
# type: (str, str) -> bool
|
# type: (str, str) -> bool
|
||||||
"""
|
"""
|
||||||
Adds users to given (or default, if not) thread.
|
Adds users to given (or default, if not) thread.
|
||||||
|
|
||||||
: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: true if user was removed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if thread_id is None and self.def_thread_type == ThreadType.GROUP:
|
thread_id = self._getThreadId(thread_id, None)
|
||||||
thread_id = self.def_thread_id
|
|
||||||
elif thread_id is None:
|
|
||||||
raise ValueError('Default Thread ID is not set.')
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"uid": user_id,
|
"uid": user_id,
|
||||||
"tid": thread_id
|
"tid": thread_id
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(RemoveUserURL, data)
|
r = self._post(ReqUrl.REMOVE_USER, data)
|
||||||
|
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
@@ -772,12 +774,13 @@ class Client(object):
|
|||||||
def changeGroupTitle(self, title, thread_id=None):
|
def changeGroupTitle(self, title, thread_id=None):
|
||||||
"""
|
"""
|
||||||
Change title of a group conversation.
|
Change title of a group conversation.
|
||||||
|
|
||||||
: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: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
|
thread_id, thread_type = self._getThreadId(thread_id, None)
|
||||||
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
data = self._getSendData(thread_id, ThreadType.GROUP)
|
||||||
|
|
||||||
data['action_type'] = 'ma-type:log-message'
|
data['action_type'] = 'ma-type:log-message'
|
||||||
data['log_message_data[name]'] = title
|
data['log_message_data[name]'] = title
|
||||||
@@ -785,6 +788,73 @@ class Client(object):
|
|||||||
|
|
||||||
return self._doSendRequest(data)
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
|
def changeThreadColor(self, new_color, thread_id=None):
|
||||||
|
# type: (ChatColor, 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
|
||||||
|
"""
|
||||||
|
thread_id = self._getThreadId(thread_id, None)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"color_choice": new_color.value,
|
||||||
|
"thread_or_other_fbid": thread_id
|
||||||
|
}
|
||||||
|
|
||||||
|
r = self._post(ReqUrl.CHAT_COLOR, data)
|
||||||
|
|
||||||
|
return r.ok
|
||||||
|
|
||||||
|
def reactToMessage(self, message_id, reaction):
|
||||||
|
# type: (str, MessageReaction) -> bool
|
||||||
|
"""
|
||||||
|
Reacts to a message.
|
||||||
|
|
||||||
|
:param message_id: message ID to react to
|
||||||
|
:param reaction: reaction emoji to send
|
||||||
|
:return: true if reacted
|
||||||
|
"""
|
||||||
|
full_data = {
|
||||||
|
"doc_id": 1491398900900362,
|
||||||
|
"dpr": 1,
|
||||||
|
"variables": {
|
||||||
|
"data": {
|
||||||
|
"action": "ADD_REACTION",
|
||||||
|
"client_mutation_id": "1",
|
||||||
|
"actor_id": self.uid,
|
||||||
|
"message_id": message_id,
|
||||||
|
"reaction": reaction.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = self._post(ReqUrl.MESSAGE_REACTION + "/?" + parse.urlencode(full_data))
|
||||||
|
return r.ok
|
||||||
|
|
||||||
|
def setTypingStatus(self, status, thread_id=None, thread_type=None):
|
||||||
|
# type: (TypingStatus, str, ThreadType) -> bool
|
||||||
|
"""
|
||||||
|
Sets users typing status.
|
||||||
|
|
||||||
|
:param status: typing or not typing
|
||||||
|
:param thread_id: user/group chat ID
|
||||||
|
:return: True if status changed
|
||||||
|
"""
|
||||||
|
thread_id, thread_type = self._getThreadId(thread_id, None)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"typ": status.value,
|
||||||
|
"thread": thread_id,
|
||||||
|
"to": thread_id if thread_type == ThreadType.USER else "",
|
||||||
|
"source": "mercury-chat"
|
||||||
|
}
|
||||||
|
|
||||||
|
r = self._post(ReqUrl.TYPING, data)
|
||||||
|
return r.ok
|
||||||
|
|
||||||
"""
|
"""
|
||||||
END SEND METHODS
|
END SEND METHODS
|
||||||
"""
|
"""
|
||||||
@@ -795,7 +865,7 @@ class Client(object):
|
|||||||
:param image: a tuple of (file name, data, mime type) to upload to facebook
|
:param image: a tuple of (file name, data, mime type) to upload to facebook
|
||||||
"""
|
"""
|
||||||
|
|
||||||
r = self._postFile(UploadURL, image)
|
r = self._postFile(ReqUrl.UPLOAD, image)
|
||||||
response_content = {}
|
response_content = {}
|
||||||
if isinstance(r.content, str) is False:
|
if isinstance(r.content, str) is False:
|
||||||
response_content = r.content.decode(facebookEncoding)
|
response_content = r.content.decode(facebookEncoding)
|
||||||
@@ -812,11 +882,7 @@ class Client(object):
|
|||||||
:return: a list of messages
|
:return: a list of messages
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if thread_id is None and self.is_def_thread_set:
|
thread_id, thread_type = self._getThreadId(thread_id, thread_type)
|
||||||
thread_id = self.def_thread_id
|
|
||||||
thread_type = self.def_thread_type
|
|
||||||
elif thread_id is None and not self.is_def_thread_set:
|
|
||||||
raise ValueError('Default Thread ID is not set.')
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -829,13 +895,13 @@ class Client(object):
|
|||||||
'messages[{}][{}][limit]'.format(key, thread_id): last_n - 1,
|
'messages[{}][{}][limit]'.format(key, thread_id): last_n - 1,
|
||||||
'messages[{}][{}][timestamp]'.format(key, thread_id): now()}
|
'messages[{}][{}][timestamp]'.format(key, thread_id): now()}
|
||||||
|
|
||||||
r = self._post(MessagesURL, query=data)
|
r = self._post(ReqUrl.MESSAGES, query=data)
|
||||||
if not r.ok or len(r.text) == 0:
|
if not r.ok or len(r.text) == 0:
|
||||||
return None
|
return []
|
||||||
|
|
||||||
j = get_json(r.text)
|
j = get_json(r.text)
|
||||||
if not j['payload']:
|
if not j['payload']:
|
||||||
return None
|
return []
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
for message in j['payload'].get('actions'):
|
for message in j['payload'].get('actions'):
|
||||||
@@ -859,9 +925,9 @@ class Client(object):
|
|||||||
'inbox[limit]' : length,
|
'inbox[limit]' : length,
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(ThreadsURL, data)
|
r = self._post(ReqUrl.THREADS, data)
|
||||||
if not r.ok or len(r.text) == 0:
|
if not r.ok or len(r.text) == 0:
|
||||||
return None
|
return []
|
||||||
|
|
||||||
j = get_json(r.text)
|
j = get_json(r.text)
|
||||||
|
|
||||||
@@ -894,7 +960,7 @@ class Client(object):
|
|||||||
# 'last_action_timestamp': 0
|
# 'last_action_timestamp': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(ThreadSyncURL, form)
|
r = self._post(ReqUrl.THREAD_SYNC, form)
|
||||||
if not r.ok or len(r.text) == 0:
|
if not r.ok or len(r.text) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -911,7 +977,7 @@ class Client(object):
|
|||||||
"thread_ids[%s][0]" % userID: threadID
|
"thread_ids[%s][0]" % userID: threadID
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(DeliveredURL, data)
|
r = self._post(ReqUrl.DELIVERED, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
def markAsRead(self, userID):
|
def markAsRead(self, userID):
|
||||||
@@ -921,11 +987,11 @@ class Client(object):
|
|||||||
"ids[%s]" % userID: True
|
"ids[%s]" % userID: True
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(ReadStatusURL, data)
|
r = self._post(ReqUrl.READ_STATUS, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
def markAsSeen(self):
|
def markAsSeen(self):
|
||||||
r = self._post(MarkSeenURL, {"seen_timestamp": 0})
|
r = self._post(ReqUrl.MARK_SEEN, {"seen_timestamp": 0})
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
@deprecated(deprecated_in='0.10.2', details='Use friendConnect() instead')
|
@deprecated(deprecated_in='0.10.2', details='Use friendConnect() instead')
|
||||||
@@ -939,7 +1005,7 @@ class Client(object):
|
|||||||
"action": "confirm"
|
"action": "confirm"
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._post(ConnectURL, data)
|
r = self._post(ReqUrl.CONNECT, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
def ping(self, sticky):
|
def ping(self, sticky):
|
||||||
@@ -952,7 +1018,7 @@ class Client(object):
|
|||||||
'sticky': sticky,
|
'sticky': sticky,
|
||||||
'viewer_uid': self.uid
|
'viewer_uid': self.uid
|
||||||
}
|
}
|
||||||
r = self._get(PingURL, data)
|
r = self._get(ReqUrl.PING, data)
|
||||||
return r.ok
|
return r.ok
|
||||||
|
|
||||||
def _getSticky(self):
|
def _getSticky(self):
|
||||||
@@ -966,7 +1032,7 @@ class Client(object):
|
|||||||
"clientid": self.client_id
|
"clientid": self.client_id
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._get(StickyURL, data)
|
r = self._get(ReqUrl.STICKY, data)
|
||||||
j = get_json(r.text)
|
j = get_json(r.text)
|
||||||
|
|
||||||
if 'lb_info' not in j:
|
if 'lb_info' not in j:
|
||||||
@@ -986,7 +1052,7 @@ class Client(object):
|
|||||||
"clientid": self.client_id,
|
"clientid": self.client_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
r = self._get(StickyURL, data)
|
r = self._get(ReqUrl.STICKY, data)
|
||||||
r.encoding = facebookEncoding
|
r.encoding = facebookEncoding
|
||||||
j = get_json(r.text)
|
j = get_json(r.text)
|
||||||
|
|
||||||
@@ -1024,7 +1090,7 @@ class Client(object):
|
|||||||
if metadata is not None:
|
if metadata is not None:
|
||||||
mid = metadata["messageId"]
|
mid = metadata["messageId"]
|
||||||
author_id = str(metadata['actorFbId'])
|
author_id = str(metadata['actorFbId'])
|
||||||
ts = int(metadata["timestamp"])
|
ts = int(metadata.get("timestamp"))
|
||||||
|
|
||||||
# Added participants
|
# Added participants
|
||||||
if 'addedParticipants' in delta:
|
if 'addedParticipants' in delta:
|
||||||
@@ -1076,18 +1142,38 @@ class Client(object):
|
|||||||
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata)
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Message delivered
|
||||||
|
elif delta.get("class") == "DeliveryReceipt":
|
||||||
|
message_ids = delta["messageIds"]
|
||||||
|
delivered_for = str(delta["actorFbId"])
|
||||||
|
ts = int(delta["deliveredWatermarkTimestampMs"])
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(delta)
|
||||||
|
self.onMessageDelivered(msg_ids=message_ids, delivered_for=delivered_for,
|
||||||
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata)
|
||||||
|
continue
|
||||||
|
|
||||||
# TODO properly implement these as they differ on different scenarios
|
# Message seen
|
||||||
# Seen
|
elif delta.get("class") == "ReadReceipt":
|
||||||
# elif delta.get("class") == "ReadReceipt":
|
seen_by = str(delta.get("actorFbId") or delta["threadKey"]["otherUserFbId"])
|
||||||
# seen_by = delta["actorFbId"] or delta["threadKey"]["otherUserFbId"]
|
seen_ts = int(delta["actionTimestampMs"])
|
||||||
# thread_id = delta["threadKey"].get("threadFbId")
|
delivered_ts = int(delta["watermarkTimestampMs"])
|
||||||
# self.onSeen(seen_by=seen_by, thread_id=thread_id, ts=ts)
|
thread_id, thread_type = getThreadIdAndThreadType(delta)
|
||||||
#
|
self.onMessageSeen(seen_by=seen_by, thread_id=thread_id, thread_type=thread_type,
|
||||||
# # Message delivered
|
seen_ts=seen_ts, delivered_ts=delivered_ts, metadata=metadata)
|
||||||
# elif delta.get("class") == 'DeliveryReceipt':
|
continue
|
||||||
# time_delivered = delta['deliveredWatermarkTimestampMs']
|
|
||||||
# self.onDelivered()
|
# Messages marked as seen
|
||||||
|
elif delta.get("class") == "MarkRead":
|
||||||
|
seen_ts = int(delta.get("actionTimestampMs") or delta.get("actionTimestamp"))
|
||||||
|
delivered_ts = int(delta.get("watermarkTimestampMs") or delta.get("watermarkTimestamp"))
|
||||||
|
|
||||||
|
threads = []
|
||||||
|
if "folders" not in delta:
|
||||||
|
threads = [getThreadIdAndThreadType({"threadKey": thr}) for thr in delta.get("threadKeys")]
|
||||||
|
|
||||||
|
# thread_id, thread_type = getThreadIdAndThreadType(delta)
|
||||||
|
self.onMarkedSeen(threads=threads, seen_ts=seen_ts, delivered_ts=delivered_ts, metadata=delta)
|
||||||
|
continue
|
||||||
|
|
||||||
# New message
|
# New message
|
||||||
elif delta.get("class") == "NewMessage":
|
elif delta.get("class") == "NewMessage":
|
||||||
@@ -1107,6 +1193,8 @@ class Client(object):
|
|||||||
# typing_status = TypingStatus(m.get("st"))
|
# typing_status = TypingStatus(m.get("st"))
|
||||||
# self.onTyping(author_id=author_id, typing_status=typing_status)
|
# self.onTyping(author_id=author_id, typing_status=typing_status)
|
||||||
|
|
||||||
|
# Delivered
|
||||||
|
|
||||||
# Seen
|
# Seen
|
||||||
# elif mtype == "m_read_receipt":
|
# elif mtype == "m_read_receipt":
|
||||||
#
|
#
|
||||||
@@ -1129,7 +1217,7 @@ class Client(object):
|
|||||||
self.onUnknownMesssageType(msg=m)
|
self.onUnknownMesssageType(msg=m)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.onMessageError(exception=e, msg=msg)
|
self.onMessageError(exception=e, msg=m)
|
||||||
|
|
||||||
|
|
||||||
@deprecated(deprecated_in='0.10.2', details='Use startListening() instead')
|
@deprecated(deprecated_in='0.10.2', details='Use startListening() instead')
|
||||||
@@ -1202,7 +1290,7 @@ class Client(object):
|
|||||||
|
|
||||||
|
|
||||||
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(ReqUrl.USER_INFO, data)
|
||||||
info = get_json(r.text)
|
info = get_json(r.text)
|
||||||
full_data= [details for profile,details in info['payload']['profiles'].items()]
|
full_data= [details for profile,details in info['payload']['profiles'].items()]
|
||||||
if len(full_data)==1:
|
if len(full_data)==1:
|
||||||
|
@@ -69,22 +69,13 @@ class ThreadType(Enum):
|
|||||||
GROUP = 2
|
GROUP = 2
|
||||||
|
|
||||||
class TypingStatus(Enum):
|
class TypingStatus(Enum):
|
||||||
DELETED = 0
|
STOPPED = 0
|
||||||
TYPING = 1
|
TYPING = 1
|
||||||
|
|
||||||
class EmojiSize(Enum):
|
class EmojiSize(Enum):
|
||||||
LARGE = {
|
LARGE = '369239383222810'
|
||||||
'value': '369239383222810',
|
MEDIUM = '369239343222814'
|
||||||
'name': 'large'
|
SMALL = '369239263222822'
|
||||||
}
|
|
||||||
MEDIUM = {
|
|
||||||
'value': '369239343222814',
|
|
||||||
'name': 'medium'
|
|
||||||
}
|
|
||||||
SMALL = {
|
|
||||||
'value': '369239263222822',
|
|
||||||
'name': 'small'
|
|
||||||
}
|
|
||||||
|
|
||||||
LIKES = {
|
LIKES = {
|
||||||
'l': EmojiSize.LARGE,
|
'l': EmojiSize.LARGE,
|
||||||
@@ -94,3 +85,29 @@ LIKES = {
|
|||||||
LIKES['large'] = LIKES['l']
|
LIKES['large'] = LIKES['l']
|
||||||
LIKES['medium'] =LIKES['m']
|
LIKES['medium'] =LIKES['m']
|
||||||
LIKES['small'] = LIKES['s']
|
LIKES['small'] = LIKES['s']
|
||||||
|
|
||||||
|
class ChatColor(Enum):
|
||||||
|
MESSENGER_BLUE = ''
|
||||||
|
VIKING = '#44bec7'
|
||||||
|
GOLDEN_POPPY = '#ffc300'
|
||||||
|
RADICAL_RED = '#fa3c4c'
|
||||||
|
SHOCKING = '#d696bb'
|
||||||
|
PICTON_BLUE = '#6699cc'
|
||||||
|
FREE_SPEECH_GREEN = '#13cf13'
|
||||||
|
PUMPKIN = '#ff7e29'
|
||||||
|
LIGHT_CORAL = '#e68585'
|
||||||
|
MEDIUM_SLATE_BLUE = '#7646ff'
|
||||||
|
DEEP_SKY_BLUE = '#20cef5'
|
||||||
|
FERN = '#67b868'
|
||||||
|
CAMEO = '#d4a88c'
|
||||||
|
BRILLIANT_ROSE = '#ff5ca1'
|
||||||
|
BILOBA_FLOWER = '#a695c7'
|
||||||
|
|
||||||
|
class MessageReaction(Enum):
|
||||||
|
LOVE = '😍'
|
||||||
|
SMILE = '😆'
|
||||||
|
WOW = '😮'
|
||||||
|
SAD = '😢'
|
||||||
|
ANGRY = '😠'
|
||||||
|
YES = '👍'
|
||||||
|
NO = '👎'
|
||||||
|
@@ -3,6 +3,8 @@ import json
|
|||||||
from time import time
|
from time import time
|
||||||
from random import random
|
from random import random
|
||||||
import warnings
|
import warnings
|
||||||
|
from .models import *
|
||||||
|
|
||||||
USER_AGENTS = [
|
USER_AGENTS = [
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/601.1.10 (KHTML, like Gecko) Version/8.0.5 Safari/601.1.10",
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/601.1.10 (KHTML, like Gecko) Version/8.0.5 Safari/601.1.10",
|
||||||
@@ -26,6 +28,33 @@ GENDERS = {
|
|||||||
11: 'unknown_plural',
|
11: 'unknown_plural',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReqUrl:
|
||||||
|
SEARCH = "https://www.facebook.com/ajax/typeahead/search.php"
|
||||||
|
LOGIN = "https://m.facebook.com/login.php?login_attempt=1"
|
||||||
|
SEND = "https://www.facebook.com/messaging/send/"
|
||||||
|
THREAD_SYNC = "https://www.facebook.com/ajax/mercury/thread_sync.php"
|
||||||
|
THREADS = "https://www.facebook.com/ajax/mercury/threadlist_info.php"
|
||||||
|
MESSAGES = "https://www.facebook.com/ajax/mercury/thread_info.php"
|
||||||
|
READ_STATUS = "https://www.facebook.com/ajax/mercury/change_read_status.php"
|
||||||
|
DELIVERED = "https://www.facebook.com/ajax/mercury/delivery_receipts.php"
|
||||||
|
MARK_SEEN = "https://www.facebook.com/ajax/mercury/mark_seen.php"
|
||||||
|
BASE = "https://www.facebook.com"
|
||||||
|
MOBILE = "https://m.facebook.com/"
|
||||||
|
STICKY = "https://0-edge-chat.facebook.com/pull"
|
||||||
|
PING = "https://0-channel-proxy-06-ash2.facebook.com/active_ping"
|
||||||
|
UPLOAD = "https://upload.facebook.com/ajax/mercury/upload.php"
|
||||||
|
USER_INFO = "https://www.facebook.com/chat/user_info/"
|
||||||
|
CONNECT = "https://www.facebook.com/ajax/add_friend/action.php?dpr=1"
|
||||||
|
REMOVE_USER = "https://www.facebook.com/chat/remove_participants/"
|
||||||
|
LOGOUT = "https://www.facebook.com/logout.php"
|
||||||
|
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"
|
||||||
|
MESSAGE_REACTION = "https://www.facebook.com/webgraphql/mutation"
|
||||||
|
TYPING = "https://www.facebook.com/ajax/messaging/typ.php"
|
||||||
|
|
||||||
|
facebookEncoding = 'UTF-8'
|
||||||
|
|
||||||
def now():
|
def now():
|
||||||
return int(time()*1000)
|
return int(time()*1000)
|
||||||
|
67
tests.py
67
tests.py
@@ -18,14 +18,7 @@ logging.basicConfig(level=logging.INFO)
|
|||||||
Tests for fbchat
|
Tests for fbchat
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
To use these tests, make a json file called test_data.json, put this example in it, and fill in the gaps:
|
To use these tests copy test_data.json to my_test_data.json or type this information manually in the terminal prompts.
|
||||||
{
|
|
||||||
"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
|
- email: Your (or a test user's) email / phone number
|
||||||
- password: Your (or a test user's) password
|
- password: Your (or a test user's) password
|
||||||
@@ -68,9 +61,13 @@ class TestFbchat(unittest.TestCase):
|
|||||||
self.assertTrue(client.isLoggedIn())
|
self.assertTrue(client.isLoggedIn())
|
||||||
|
|
||||||
def test_setDefaultThreadId(self):
|
def test_setDefaultThreadId(self):
|
||||||
client.setDefaultThreadId(client.uid, ThreadType.USER)
|
client.setDefaultThread(client.uid, ThreadType.USER)
|
||||||
self.assertTrue(client.sendMessage("test_default_recipient"))
|
self.assertTrue(client.sendMessage("test_default_recipient"))
|
||||||
|
|
||||||
|
def test_resetDefaultThreadId(self):
|
||||||
|
client.resetDefaultThread()
|
||||||
|
self.assertRaises(ValueError, client.sendMessage("should_not_send"))
|
||||||
|
|
||||||
def test_getAllUsers(self):
|
def test_getAllUsers(self):
|
||||||
users = client.getAllUsers()
|
users = client.getAllUsers()
|
||||||
self.assertGreater(len(users), 0)
|
self.assertGreater(len(users), 0)
|
||||||
@@ -83,19 +80,20 @@ class TestFbchat(unittest.TestCase):
|
|||||||
|
|
||||||
# Test if values are set correctly
|
# Test if values are set correctly
|
||||||
self.assertIsInstance(u.uid, int)
|
self.assertIsInstance(u.uid, int)
|
||||||
self.assertEquals(u.type, 'user')
|
self.assertEqual(u.type, 'user')
|
||||||
self.assertEquals(u.photo[:4], 'http')
|
self.assertEqual(u.photo[:4], 'http')
|
||||||
self.assertEquals(u.url[:4], 'http')
|
self.assertEqual(u.url[:4], 'http')
|
||||||
self.assertEquals(u.name, 'Mark Zuckerberg')
|
self.assertEqual(u.name, 'Mark Zuckerberg')
|
||||||
self.assertGreater(u.score, 0)
|
self.assertGreater(u.score, 0)
|
||||||
|
|
||||||
def test_sendEmoji(self):
|
def test_sendEmoji(self):
|
||||||
self.assertTrue(client.sendEmoji(EmojiSize.SMALL, user_uid, ThreadType.USER))
|
self.assertTrue(client.sendEmoji(size=EmojiSize.SMALL, thread_id=user_uid, thread_type=ThreadType.USER))
|
||||||
self.assertTrue(client.sendEmoji(EmojiSize.MEDIUM, user_uid, ThreadType.USER))
|
self.assertTrue(client.sendEmoji(size=EmojiSize.MEDIUM, thread_id=user_uid, thread_type=ThreadType.USER))
|
||||||
self.assertTrue(client.sendEmoji(EmojiSize.LARGE, user_uid, ThreadType.USER))
|
self.assertTrue(client.sendEmoji("😆", EmojiSize.LARGE, user_uid, ThreadType.USER))
|
||||||
self.assertTrue(client.sendEmoji(EmojiSize.SMALL, group_uid, ThreadType.GROUP))
|
|
||||||
self.assertTrue(client.sendEmoji(EmojiSize.MEDIUM, group_uid, ThreadType.GROUP))
|
self.assertTrue(client.sendEmoji(size=EmojiSize.SMALL, thread_id=group_uid, thread_type=ThreadType.GROUP))
|
||||||
self.assertTrue(client.sendEmoji(EmojiSize.LARGE, group_uid, ThreadType.GROUP))
|
self.assertTrue(client.sendEmoji(size=EmojiSize.MEDIUM, thread_id=group_uid, thread_type=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.assertTrue(client.sendMessage('test_send_user', user_uid, ThreadType.USER))
|
||||||
@@ -109,13 +107,13 @@ class TestFbchat(unittest.TestCase):
|
|||||||
# Idk why but doesnt work, payload is null
|
# Idk why but doesnt work, payload is null
|
||||||
self.assertTrue(client.sendLocalImage(image_local_url, 'test_send_group_images_local', user_uid, ThreadType.USER))
|
self.assertTrue(client.sendLocalImage(image_local_url, 'test_send_group_images_local', user_uid, ThreadType.USER))
|
||||||
self.assertTrue(client.sendLocalImage(image_local_url, 'test_send_group_images_local', group_uid, ThreadType.GROUP))
|
self.assertTrue(client.sendLocalImage(image_local_url, 'test_send_group_images_local', group_uid, ThreadType.GROUP))
|
||||||
|
|
||||||
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)
|
time.sleep(3)
|
||||||
info = client.getThreadInfo(20, user_uid, ThreadType.USER)
|
info = client.getThreadInfo(20, user_uid, ThreadType.USER)
|
||||||
self.assertEquals(info[0].author, 'fbid:' + client.uid)
|
self.assertEqual(info[0].author, 'fbid:' + client.uid)
|
||||||
self.assertEquals(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)
|
time.sleep(3)
|
||||||
@@ -141,6 +139,29 @@ class TestFbchat(unittest.TestCase):
|
|||||||
def test_changeThreadTitle(self):
|
def test_changeThreadTitle(self):
|
||||||
self.assertTrue(client.changeThreadTitle('test_changeThreadTitle', group_uid))
|
self.assertTrue(client.changeThreadTitle('test_changeThreadTitle', 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)
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
self.assertTrue(client.changeThreadColor(ChatColor.MESSENGER_BLUE, group_uid))
|
||||||
|
client.sendMessage(ChatColor.MESSENGER_BLUE.name, group_uid, ThreadType.GROUP)
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
self.assertTrue(client.changeThreadColor(ChatColor.BRILLIANT_ROSE, user_uid))
|
||||||
|
client.sendMessage(ChatColor.BRILLIANT_ROSE.name, user_uid, ThreadType.USER)
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
self.assertTrue(client.changeThreadColor(ChatColor.MESSENGER_BLUE, user_uid))
|
||||||
|
client.sendMessage(ChatColor.MESSENGER_BLUE.name, user_uid, ThreadType.USER)
|
||||||
|
|
||||||
|
def test_reactToMessage(self):
|
||||||
|
mid = client.sendMessage("react_to_message", user_uid, ThreadType.USER)[0]
|
||||||
|
self.assertTrue(client.reactToMessage(mid, fbchat.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=[]):
|
||||||
global client
|
global client
|
||||||
@@ -169,7 +190,7 @@ if __name__ == 'tests':
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path.join(path.dirname(__file__), 'test_data.json'), 'r') as f:
|
with open(path.join(path.dirname(__file__), 'my_test_data.json'), 'r') as f:
|
||||||
json = json.load(f)
|
json = json.load(f)
|
||||||
email = json['email']
|
email = json['email']
|
||||||
password = json['password']
|
password = json['password']
|
||||||
|
Reference in New Issue
Block a user