From cab8abd1a0469c489fa45f2e00a9071267964f9f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 2 Jul 2019 18:21:00 +0200 Subject: [PATCH 1/3] Properly namespace GraphQL utility functions --- fbchat/_client.py | 7 ++++--- fbchat/_graphql.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fbchat/_client.py b/fbchat/_client.py index bfee672..fde5ccc 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -10,7 +10,8 @@ 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 ._graphql import GraphQL from ._state import State import time import json @@ -134,7 +135,7 @@ class Client(object): try: if as_graphql: content = check_request(r, as_json=False) - return graphql_response_to_json(content) + return _graphql.response_to_json(content) else: return check_request(r) except FBchatFacebookError as e: @@ -167,7 +168,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)) diff --git a/fbchat/_graphql.py b/fbchat/_graphql.py index 508d914..7506e29 100644 --- a/fbchat/_graphql.py +++ b/fbchat/_graphql.py @@ -27,7 +27,7 @@ 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 """ @@ -37,7 +37,7 @@ def graphql_queries_to_json(*queries): 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) From 1b2aeb01ceb4924354f61a67fbaf10a9643c9ca3 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 2 Jul 2019 18:23:29 +0200 Subject: [PATCH 2/3] Move GraphQL constants into the module --- fbchat/_client.py | 8 +- fbchat/_graphql.py | 277 +++++++++++++++++++++++---------------------- 2 files changed, 143 insertions(+), 142 deletions(-) diff --git a/fbchat/_client.py b/fbchat/_client.py index fde5ccc..c0582c6 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -436,7 +436,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(query=_graphql.SEARCH_USER, params=params)) return [User._from_graphql(node) for node in j[name]["users"]["nodes"]] @@ -450,7 +450,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(query=_graphql.SEARCH_PAGE, params=params)) return [Page._from_graphql(node) for node in j[name]["pages"]["nodes"]] @@ -465,7 +465,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(query=_graphql.SEARCH_GROUP, params=params)) return [Group._from_graphql(node) for node in j["viewer"]["groups"]["nodes"]] @@ -480,7 +480,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(query=_graphql.SEARCH_THREAD, params=params)) rtn = [] for node in j[name]["threads"]["nodes"]: diff --git a/fbchat/_graphql.py b/fbchat/_graphql.py index 7506e29..79206f3 100644 --- a/fbchat/_graphql.py +++ b/fbchat/_graphql.py @@ -73,160 +73,161 @@ class GraphQL(object): else: raise FBchatUserError("A query or doc_id must be specified") - 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 - } +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 +) From 1293814c3a10e5241bd699bfba8c240b83a24c0a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 2 Jul 2019 18:26:35 +0200 Subject: [PATCH 3/3] Remove GraphQL object in favor of helper functions --- fbchat/_client.py | 25 ++++++++++++------------- fbchat/_graphql.py | 28 ++++++++++++++++------------ 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/fbchat/_client.py b/fbchat/_client.py index c0582c6..f9953c8 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -11,7 +11,6 @@ from collections import OrderedDict from ._util import * from .models import * from . import _graphql -from ._graphql import GraphQL from ._state import State import time import json @@ -158,8 +157,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 @@ -324,7 +323,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): """ @@ -436,7 +435,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"]] @@ -450,7 +449,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"]] @@ -465,7 +464,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"]] @@ -480,7 +479,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"]: @@ -706,7 +705,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) @@ -771,7 +770,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)) @@ -828,7 +827,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"]: @@ -933,7 +932,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): @@ -1570,7 +1569,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 79206f3..dec0049 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 @@ -33,7 +33,7 @@ def queries_to_json(*queries): """ rtn = {} for i, query in enumerate(queries): - rtn["q{}".format(i)] = query.value + rtn["q{}".format(i)] = query return json.dumps(rtn) @@ -62,16 +62,20 @@ def 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} + + +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 = """