diff --git a/fbchat/_client.py b/fbchat/_client.py index afa3815..ed671d0 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -10,7 +10,7 @@ from mimetypes import guess_type from collections import OrderedDict from ._util import * from .models import * -from ._graphql import graphql_queries_to_json, graphql_response_to_json, GraphQL +from . import _graphql from ._state import State import time import json @@ -132,7 +132,7 @@ class Client(object): content = check_request(r) try: if as_graphql: - return graphql_response_to_json(content) + return _graphql.response_to_json(content) else: j = to_json(content) # TODO: Remove this, and move it to _payload_post instead @@ -160,8 +160,8 @@ class Client(object): def graphql_requests(self, *queries): """ - :param queries: Zero or more GraphQL objects - :type queries: GraphQL + :param queries: Zero or more dictionaries + :type queries: dict :raises: FBchatException if request failed :return: A tuple containing json graphql queries @@ -170,7 +170,7 @@ class Client(object): data = { "method": "GET", "response_format": "json", - "queries": graphql_queries_to_json(*queries), + "queries": _graphql.queries_to_json(*queries), } return tuple(self._post("/api/graphqlbatch/", data, as_graphql=True)) @@ -326,7 +326,7 @@ class Client(object): def _forcedFetch(self, thread_id, mid): params = {"thread_and_message_id": {"thread_id": thread_id, "message_id": mid}} - return self.graphql_request(GraphQL(doc_id="1768656253222505", params=params)) + return self.graphql_request(_graphql.from_doc_id("1768656253222505", params)) def fetchThreads(self, thread_location, before=None, after=None, limit=None): """ @@ -438,7 +438,7 @@ class Client(object): :raises: FBchatException if request failed """ params = {"search": name, "limit": limit} - j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_USER, params=params)) + j = self.graphql_request(_graphql.from_query(_graphql.SEARCH_USER, params)) return [User._from_graphql(node) for node in j[name]["users"]["nodes"]] @@ -452,7 +452,7 @@ class Client(object): :raises: FBchatException if request failed """ params = {"search": name, "limit": limit} - j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_PAGE, params=params)) + j = self.graphql_request(_graphql.from_query(_graphql.SEARCH_PAGE, params)) return [Page._from_graphql(node) for node in j[name]["pages"]["nodes"]] @@ -467,7 +467,7 @@ class Client(object): :raises: FBchatException if request failed """ params = {"search": name, "limit": limit} - j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_GROUP, params=params)) + j = self.graphql_request(_graphql.from_query(_graphql.SEARCH_GROUP, params)) return [Group._from_graphql(node) for node in j["viewer"]["groups"]["nodes"]] @@ -482,7 +482,7 @@ class Client(object): :raises: FBchatException if request failed """ params = {"search": name, "limit": limit} - j = self.graphql_request(GraphQL(query=GraphQL.SEARCH_THREAD, params=params)) + j = self.graphql_request(_graphql.from_query(_graphql.SEARCH_THREAD, params)) rtn = [] for node in j[name]["threads"]["nodes"]: @@ -708,7 +708,7 @@ class Client(object): "load_read_receipts": False, "before": None, } - queries.append(GraphQL(doc_id="2147762685294928", params=params)) + queries.append(_graphql.from_doc_id("2147762685294928", params)) j = self.graphql_requests(*queries) @@ -773,7 +773,7 @@ class Client(object): "load_read_receipts": True, "before": before, } - j = self.graphql_request(GraphQL(doc_id="1860982147341344", params=params)) + j = self.graphql_request(_graphql.from_doc_id("1860982147341344", params)) if j.get("message_thread") is None: raise FBchatException("Could not fetch thread {}: {}".format(thread_id, j)) @@ -830,7 +830,7 @@ class Client(object): "includeDeliveryReceipts": True, "includeSeqID": False, } - j = self.graphql_request(GraphQL(doc_id="1349387578499440", params=params)) + j = self.graphql_request(_graphql.from_doc_id("1349387578499440", params)) rtn = [] for node in j["viewer"]["message_threads"]["nodes"]: @@ -935,7 +935,7 @@ class Client(object): return Plan._from_fetch(j) def _getPrivateData(self): - j = self.graphql_request(GraphQL(doc_id="1868889766468115")) + j = self.graphql_request(_graphql.from_doc_id("1868889766468115", {})) return j["viewer"] def getPhoneNumbers(self): @@ -1577,7 +1577,7 @@ class Client(object): "surface": "ADMIN_MODEL_APPROVAL_CENTER", } j = self.graphql_request( - GraphQL(doc_id="1574519202665847", params={"data": data}) + _graphql.from_doc_id("1574519202665847", {"data": data}) ) def acceptUsersToGroup(self, user_ids, thread_id=None): diff --git a/fbchat/_graphql.py b/fbchat/_graphql.py index 50eaea4..375f307 100644 --- a/fbchat/_graphql.py +++ b/fbchat/_graphql.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import json import re from . import _util -from ._exception import FBchatException, FBchatUserError +from ._exception import FBchatException # Shameless copy from https://stackoverflow.com/a/8730674 FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL @@ -27,17 +27,17 @@ class ConcatJSONDecoder(json.JSONDecoder): # End shameless copy -def graphql_queries_to_json(*queries): +def queries_to_json(*queries): """ Queries should be a list of GraphQL objects """ rtn = {} for i, query in enumerate(queries): - rtn["q{}".format(i)] = query.value + rtn["q{}".format(i)] = query return json.dumps(rtn) -def graphql_response_to_json(content): +def response_to_json(content): content = _util.strip_json_cruft(content) # Usually only needed in some error cases try: j = json.loads(content, cls=ConcatJSONDecoder) @@ -62,171 +62,176 @@ def graphql_response_to_json(content): return rtn -class GraphQL(object): - def __init__(self, query=None, doc_id=None, params=None): - if params is None: - params = {} - if query is not None: - self.value = {"priority": 0, "q": query, "query_params": params} - elif doc_id is not None: - self.value = {"doc_id": doc_id, "query_params": params} - else: - raise FBchatUserError("A query or doc_id must be specified") +def from_query(query, params): + return {"priority": 0, "q": query, "query_params": params} - FRAGMENT_USER = """ - QueryFragment User: User { - id, - name, - first_name, - last_name, - profile_picture.width().height() { - uri - }, - is_viewer_friend, - url, - gender, - viewer_affinity - } - """ - FRAGMENT_GROUP = """ - QueryFragment Group: MessageThread { - name, - thread_key { - thread_fbid - }, - image { - uri - }, - is_group_thread, - all_participants { - nodes { - messaging_actor { - id - } +def from_query_id(query_id, params): + return {"query_id": query_id, "query_params": params} + + +def from_doc(doc, params): + return {"doc": doc, "query_params": params} + + +def from_doc_id(doc_id, params): + return {"doc_id": doc_id, "query_params": params} + + +FRAGMENT_USER = """ +QueryFragment User: User { + id, + name, + first_name, + last_name, + profile_picture.width().height() { + uri + }, + is_viewer_friend, + url, + gender, + viewer_affinity +} +""" + +FRAGMENT_GROUP = """ +QueryFragment Group: MessageThread { + name, + thread_key { + thread_fbid + }, + image { + uri + }, + is_group_thread, + all_participants { + nodes { + messaging_actor { + id } + } + }, + customization_info { + participant_customizations { + participant_id, + nickname }, - customization_info { - participant_customizations { - participant_id, - nickname + outgoing_bubble_color, + emoji + }, + thread_admins { + id + }, + group_approval_queue { + nodes { + requester { + id + } + } + }, + approval_mode, + joinable_mode { + mode, + link + }, + event_reminders { + nodes { + id, + lightweight_event_creator { + id }, - outgoing_bubble_color, - emoji - }, - thread_admins { - id - }, - group_approval_queue { + time, + location_name, + event_title, + event_reminder_members { + edges { + node { + id + }, + guest_list_state + } + } + } + } +} +""" + +FRAGMENT_PAGE = """ +QueryFragment Page: Page { + id, + name, + profile_picture.width(32).height(32) { + uri + }, + url, + category_type, + city { + name + } +} +""" + +SEARCH_USER = ( + """ +Query SearchUser( = '', = 10) { + entities_named() { + search_results.of_type(user).first() as users { nodes { - requester { - id - } + @User } - }, - approval_mode, - joinable_mode { - mode, - link - }, - event_reminders { + } + } +} +""" + + FRAGMENT_USER +) + +SEARCH_GROUP = ( + """ +Query SearchGroup( = '', = 10, = 32) { + viewer() { + message_threads.with_thread_name().last() as groups { nodes { - id, - lightweight_event_creator { - id - }, - time, - location_name, - event_title, - event_reminder_members { - edges { - node { - id - }, - guest_list_state - } - } + @Group } } } - """ +} +""" + + FRAGMENT_GROUP +) - FRAGMENT_PAGE = """ - QueryFragment Page: Page { - id, - name, - profile_picture.width(32).height(32) { - uri - }, - url, - category_type, - city { - name - } - } +SEARCH_PAGE = ( """ - - SEARCH_USER = ( - """ - Query SearchUser( = '', = 10) { - entities_named() { - search_results.of_type(user).first() as users { - nodes { - @User - } +Query SearchPage( = '', = 10) { + entities_named() { + search_results.of_type(page).first() as pages { + nodes { + @Page } } } - """ - + FRAGMENT_USER - ) +} +""" + + FRAGMENT_PAGE +) - SEARCH_GROUP = ( - """ - Query SearchGroup( = '', = 10, = 32) { - viewer() { - message_threads.with_thread_name().last() as groups { - nodes { - @Group - } +SEARCH_THREAD = ( + """ +Query SearchThread( = '', = 10) { + entities_named() { + search_results.first() as threads { + nodes { + __typename, + @User, + @Group, + @Page } } } - """ - + FRAGMENT_GROUP - ) - - SEARCH_PAGE = ( - """ - Query SearchPage( = '', = 10) { - entities_named() { - search_results.of_type(page).first() as pages { - nodes { - @Page - } - } - } - } - """ - + FRAGMENT_PAGE - ) - - SEARCH_THREAD = ( - """ - Query SearchThread( = '', = 10) { - entities_named() { - search_results.first() as threads { - nodes { - __typename, - @User, - @Group, - @Page - } - } - } - } - """ - + FRAGMENT_USER - + FRAGMENT_GROUP - + FRAGMENT_PAGE - ) +} +""" + + FRAGMENT_USER + + FRAGMENT_GROUP + + FRAGMENT_PAGE +)