|
|
@@ -20,6 +20,13 @@ except ImportError:
|
|
|
|
from urlparse import urlparse, parse_qs
|
|
|
|
from urlparse import urlparse, parse_qs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ACONTEXT = {
|
|
|
|
|
|
|
|
"action_history": [
|
|
|
|
|
|
|
|
{"surface": "messenger_chat_tab", "mechanism": "messenger_composer"}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Client(object):
|
|
|
|
class Client(object):
|
|
|
|
"""A client for the Facebook Chat (Messenger).
|
|
|
|
"""A client for the Facebook Chat (Messenger).
|
|
|
|
|
|
|
|
|
|
|
@@ -60,7 +67,6 @@ class Client(object):
|
|
|
|
:type logging_level: int
|
|
|
|
:type logging_level: int
|
|
|
|
:raises: FBchatException on failed login
|
|
|
|
:raises: FBchatException on failed login
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
self.sticky, self.pool = (None, None)
|
|
|
|
self.sticky, self.pool = (None, None)
|
|
|
|
self._session = requests.session()
|
|
|
|
self._session = requests.session()
|
|
|
|
self.req_counter = 1
|
|
|
|
self.req_counter = 1
|
|
|
@@ -166,6 +172,7 @@ class Client(object):
|
|
|
|
timeout=30,
|
|
|
|
timeout=30,
|
|
|
|
fix_request=False,
|
|
|
|
fix_request=False,
|
|
|
|
as_json=False,
|
|
|
|
as_json=False,
|
|
|
|
|
|
|
|
as_graphql=False,
|
|
|
|
error_retries=3,
|
|
|
|
error_retries=3,
|
|
|
|
):
|
|
|
|
):
|
|
|
|
payload = self._generatePayload(query)
|
|
|
|
payload = self._generatePayload(query)
|
|
|
@@ -179,7 +186,11 @@ class Client(object):
|
|
|
|
if not fix_request:
|
|
|
|
if not fix_request:
|
|
|
|
return r
|
|
|
|
return r
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
return check_request(r, as_json=as_json)
|
|
|
|
if as_graphql:
|
|
|
|
|
|
|
|
content = check_request(r, as_json=False)
|
|
|
|
|
|
|
|
return graphql_response_to_json(content)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return check_request(r, as_json=as_json)
|
|
|
|
except FBchatFacebookError as e:
|
|
|
|
except FBchatFacebookError as e:
|
|
|
|
if error_retries > 0 and self._fix_fb_errors(e.fb_error_code):
|
|
|
|
if error_retries > 0 and self._fix_fb_errors(e.fb_error_code):
|
|
|
|
return self._post(
|
|
|
|
return self._post(
|
|
|
@@ -188,21 +199,11 @@ class Client(object):
|
|
|
|
timeout=timeout,
|
|
|
|
timeout=timeout,
|
|
|
|
fix_request=fix_request,
|
|
|
|
fix_request=fix_request,
|
|
|
|
as_json=as_json,
|
|
|
|
as_json=as_json,
|
|
|
|
|
|
|
|
as_graphql=as_graphql,
|
|
|
|
error_retries=error_retries - 1,
|
|
|
|
error_retries=error_retries - 1,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
raise e
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
|
|
def _graphql(self, payload, error_retries=3):
|
|
|
|
|
|
|
|
content = self._post(
|
|
|
|
|
|
|
|
self.req_url.GRAPHQL, payload, fix_request=True, as_json=False
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return graphql_response_to_json(content)
|
|
|
|
|
|
|
|
except FBchatFacebookError as e:
|
|
|
|
|
|
|
|
if error_retries > 0 and self._fix_fb_errors(e.fb_error_code):
|
|
|
|
|
|
|
|
return self._graphql(payload, error_retries=error_retries - 1)
|
|
|
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cleanGet(self, url, query=None, timeout=30, allow_redirects=True):
|
|
|
|
def _cleanGet(self, url, query=None, timeout=30, allow_redirects=True):
|
|
|
|
return self._session.get(
|
|
|
|
return self._session.get(
|
|
|
|
url,
|
|
|
|
url,
|
|
|
@@ -272,15 +273,13 @@ class Client(object):
|
|
|
|
:return: A tuple containing json graphql queries
|
|
|
|
:return: A tuple containing json graphql queries
|
|
|
|
:rtype: tuple
|
|
|
|
:rtype: tuple
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
|
|
|
"method": "GET",
|
|
|
|
|
|
|
|
"response_format": "json",
|
|
|
|
|
|
|
|
"queries": graphql_queries_to_json(*queries),
|
|
|
|
|
|
|
|
}
|
|
|
|
return tuple(
|
|
|
|
return tuple(
|
|
|
|
self._graphql(
|
|
|
|
self._post(self.req_url.GRAPHQL, data, fix_request=True, as_graphql=True)
|
|
|
|
{
|
|
|
|
|
|
|
|
"method": "GET",
|
|
|
|
|
|
|
|
"response_format": "json",
|
|
|
|
|
|
|
|
"queries": graphql_queries_to_json(*queries),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def graphql_request(self, query):
|
|
|
|
def graphql_request(self, query):
|
|
|
@@ -314,7 +313,7 @@ class Client(object):
|
|
|
|
if self.uid is None:
|
|
|
|
if self.uid is None:
|
|
|
|
raise FBchatException("Could not find c_user cookie")
|
|
|
|
raise FBchatException("Could not find c_user cookie")
|
|
|
|
self.uid = str(self.uid)
|
|
|
|
self.uid = str(self.uid)
|
|
|
|
self.user_channel = "p_" + self.uid
|
|
|
|
self.user_channel = "p_{}".format(self.uid)
|
|
|
|
self.ttstamp = ""
|
|
|
|
self.ttstamp = ""
|
|
|
|
|
|
|
|
|
|
|
|
r = self._get(self.req_url.BASE)
|
|
|
|
r = self._get(self.req_url.BASE)
|
|
|
@@ -390,9 +389,9 @@ class Client(object):
|
|
|
|
if "home" in r.url:
|
|
|
|
if "home" in r.url:
|
|
|
|
return r
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
del (data["approvals_code"])
|
|
|
|
del data["approvals_code"]
|
|
|
|
del (data["submit[Submit Code]"])
|
|
|
|
del data["submit[Submit Code]"]
|
|
|
|
del (data["codes_submitted"])
|
|
|
|
del data["codes_submitted"]
|
|
|
|
|
|
|
|
|
|
|
|
data["name_action_selected"] = "save_device"
|
|
|
|
data["name_action_selected"] = "save_device"
|
|
|
|
data["submit[Continue]"] = "Continue"
|
|
|
|
data["submit[Continue]"] = "Continue"
|
|
|
@@ -404,7 +403,7 @@ class Client(object):
|
|
|
|
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(
|
|
|
|
log.info(
|
|
|
|
"Starting Facebook checkup flow."
|
|
|
|
"Starting Facebook checkup flow."
|
|
|
|
) # At this stage, we have dtsg, nh, submit[Continue]
|
|
|
|
) # At this stage, we have dtsg, nh, submit[Continue]
|
|
|
@@ -413,7 +412,7 @@ class Client(object):
|
|
|
|
if "home" in r.url:
|
|
|
|
if "home" in r.url:
|
|
|
|
return r
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
log.info(
|
|
|
|
"Verifying login attempt."
|
|
|
|
"Verifying login attempt."
|
|
|
@@ -423,7 +422,7 @@ class Client(object):
|
|
|
|
if "home" in r.url:
|
|
|
|
if "home" in r.url:
|
|
|
|
return r
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
del (data["submit[This was me]"])
|
|
|
|
del data["submit[This was me]"]
|
|
|
|
data["submit[Continue]"] = "Continue"
|
|
|
|
data["submit[Continue]"] = "Continue"
|
|
|
|
data["name_action_selected"] = "save_device"
|
|
|
|
data["name_action_selected"] = "save_device"
|
|
|
|
log.info(
|
|
|
|
log.info(
|
|
|
@@ -459,7 +458,6 @@ class Client(object):
|
|
|
|
:return: False if `session_cookies` does not contain proper cookies
|
|
|
|
:return: False if `session_cookies` does not contain proper cookies
|
|
|
|
:rtype: bool
|
|
|
|
:rtype: bool
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
# Quick check to see if session_cookies is formatted properly
|
|
|
|
# Quick check to see if session_cookies is formatted properly
|
|
|
|
if not session_cookies or "c_user" not in session_cookies:
|
|
|
|
if not session_cookies or "c_user" not in session_cookies:
|
|
|
|
return False
|
|
|
|
return False
|
|
|
@@ -512,9 +510,8 @@ class Client(object):
|
|
|
|
break
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
raise FBchatUserError(
|
|
|
|
raise FBchatUserError(
|
|
|
|
"Login failed. Check email/password. (Failed on url: {})".format(
|
|
|
|
"Login failed. Check email/password. "
|
|
|
|
login_url
|
|
|
|
"(Failed on url: {})".format(login_url)
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def logout(self):
|
|
|
|
def logout(self):
|
|
|
@@ -525,7 +522,6 @@ class Client(object):
|
|
|
|
:return: True if the action was successful
|
|
|
|
:return: True if the action was successful
|
|
|
|
:rtype: bool
|
|
|
|
:rtype: bool
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
if not hasattr(self, "fb_h"):
|
|
|
|
if not hasattr(self, "fb_h"):
|
|
|
|
h_r = self._post(self.req_url.MODERN_SETTINGS_MENU, {"pmid": "4"})
|
|
|
|
h_r = self._post(self.req_url.MODERN_SETTINGS_MENU, {"pmid": "4"})
|
|
|
|
self.fb_h = re.search(r'name=\\"h\\" value=\\"(.*?)\\"', h_r.text).group(1)
|
|
|
|
self.fb_h = re.search(r'name=\\"h\\" value=\\"(.*?)\\"', h_r.text).group(1)
|
|
|
@@ -586,15 +582,8 @@ class Client(object):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def _forcedFetch(self, thread_id, mid):
|
|
|
|
def _forcedFetch(self, thread_id, mid):
|
|
|
|
j = self.graphql_request(
|
|
|
|
params = {"thread_and_message_id": {"thread_id": thread_id, "message_id": mid}}
|
|
|
|
GraphQL(
|
|
|
|
return self.graphql_request(GraphQL(doc_id="1768656253222505", params=params))
|
|
|
|
doc_id="1768656253222505",
|
|
|
|
|
|
|
|
params={
|
|
|
|
|
|
|
|
"thread_and_message_id": {"thread_id": thread_id, "message_id": mid}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
return j
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetchThreads(self, thread_location, before=None, after=None, limit=None):
|
|
|
|
def fetchThreads(self, thread_location, before=None, after=None, limit=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -671,8 +660,6 @@ class Client(object):
|
|
|
|
and user_id not in users_to_fetch
|
|
|
|
and user_id not in users_to_fetch
|
|
|
|
):
|
|
|
|
):
|
|
|
|
users_to_fetch.append(user_id)
|
|
|
|
users_to_fetch.append(user_id)
|
|
|
|
else:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
for user_id, user in self.fetchUserInfo(*users_to_fetch).items():
|
|
|
|
for user_id, user in self.fetchUserInfo(*users_to_fetch).items():
|
|
|
|
users.append(user)
|
|
|
|
users.append(user)
|
|
|
|
return users
|
|
|
|
return users
|
|
|
@@ -685,7 +672,6 @@ class Client(object):
|
|
|
|
:rtype: list
|
|
|
|
:rtype: list
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
data = {"viewer": self.uid}
|
|
|
|
data = {"viewer": self.uid}
|
|
|
|
j = self._post(
|
|
|
|
j = self._post(
|
|
|
|
self.req_url.ALL_USERS, query=data, fix_request=True, as_json=True
|
|
|
|
self.req_url.ALL_USERS, query=data, fix_request=True, as_json=True
|
|
|
@@ -700,7 +686,6 @@ class Client(object):
|
|
|
|
# Skip invalid users
|
|
|
|
# Skip invalid users
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
users.append(User._from_all_fetch(data))
|
|
|
|
users.append(User._from_all_fetch(data))
|
|
|
|
|
|
|
|
|
|
|
|
return users
|
|
|
|
return users
|
|
|
|
|
|
|
|
|
|
|
|
def searchForUsers(self, name, limit=10):
|
|
|
|
def searchForUsers(self, name, limit=10):
|
|
|
@@ -713,10 +698,8 @@ class Client(object):
|
|
|
|
:rtype: list
|
|
|
|
:rtype: list
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
params = {"search": name, "limit": limit}
|
|
|
|
j = self.graphql_request(
|
|
|
|
j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_USER, params=params))
|
|
|
|
GraphQL(query=GraphQL.SEARCH_USER, params={"search": name, "limit": limit})
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [User._from_graphql(node) for node in j[name]["users"]["nodes"]]
|
|
|
|
return [User._from_graphql(node) for node in j[name]["users"]["nodes"]]
|
|
|
|
|
|
|
|
|
|
|
@@ -729,10 +712,8 @@ class Client(object):
|
|
|
|
:rtype: list
|
|
|
|
:rtype: list
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
params = {"search": name, "limit": limit}
|
|
|
|
j = self.graphql_request(
|
|
|
|
j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_PAGE, params=params))
|
|
|
|
GraphQL(query=GraphQL.SEARCH_PAGE, params={"search": name, "limit": limit})
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [Page._from_graphql(node) for node in j[name]["pages"]["nodes"]]
|
|
|
|
return [Page._from_graphql(node) for node in j[name]["pages"]["nodes"]]
|
|
|
|
|
|
|
|
|
|
|
@@ -746,10 +727,8 @@ class Client(object):
|
|
|
|
:rtype: list
|
|
|
|
:rtype: list
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
params = {"search": name, "limit": limit}
|
|
|
|
j = self.graphql_request(
|
|
|
|
j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_GROUP, params=params))
|
|
|
|
GraphQL(query=GraphQL.SEARCH_GROUP, params={"search": name, "limit": limit})
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [Group._from_graphql(node) for node in j["viewer"]["groups"]["nodes"]]
|
|
|
|
return [Group._from_graphql(node) for node in j["viewer"]["groups"]["nodes"]]
|
|
|
|
|
|
|
|
|
|
|
@@ -763,12 +742,8 @@ class Client(object):
|
|
|
|
:rtype: list
|
|
|
|
:rtype: list
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
params = {"search": name, "limit": limit}
|
|
|
|
j = self.graphql_request(
|
|
|
|
j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_THREAD, params=params))
|
|
|
|
GraphQL(
|
|
|
|
|
|
|
|
query=GraphQL.SEARCH_THREAD, params={"search": name, "limit": limit}
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rtn = []
|
|
|
|
rtn = []
|
|
|
|
for node in j[name]["threads"]["nodes"]:
|
|
|
|
for node in j[name]["threads"]["nodes"]:
|
|
|
@@ -784,9 +759,7 @@ class Client(object):
|
|
|
|
pass
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
log.warning(
|
|
|
|
log.warning(
|
|
|
|
"Unknown __typename: {} in {}".format(
|
|
|
|
"Unknown type {} in {}".format(repr(node["__typename"]), node)
|
|
|
|
repr(node["__typename"]), node
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return rtn
|
|
|
|
return rtn
|
|
|
@@ -864,23 +837,17 @@ class Client(object):
|
|
|
|
j = self._post(
|
|
|
|
j = self._post(
|
|
|
|
self.req_url.SEARCH_MESSAGES, data, fix_request=True, as_json=True
|
|
|
|
self.req_url.SEARCH_MESSAGES, data, fix_request=True, as_json=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
result = j["payload"]["search_snippets"][query]
|
|
|
|
result = j["payload"]["search_snippets"][query]
|
|
|
|
|
|
|
|
|
|
|
|
if fetch_messages:
|
|
|
|
if fetch_messages:
|
|
|
|
return {
|
|
|
|
search_method = self.searchForMessages
|
|
|
|
thread_id: self.searchForMessages(
|
|
|
|
|
|
|
|
query, limit=message_limit, thread_id=thread_id
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
for thread_id in result
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
return {
|
|
|
|
search_method = self.searchForMessageIDs
|
|
|
|
thread_id: self.searchForMessageIDs(
|
|
|
|
|
|
|
|
query, limit=message_limit, thread_id=thread_id
|
|
|
|
return {
|
|
|
|
)
|
|
|
|
thread_id: search_method(query, limit=message_limit, thread_id=thread_id)
|
|
|
|
for thread_id in result
|
|
|
|
for thread_id in result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _fetchInfo(self, *ids):
|
|
|
|
def _fetchInfo(self, *ids):
|
|
|
|
data = {"ids[{}]".format(i): _id for i, _id in enumerate(ids)}
|
|
|
|
data = {"ids[{}]".format(i): _id for i, _id in enumerate(ids)}
|
|
|
@@ -931,14 +898,13 @@ class Client(object):
|
|
|
|
:rtype: dict
|
|
|
|
:rtype: dict
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
threads = self.fetchThreadInfo(*user_ids)
|
|
|
|
threads = self.fetchThreadInfo(*user_ids)
|
|
|
|
users = {}
|
|
|
|
users = {}
|
|
|
|
for k in threads:
|
|
|
|
for id_, thread in threads.items():
|
|
|
|
if threads[k].type == ThreadType.USER:
|
|
|
|
if thread.type == ThreadType.USER:
|
|
|
|
users[k] = threads[k]
|
|
|
|
users[id_] = thread
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
raise FBchatUserError("Thread {} was not a user".format(threads[k]))
|
|
|
|
raise FBchatUserError("Thread {} was not a user".format(thread))
|
|
|
|
|
|
|
|
|
|
|
|
return users
|
|
|
|
return users
|
|
|
|
|
|
|
|
|
|
|
@@ -954,14 +920,13 @@ class Client(object):
|
|
|
|
:rtype: dict
|
|
|
|
:rtype: dict
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
threads = self.fetchThreadInfo(*page_ids)
|
|
|
|
threads = self.fetchThreadInfo(*page_ids)
|
|
|
|
pages = {}
|
|
|
|
pages = {}
|
|
|
|
for k in threads:
|
|
|
|
for id_, thread in threads.items():
|
|
|
|
if threads[k].type == ThreadType.PAGE:
|
|
|
|
if thread.type == ThreadType.PAGE:
|
|
|
|
pages[k] = threads[k]
|
|
|
|
pages[id_] = thread
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
raise FBchatUserError("Thread {} was not a page".format(threads[k]))
|
|
|
|
raise FBchatUserError("Thread {} was not a page".format(thread))
|
|
|
|
|
|
|
|
|
|
|
|
return pages
|
|
|
|
return pages
|
|
|
|
|
|
|
|
|
|
|
@@ -974,14 +939,13 @@ class Client(object):
|
|
|
|
:rtype: dict
|
|
|
|
:rtype: dict
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
threads = self.fetchThreadInfo(*group_ids)
|
|
|
|
threads = self.fetchThreadInfo(*group_ids)
|
|
|
|
groups = {}
|
|
|
|
groups = {}
|
|
|
|
for k in threads:
|
|
|
|
for id_, thread in threads.items():
|
|
|
|
if threads[k].type == ThreadType.GROUP:
|
|
|
|
if thread.type == ThreadType.GROUP:
|
|
|
|
groups[k] = threads[k]
|
|
|
|
groups[id_] = thread
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
raise FBchatUserError("Thread {} was not a group".format(threads[k]))
|
|
|
|
raise FBchatUserError("Thread {} was not a group".format(thread))
|
|
|
|
|
|
|
|
|
|
|
|
return groups
|
|
|
|
return groups
|
|
|
|
|
|
|
|
|
|
|
@@ -997,21 +961,16 @@ class Client(object):
|
|
|
|
:rtype: dict
|
|
|
|
:rtype: dict
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
queries = []
|
|
|
|
queries = []
|
|
|
|
for thread_id in thread_ids:
|
|
|
|
for thread_id in thread_ids:
|
|
|
|
queries.append(
|
|
|
|
params = {
|
|
|
|
GraphQL(
|
|
|
|
"id": thread_id,
|
|
|
|
doc_id="2147762685294928",
|
|
|
|
"message_limit": 0,
|
|
|
|
params={
|
|
|
|
"load_messages": False,
|
|
|
|
"id": thread_id,
|
|
|
|
"load_read_receipts": False,
|
|
|
|
"message_limit": 0,
|
|
|
|
"before": None,
|
|
|
|
"load_messages": False,
|
|
|
|
}
|
|
|
|
"load_read_receipts": False,
|
|
|
|
queries.append(GraphQL(doc_id="2147762685294928", params=params))
|
|
|
|
"before": None,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
j = self.graphql_requests(*queries)
|
|
|
|
j = self.graphql_requests(*queries)
|
|
|
|
|
|
|
|
|
|
|
@@ -1067,33 +1026,26 @@ class Client(object):
|
|
|
|
:rtype: list
|
|
|
|
:rtype: list
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
|
|
|
|
|
|
|
|
j = self.graphql_request(
|
|
|
|
params = {
|
|
|
|
GraphQL(
|
|
|
|
"id": thread_id,
|
|
|
|
doc_id="1386147188135407",
|
|
|
|
"message_limit": limit,
|
|
|
|
params={
|
|
|
|
"load_messages": True,
|
|
|
|
"id": thread_id,
|
|
|
|
"load_read_receipts": True,
|
|
|
|
"message_limit": limit,
|
|
|
|
"before": before,
|
|
|
|
"load_messages": True,
|
|
|
|
}
|
|
|
|
"load_read_receipts": True,
|
|
|
|
j = self.graphql_request(GraphQL(doc_id="1860982147341344", params=params))
|
|
|
|
"before": before,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if j.get("message_thread") is None:
|
|
|
|
if j.get("message_thread") is None:
|
|
|
|
raise FBchatException("Could not fetch thread {}: {}".format(thread_id, j))
|
|
|
|
raise FBchatException("Could not fetch thread {}: {}".format(thread_id, j))
|
|
|
|
|
|
|
|
|
|
|
|
messages = list(
|
|
|
|
messages = [
|
|
|
|
reversed(
|
|
|
|
Message._from_graphql(message)
|
|
|
|
[
|
|
|
|
for message in j["message_thread"]["messages"]["nodes"]
|
|
|
|
Message._from_graphql(message)
|
|
|
|
]
|
|
|
|
for message in j["message_thread"]["messages"]["nodes"]
|
|
|
|
messages.reverse()
|
|
|
|
]
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
read_receipts = j["message_thread"]["read_receipts"]["nodes"]
|
|
|
|
read_receipts = j["message_thread"]["read_receipts"]["nodes"]
|
|
|
|
|
|
|
|
|
|
|
|
for message in messages:
|
|
|
|
for message in messages:
|
|
|
@@ -1118,10 +1070,11 @@ class Client(object):
|
|
|
|
:rtype: list
|
|
|
|
:rtype: list
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
if offset is not None:
|
|
|
|
if offset is not None:
|
|
|
|
log.warning(
|
|
|
|
log.warning(
|
|
|
|
"Using `offset` in `fetchThreadList` is no longer supported, since Facebook migrated to the use of GraphQL in this request. Use `before` instead"
|
|
|
|
"Using `offset` in `fetchThreadList` is no longer supported, "
|
|
|
|
|
|
|
|
"since Facebook migrated to the use of GraphQL in this request. "
|
|
|
|
|
|
|
|
"Use `before` instead."
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if limit > 20 or limit < 1:
|
|
|
|
if limit > 20 or limit < 1:
|
|
|
@@ -1132,18 +1085,14 @@ class Client(object):
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
raise FBchatUserError('"thread_location" must be a value of ThreadLocation')
|
|
|
|
raise FBchatUserError('"thread_location" must be a value of ThreadLocation')
|
|
|
|
|
|
|
|
|
|
|
|
j = self.graphql_request(
|
|
|
|
params = {
|
|
|
|
GraphQL(
|
|
|
|
"limit": limit,
|
|
|
|
doc_id="1349387578499440",
|
|
|
|
"tags": [loc_str],
|
|
|
|
params={
|
|
|
|
"before": before,
|
|
|
|
"limit": limit,
|
|
|
|
"includeDeliveryReceipts": True,
|
|
|
|
"tags": [loc_str],
|
|
|
|
"includeSeqID": False,
|
|
|
|
"before": before,
|
|
|
|
}
|
|
|
|
"includeDeliveryReceipts": True,
|
|
|
|
j = self.graphql_request(GraphQL(doc_id="1349387578499440", params=params))
|
|
|
|
"includeSeqID": False,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rtn = []
|
|
|
|
rtn = []
|
|
|
|
for node in j["viewer"]["message_threads"]["nodes"]:
|
|
|
|
for node in j["viewer"]["message_threads"]["nodes"]:
|
|
|
@@ -1172,13 +1121,11 @@ class Client(object):
|
|
|
|
"last_action_timestamp": now() - 60 * 1000
|
|
|
|
"last_action_timestamp": now() - 60 * 1000
|
|
|
|
# 'last_action_timestamp': 0
|
|
|
|
# 'last_action_timestamp': 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(
|
|
|
|
j = self._post(
|
|
|
|
self.req_url.UNREAD_THREADS, form, fix_request=True, as_json=True
|
|
|
|
self.req_url.UNREAD_THREADS, form, fix_request=True, as_json=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
payload = j["payload"]["unread_thread_fbids"][0]
|
|
|
|
payload = j["payload"]["unread_thread_fbids"][0]
|
|
|
|
|
|
|
|
|
|
|
|
return payload["thread_fbids"] + payload["other_user_fbids"]
|
|
|
|
return payload["thread_fbids"] + payload["other_user_fbids"]
|
|
|
|
|
|
|
|
|
|
|
|
def fetchUnseen(self):
|
|
|
|
def fetchUnseen(self):
|
|
|
@@ -1194,7 +1141,6 @@ class Client(object):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
payload = j["payload"]["unseen_thread_fbids"][0]
|
|
|
|
payload = j["payload"]["unseen_thread_fbids"][0]
|
|
|
|
|
|
|
|
|
|
|
|
return payload["thread_fbids"] + payload["other_user_fbids"]
|
|
|
|
return payload["thread_fbids"] + payload["other_user_fbids"]
|
|
|
|
|
|
|
|
|
|
|
|
def fetchImageUrl(self, image_id):
|
|
|
|
def fetchImageUrl(self, image_id):
|
|
|
@@ -1207,8 +1153,9 @@ class Client(object):
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
image_id = str(image_id)
|
|
|
|
image_id = str(image_id)
|
|
|
|
j = check_request(
|
|
|
|
data = {"photo_id": str(image_id)}
|
|
|
|
self._get(ReqUrl.ATTACHMENT_PHOTO, query={"photo_id": str(image_id)})
|
|
|
|
j = self._get(
|
|
|
|
|
|
|
|
ReqUrl.ATTACHMENT_PHOTO, query=data, fix_request=True, as_json=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
url = get_jsmods_require(j, 3)
|
|
|
|
url = get_jsmods_require(j, 3)
|
|
|
@@ -1239,11 +1186,9 @@ class Client(object):
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
data = {"question_id": poll_id}
|
|
|
|
data = {"question_id": poll_id}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(
|
|
|
|
j = self._post(
|
|
|
|
self.req_url.GET_POLL_OPTIONS, data, fix_request=True, as_json=True
|
|
|
|
self.req_url.GET_POLL_OPTIONS, data, fix_request=True, as_json=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return [PollOption._from_graphql(m) for m in j["payload"]]
|
|
|
|
return [PollOption._from_graphql(m) for m in j["payload"]]
|
|
|
|
|
|
|
|
|
|
|
|
def fetchPlanInfo(self, plan_id):
|
|
|
|
def fetchPlanInfo(self, plan_id):
|
|
|
@@ -1316,7 +1261,7 @@ class Client(object):
|
|
|
|
timestamp = now()
|
|
|
|
timestamp = now()
|
|
|
|
data = {
|
|
|
|
data = {
|
|
|
|
"client": self.client,
|
|
|
|
"client": self.client,
|
|
|
|
"author": "fbid:" + str(self.uid),
|
|
|
|
"author": "fbid:{}".format(self.uid),
|
|
|
|
"timestamp": timestamp,
|
|
|
|
"timestamp": timestamp,
|
|
|
|
"source": "source:chat:web",
|
|
|
|
"source": "source:chat:web",
|
|
|
|
"offline_threading_id": messageAndOTID,
|
|
|
|
"offline_threading_id": messageAndOTID,
|
|
|
@@ -1374,6 +1319,9 @@ class Client(object):
|
|
|
|
xmd["quick_replies"] = xmd["quick_replies"][0]
|
|
|
|
xmd["quick_replies"] = xmd["quick_replies"][0]
|
|
|
|
data["platform_xmd"] = json.dumps(xmd)
|
|
|
|
data["platform_xmd"] = json.dumps(xmd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if message.reply_to_id:
|
|
|
|
|
|
|
|
data["replied_to_message_id"] = message.reply_to_id
|
|
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
def _doSendRequest(self, data, get_thread_id=False):
|
|
|
|
def _doSendRequest(self, data, get_thread_id=False):
|
|
|
@@ -1399,9 +1347,8 @@ class Client(object):
|
|
|
|
return message_ids[0][0]
|
|
|
|
return message_ids[0][0]
|
|
|
|
except (KeyError, IndexError, TypeError) as e:
|
|
|
|
except (KeyError, IndexError, TypeError) as e:
|
|
|
|
raise FBchatException(
|
|
|
|
raise FBchatException(
|
|
|
|
"Error when sending message: No message IDs could be found: {}".format(
|
|
|
|
"Error when sending message: "
|
|
|
|
j
|
|
|
|
"No message IDs could be found: {}".format(j)
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def send(self, message, thread_id=None, thread_type=ThreadType.USER):
|
|
|
|
def send(self, message, thread_id=None, thread_type=ThreadType.USER):
|
|
|
@@ -1420,7 +1367,6 @@ class Client(object):
|
|
|
|
data = self._getSendData(
|
|
|
|
data = self._getSendData(
|
|
|
|
message=message, thread_id=thread_id, thread_type=thread_type
|
|
|
|
message=message, thread_id=thread_id, thread_type=thread_type
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return self._doSendRequest(data)
|
|
|
|
return self._doSendRequest(data)
|
|
|
|
|
|
|
|
|
|
|
|
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
|
|
|
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
|
|
@@ -1713,19 +1659,15 @@ class Client(object):
|
|
|
|
Deprecated. Use :func:`fbchat.Client._sendFiles` instead
|
|
|
|
Deprecated. Use :func:`fbchat.Client._sendFiles` instead
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if is_gif:
|
|
|
|
if is_gif:
|
|
|
|
return self._sendFiles(
|
|
|
|
mimetype = "image/gif"
|
|
|
|
files=[(image_id, "image/png")],
|
|
|
|
|
|
|
|
message=message,
|
|
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
|
|
thread_type=thread_type,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
return self._sendFiles(
|
|
|
|
mimetype = "image/png"
|
|
|
|
files=[(image_id, "image/gif")],
|
|
|
|
return self._sendFiles(
|
|
|
|
message=message,
|
|
|
|
files=[(image_id, mimetype)],
|
|
|
|
thread_id=thread_id,
|
|
|
|
message=message,
|
|
|
|
thread_type=thread_type,
|
|
|
|
thread_id=thread_id,
|
|
|
|
)
|
|
|
|
thread_type=thread_type,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def sendRemoteImage(
|
|
|
|
def sendRemoteImage(
|
|
|
|
self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER
|
|
|
|
self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER
|
|
|
@@ -1801,8 +1743,8 @@ class Client(object):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
data[
|
|
|
|
data[
|
|
|
|
"log_message_data[added_participants][" + str(i) + "]"
|
|
|
|
"log_message_data[added_participants][{}]".format(i)
|
|
|
|
] = "fbid:" + str(user_id)
|
|
|
|
] = "fbid:{}".format(user_id)
|
|
|
|
|
|
|
|
|
|
|
|
return self._doSendRequest(data)
|
|
|
|
return self._doSendRequest(data)
|
|
|
|
|
|
|
|
|
|
|
@@ -1814,11 +1756,9 @@ class Client(object):
|
|
|
|
:param thread_id: Group ID to remove people from. See :ref:`intro_threads`
|
|
|
|
:param thread_id: Group ID to remove people from. See :ref:`intro_threads`
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
|
|
|
|
|
|
|
|
data = {"uid": user_id, "tid": thread_id}
|
|
|
|
data = {"uid": user_id, "tid": thread_id}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(self.req_url.REMOVE_USER, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(self.req_url.REMOVE_USER, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
def _adminStatus(self, admin_ids, admin, thread_id=None):
|
|
|
|
def _adminStatus(self, admin_ids, admin, thread_id=None):
|
|
|
@@ -1829,7 +1769,7 @@ class Client(object):
|
|
|
|
admin_ids = require_list(admin_ids)
|
|
|
|
admin_ids = require_list(admin_ids)
|
|
|
|
|
|
|
|
|
|
|
|
for i, admin_id in enumerate(admin_ids):
|
|
|
|
for i, admin_id in enumerate(admin_ids):
|
|
|
|
data["admin_ids[" + str(i) + "]"] = str(admin_id)
|
|
|
|
data["admin_ids[{}]".format(i)] = str(admin_id)
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(self.req_url.SAVE_ADMINS, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(self.req_url.SAVE_ADMINS, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
@@ -1864,7 +1804,6 @@ class Client(object):
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
|
|
|
|
|
|
|
|
data = {"set_mode": int(require_admin_approval), "thread_fbid": thread_id}
|
|
|
|
data = {"set_mode": int(require_admin_approval), "thread_fbid": thread_id}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(self.req_url.APPROVAL_MODE, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(self.req_url.APPROVAL_MODE, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
def _usersApproval(self, user_ids, approve, thread_id=None):
|
|
|
|
def _usersApproval(self, user_ids, approve, thread_id=None):
|
|
|
@@ -1872,20 +1811,16 @@ class Client(object):
|
|
|
|
|
|
|
|
|
|
|
|
user_ids = list(require_list(user_ids))
|
|
|
|
user_ids = list(require_list(user_ids))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
|
|
|
"client_mutation_id": "0",
|
|
|
|
|
|
|
|
"actor_id": self.uid,
|
|
|
|
|
|
|
|
"thread_fbid": thread_id,
|
|
|
|
|
|
|
|
"user_ids": user_ids,
|
|
|
|
|
|
|
|
"response": "ACCEPT" if approve else "DENY",
|
|
|
|
|
|
|
|
"surface": "ADMIN_MODEL_APPROVAL_CENTER",
|
|
|
|
|
|
|
|
}
|
|
|
|
j = self.graphql_request(
|
|
|
|
j = self.graphql_request(
|
|
|
|
GraphQL(
|
|
|
|
GraphQL(doc_id="1574519202665847", params={"data": data})
|
|
|
|
doc_id="1574519202665847",
|
|
|
|
|
|
|
|
params={
|
|
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
|
|
"client_mutation_id": "0",
|
|
|
|
|
|
|
|
"actor_id": self.uid,
|
|
|
|
|
|
|
|
"thread_fbid": thread_id,
|
|
|
|
|
|
|
|
"user_ids": user_ids,
|
|
|
|
|
|
|
|
"response": "ACCEPT" if approve else "DENY",
|
|
|
|
|
|
|
|
"surface": "ADMIN_MODEL_APPROVAL_CENTER",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def acceptUsersToGroup(self, user_ids, thread_id=None):
|
|
|
|
def acceptUsersToGroup(self, user_ids, thread_id=None):
|
|
|
@@ -1916,7 +1851,6 @@ class Client(object):
|
|
|
|
:param thread_id: User/Group ID to change image. See :ref:`intro_threads`
|
|
|
|
:param thread_id: User/Group ID to change image. See :ref:`intro_threads`
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
|
|
|
|
|
|
|
|
data = {"thread_image_id": image_id, "thread_id": thread_id}
|
|
|
|
data = {"thread_image_id": image_id, "thread_id": thread_id}
|
|
|
@@ -1932,7 +1866,6 @@ class Client(object):
|
|
|
|
:param thread_id: User/Group ID to change image. See :ref:`intro_threads`
|
|
|
|
:param thread_id: User/Group ID to change image. See :ref:`intro_threads`
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
(image_id, mimetype), = self._upload(get_files_from_urls([image_url]))
|
|
|
|
(image_id, mimetype), = self._upload(get_files_from_urls([image_url]))
|
|
|
|
return self._changeGroupImage(image_id, thread_id)
|
|
|
|
return self._changeGroupImage(image_id, thread_id)
|
|
|
|
|
|
|
|
|
|
|
@@ -1944,7 +1877,6 @@ class Client(object):
|
|
|
|
:param thread_id: User/Group ID to change image. See :ref:`intro_threads`
|
|
|
|
:param thread_id: User/Group ID to change image. See :ref:`intro_threads`
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
with get_files_from_paths([image_path]) as files:
|
|
|
|
with get_files_from_paths([image_path]) as files:
|
|
|
|
(image_id, mimetype), = self._upload(files)
|
|
|
|
(image_id, mimetype), = self._upload(files)
|
|
|
|
|
|
|
|
|
|
|
@@ -1961,7 +1893,6 @@ class Client(object):
|
|
|
|
:type thread_type: models.ThreadType
|
|
|
|
:type thread_type: models.ThreadType
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, thread_type)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, thread_type)
|
|
|
|
|
|
|
|
|
|
|
|
if thread_type == ThreadType.USER:
|
|
|
|
if thread_type == ThreadType.USER:
|
|
|
@@ -1971,7 +1902,6 @@ class Client(object):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
data = {"thread_name": title, "thread_id": thread_id}
|
|
|
|
data = {"thread_name": title, "thread_id": thread_id}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(self.req_url.THREAD_NAME, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(self.req_url.THREAD_NAME, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
def changeNickname(
|
|
|
|
def changeNickname(
|
|
|
@@ -1994,7 +1924,6 @@ class Client(object):
|
|
|
|
"participant_id": user_id,
|
|
|
|
"participant_id": user_id,
|
|
|
|
"thread_or_other_fbid": thread_id,
|
|
|
|
"thread_or_other_fbid": thread_id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(
|
|
|
|
j = self._post(
|
|
|
|
self.req_url.THREAD_NICKNAME, data, fix_request=True, as_json=True
|
|
|
|
self.req_url.THREAD_NICKNAME, data, fix_request=True, as_json=True
|
|
|
|
)
|
|
|
|
)
|
|
|
@@ -2014,7 +1943,6 @@ class Client(object):
|
|
|
|
"color_choice": color.value if color != ThreadColor.MESSENGER_BLUE else "",
|
|
|
|
"color_choice": color.value if color != ThreadColor.MESSENGER_BLUE else "",
|
|
|
|
"thread_or_other_fbid": thread_id,
|
|
|
|
"thread_or_other_fbid": thread_id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(self.req_url.THREAD_COLOR, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(self.req_url.THREAD_COLOR, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
def changeThreadEmoji(self, emoji, thread_id=None):
|
|
|
|
def changeThreadEmoji(self, emoji, thread_id=None):
|
|
|
@@ -2030,7 +1958,6 @@ class Client(object):
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
|
|
|
|
|
|
|
|
data = {"emoji_choice": emoji, "thread_or_other_fbid": thread_id}
|
|
|
|
data = {"emoji_choice": emoji, "thread_or_other_fbid": thread_id}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(self.req_url.THREAD_EMOJI, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(self.req_url.THREAD_EMOJI, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
def reactToMessage(self, message_id, reaction):
|
|
|
|
def reactToMessage(self, message_id, reaction):
|
|
|
@@ -2043,19 +1970,13 @@ class Client(object):
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
data = {
|
|
|
|
data = {
|
|
|
|
"doc_id": 1491398900900362,
|
|
|
|
"action": "ADD_REACTION" if reaction else "REMOVE_REACTION",
|
|
|
|
"variables": json.dumps(
|
|
|
|
"client_mutation_id": "1",
|
|
|
|
{
|
|
|
|
"actor_id": self.uid,
|
|
|
|
"data": {
|
|
|
|
"message_id": str(message_id),
|
|
|
|
"action": "ADD_REACTION" if reaction else "REMOVE_REACTION",
|
|
|
|
"reaction": reaction.value if reaction else None,
|
|
|
|
"client_mutation_id": "1",
|
|
|
|
|
|
|
|
"actor_id": self.uid,
|
|
|
|
|
|
|
|
"message_id": str(message_id),
|
|
|
|
|
|
|
|
"reaction": reaction.value if reaction else None,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
data = {"doc_id": 1491398900900362, "variables": json.dumps({"data": data})}
|
|
|
|
self._post(self.req_url.MESSAGE_REACTION, data, fix_request=True, as_json=True)
|
|
|
|
self._post(self.req_url.MESSAGE_REACTION, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
def createPlan(self, plan, thread_id=None):
|
|
|
|
def createPlan(self, plan, thread_id=None):
|
|
|
@@ -2069,23 +1990,16 @@ class Client(object):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
|
|
|
|
|
|
|
|
full_data = {
|
|
|
|
data = {
|
|
|
|
"event_type": "EVENT",
|
|
|
|
"event_type": "EVENT",
|
|
|
|
"event_time": plan.time,
|
|
|
|
"event_time": plan.time,
|
|
|
|
"title": plan.title,
|
|
|
|
"title": plan.title,
|
|
|
|
"thread_id": thread_id,
|
|
|
|
"thread_id": thread_id,
|
|
|
|
"location_id": plan.location_id or "",
|
|
|
|
"location_id": plan.location_id or "",
|
|
|
|
"location_name": plan.location or "",
|
|
|
|
"location_name": plan.location or "",
|
|
|
|
"acontext": {
|
|
|
|
"acontext": ACONTEXT,
|
|
|
|
"action_history": [
|
|
|
|
|
|
|
|
{"surface": "messenger_chat_tab", "mechanism": "messenger_composer"}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
j = self._post(self.req_url.PLAN_CREATE, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(
|
|
|
|
|
|
|
|
self.req_url.PLAN_CREATE, full_data, fix_request=True, as_json=True
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def editPlan(self, plan, new_plan):
|
|
|
|
def editPlan(self, plan, new_plan):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2096,23 +2010,16 @@ class Client(object):
|
|
|
|
:type plan: models.Plan
|
|
|
|
:type plan: models.Plan
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
full_data = {
|
|
|
|
data = {
|
|
|
|
"event_reminder_id": plan.uid,
|
|
|
|
"event_reminder_id": plan.uid,
|
|
|
|
"delete": "false",
|
|
|
|
"delete": "false",
|
|
|
|
"date": new_plan.time,
|
|
|
|
"date": new_plan.time,
|
|
|
|
"location_name": new_plan.location or "",
|
|
|
|
"location_name": new_plan.location or "",
|
|
|
|
"location_id": new_plan.location_id or "",
|
|
|
|
"location_id": new_plan.location_id or "",
|
|
|
|
"title": new_plan.title,
|
|
|
|
"title": new_plan.title,
|
|
|
|
"acontext": {
|
|
|
|
"acontext": ACONTEXT,
|
|
|
|
"action_history": [
|
|
|
|
|
|
|
|
{"surface": "messenger_chat_tab", "mechanism": "reminder_banner"}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
j = self._post(self.req_url.PLAN_CHANGE, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(
|
|
|
|
|
|
|
|
self.req_url.PLAN_CHANGE, full_data, fix_request=True, as_json=True
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def deletePlan(self, plan):
|
|
|
|
def deletePlan(self, plan):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2121,19 +2028,8 @@ class Client(object):
|
|
|
|
:param plan: Plan to delete
|
|
|
|
:param plan: Plan to delete
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
full_data = {
|
|
|
|
data = {"event_reminder_id": plan.uid, "delete": "true", "acontext": ACONTEXT}
|
|
|
|
"event_reminder_id": plan.uid,
|
|
|
|
j = self._post(self.req_url.PLAN_CHANGE, data, fix_request=True, as_json=True)
|
|
|
|
"delete": "true",
|
|
|
|
|
|
|
|
"acontext": {
|
|
|
|
|
|
|
|
"action_history": [
|
|
|
|
|
|
|
|
{"surface": "messenger_chat_tab", "mechanism": "reminder_banner"}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(
|
|
|
|
|
|
|
|
self.req_url.PLAN_CHANGE, full_data, fix_request=True, as_json=True
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def changePlanParticipation(self, plan, take_part=True):
|
|
|
|
def changePlanParticipation(self, plan, take_part=True):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2143,30 +2039,21 @@ class Client(object):
|
|
|
|
:param take_part: Whether to take part in the plan
|
|
|
|
:param take_part: Whether to take part in the plan
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
:raises: FBchatException if request failed
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
full_data = {
|
|
|
|
data = {
|
|
|
|
"event_reminder_id": plan.uid,
|
|
|
|
"event_reminder_id": plan.uid,
|
|
|
|
"guest_state": "GOING" if take_part else "DECLINED",
|
|
|
|
"guest_state": "GOING" if take_part else "DECLINED",
|
|
|
|
"acontext": {
|
|
|
|
"acontext": ACONTEXT,
|
|
|
|
"action_history": [
|
|
|
|
|
|
|
|
{"surface": "messenger_chat_tab", "mechanism": "reminder_banner"}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(
|
|
|
|
j = self._post(
|
|
|
|
self.req_url.PLAN_PARTICIPATION, full_data, fix_request=True, as_json=True
|
|
|
|
self.req_url.PLAN_PARTICIPATION, data, fix_request=True, as_json=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def eventReminder(self, thread_id, time, title, location="", location_id=""):
|
|
|
|
def eventReminder(self, thread_id, time, title, location="", location_id=""):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Deprecated. Use :func:`fbchat.Client.createPlan` instead
|
|
|
|
Deprecated. Use :func:`fbchat.Client.createPlan` instead
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
self.createPlan(
|
|
|
|
plan = Plan(time=time, title=title, location=location, location_id=location_id)
|
|
|
|
plan=Plan(
|
|
|
|
self.createPlan(plan=plan, thread_id=thread_id)
|
|
|
|
time=time, title=title, location=location, location_id=location_id
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def createPoll(self, poll, thread_id=None):
|
|
|
|
def createPoll(self, poll, thread_id=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2235,7 +2122,6 @@ class Client(object):
|
|
|
|
"to": thread_id if thread_type == ThreadType.USER else "",
|
|
|
|
"to": thread_id if thread_type == ThreadType.USER else "",
|
|
|
|
"source": "mercury-chat",
|
|
|
|
"source": "mercury-chat",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
j = self._post(self.req_url.TYPING, data, fix_request=True, as_json=True)
|
|
|
|
j = self._post(self.req_url.TYPING, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2436,8 +2322,7 @@ class Client(object):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
data = {"mute_settings": str(mute_time), "thread_fbid": thread_id}
|
|
|
|
data = {"mute_settings": str(mute_time), "thread_fbid": thread_id}
|
|
|
|
r = self._post(self.req_url.MUTE_THREAD, data)
|
|
|
|
content = self._post(self.req_url.MUTE_THREAD, data, fix_request=True)
|
|
|
|
r.raise_for_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unmuteThread(self, thread_id=None):
|
|
|
|
def unmuteThread(self, thread_id=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2456,8 +2341,7 @@ class Client(object):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
data = {"reactions_mute_mode": int(mute), "thread_fbid": thread_id}
|
|
|
|
data = {"reactions_mute_mode": int(mute), "thread_fbid": thread_id}
|
|
|
|
r = self._post(self.req_url.MUTE_REACTIONS, data)
|
|
|
|
r = self._post(self.req_url.MUTE_REACTIONS, data, fix_request=True)
|
|
|
|
r.raise_for_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unmuteThreadReactions(self, thread_id=None):
|
|
|
|
def unmuteThreadReactions(self, thread_id=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2476,8 +2360,7 @@ class Client(object):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
thread_id, thread_type = self._getThread(thread_id, None)
|
|
|
|
data = {"mentions_mute_mode": int(mute), "thread_fbid": thread_id}
|
|
|
|
data = {"mentions_mute_mode": int(mute), "thread_fbid": thread_id}
|
|
|
|
r = self._post(self.req_url.MUTE_MENTIONS, data)
|
|
|
|
r = self._post(self.req_url.MUTE_MENTIONS, data, fix_request=True)
|
|
|
|
r.raise_for_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unmuteThreadMentions(self, thread_id=None):
|
|
|
|
def unmuteThreadMentions(self, thread_id=None):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
@@ -2507,7 +2390,6 @@ class Client(object):
|
|
|
|
|
|
|
|
|
|
|
|
def _pullMessage(self):
|
|
|
|
def _pullMessage(self):
|
|
|
|
"""Call pull api with seq value to get message data."""
|
|
|
|
"""Call pull api with seq value to get message data."""
|
|
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
data = {
|
|
|
|
"msgs_recv": 0,
|
|
|
|
"msgs_recv": 0,
|
|
|
|
"sticky_token": self.sticky,
|
|
|
|
"sticky_token": self.sticky,
|
|
|
@@ -2515,11 +2397,7 @@ class Client(object):
|
|
|
|
"clientid": self.client_id,
|
|
|
|
"clientid": self.client_id,
|
|
|
|
"state": "active" if self._markAlive else "offline",
|
|
|
|
"state": "active" if self._markAlive else "offline",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self._get(self.req_url.STICKY, data, fix_request=True, as_json=True)
|
|
|
|
j = self._get(self.req_url.STICKY, data, fix_request=True, as_json=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.seq = j.get("seq", "0")
|
|
|
|
|
|
|
|
return j
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parseDelta(self, m):
|
|
|
|
def _parseDelta(self, m):
|
|
|
|
def getThreadIdAndThreadType(msg_metadata):
|
|
|
|
def getThreadIdAndThreadType(msg_metadata):
|
|
|
@@ -3027,6 +2905,24 @@ class Client(object):
|
|
|
|
msg=m,
|
|
|
|
msg=m,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elif d.get("deltaMessageReply"):
|
|
|
|
|
|
|
|
i = d["deltaMessageReply"]
|
|
|
|
|
|
|
|
metadata = i["message"]["messageMetadata"]
|
|
|
|
|
|
|
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
|
|
|
|
|
|
|
message = Message._from_reply(i["message"])
|
|
|
|
|
|
|
|
message.replied_to = Message._from_reply(i["repliedToMessage"])
|
|
|
|
|
|
|
|
self.onMessage(
|
|
|
|
|
|
|
|
mid=message.uid,
|
|
|
|
|
|
|
|
author_id=message.author,
|
|
|
|
|
|
|
|
message=message.text,
|
|
|
|
|
|
|
|
message_object=message,
|
|
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
|
|
thread_type=thread_type,
|
|
|
|
|
|
|
|
ts=message.timestamp,
|
|
|
|
|
|
|
|
metadata=metadata,
|
|
|
|
|
|
|
|
msg=m,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# New message
|
|
|
|
# New message
|
|
|
|
elif delta.get("class") == "NewMessage":
|
|
|
|
elif delta.get("class") == "NewMessage":
|
|
|
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
|
|
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
|
|
@@ -3050,6 +2946,7 @@ class Client(object):
|
|
|
|
|
|
|
|
|
|
|
|
def _parseMessage(self, content):
|
|
|
|
def _parseMessage(self, content):
|
|
|
|
"""Get message and author name from content. May contain multiple messages in the content."""
|
|
|
|
"""Get message and author name from content. May contain multiple messages in the content."""
|
|
|
|
|
|
|
|
self.seq = content.get("seq", "0")
|
|
|
|
|
|
|
|
|
|
|
|
if "lb_info" in content:
|
|
|
|
if "lb_info" in content:
|
|
|
|
self.sticky = content["lb_info"]["sticky"]
|
|
|
|
self.sticky = content["lb_info"]["sticky"]
|
|
|
|