Update facebook-sdk-3.1.0

This commit is contained in:
JonnyWong16 2021-10-15 00:05:44 -07:00
parent 586f033347
commit 7dd3963bf2
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
2 changed files with 103 additions and 71 deletions

View file

@ -44,10 +44,10 @@ from . import version
__version__ = version.__version__ __version__ = version.__version__
FACEBOOK_GRAPH_URL = "https://graph.facebook.com/" FACEBOOK_GRAPH_URL = "https://graph.facebook.com/"
FACEBOOK_OAUTH_DIALOG_URL = "https://www.facebook.com/dialog/oauth?" FACEBOOK_WWW_URL = "https://www.facebook.com/"
VALID_API_VERSIONS = [ FACEBOOK_OAUTH_DIALOG_PATH = "dialog/oauth?"
"2.5", "2.6", "2.7", "2.8", "2.9", "2.10", "2.11", "2.12"] VALID_API_VERSIONS = ["2.8", "2.9", "2.10", "2.11", "2.12", "3.0", "3.1"]
VALID_SEARCH_TYPES = ["page", "event", "group", "place", "placetopic", "user"] VALID_SEARCH_TYPES = ["place", "placetopic"]
class GraphAPI(object): class GraphAPI(object):
@ -79,8 +79,14 @@ class GraphAPI(object):
""" """
def __init__(self, access_token=None, timeout=None, version=None, def __init__(
proxies=None, session=None): self,
access_token=None,
timeout=None,
version=None,
proxies=None,
session=None,
):
# The default version is only used if the version kwarg does not exist. # The default version is only used if the version kwarg does not exist.
default_version = VALID_API_VERSIONS[0] default_version = VALID_API_VERSIONS[0]
@ -94,13 +100,17 @@ class GraphAPI(object):
match = version_regex.search(str(version)) match = version_regex.search(str(version))
if match is not None: if match is not None:
if str(version) not in VALID_API_VERSIONS: if str(version) not in VALID_API_VERSIONS:
raise GraphAPIError("Valid API versions are " + raise GraphAPIError(
str(VALID_API_VERSIONS).strip('[]')) "Valid API versions are "
+ str(VALID_API_VERSIONS).strip("[]")
)
else: else:
self.version = "v" + str(version) self.version = "v" + str(version)
else: else:
raise GraphAPIError("Version number should be in the" raise GraphAPIError(
" following format: #.# (e.g. 2.0).") "Version number should be in the"
" following format: #.# (e.g. 2.0)."
)
else: else:
self.version = "v" + default_version self.version = "v" + default_version
@ -108,7 +118,7 @@ class GraphAPI(object):
"""Fetches the permissions object from the graph.""" """Fetches the permissions object from the graph."""
response = self.request( response = self.request(
"{0}/{1}/permissions".format(self.version, user_id), {} "{0}/{1}/permissions".format(self.version, user_id), {}
)["data"] )["data"]
return {x["permission"] for x in response if x["status"] == "granted"} return {x["permission"] for x in response if x["status"] == "granted"}
def get_object(self, id, **args): def get_object(self, id, **args):
@ -125,14 +135,11 @@ class GraphAPI(object):
return self.request(self.version + "/", args) return self.request(self.version + "/", args)
def search(self, type, **args): def search(self, type, **args):
"""Fetches all objects of a given type from the graph. """https://developers.facebook.com/docs/places/search"""
Returns all objects of a given type from the graph as a dict.
"""
if type not in VALID_SEARCH_TYPES: if type not in VALID_SEARCH_TYPES:
raise GraphAPIError('Valid types are: %s' raise GraphAPIError(
% ', '.join(VALID_SEARCH_TYPES)) "Valid types are: %s" % ", ".join(VALID_SEARCH_TYPES)
)
args["type"] = type args["type"] = type
return self.request(self.version + "/search/", args) return self.request(self.version + "/search/", args)
@ -140,7 +147,8 @@ class GraphAPI(object):
def get_connections(self, id, connection_name, **args): def get_connections(self, id, connection_name, **args):
"""Fetches the connections for given object.""" """Fetches the connections for given object."""
return self.request( return self.request(
"{0}/{1}/{2}".format(self.version, id, connection_name), args) "{0}/{1}/{2}".format(self.version, id, connection_name), args
)
def get_all_connections(self, id, connection_name, **args): def get_all_connections(self, id, connection_name, **args):
"""Get all pages from a get_connections call """Get all pages from a get_connections call
@ -150,13 +158,13 @@ class GraphAPI(object):
""" """
while True: while True:
page = self.get_connections(id, connection_name, **args) page = self.get_connections(id, connection_name, **args)
for post in page['data']: for post in page["data"]:
yield post yield post
next = page.get('paging', {}).get('next') next = page.get("paging", {}).get("next")
if not next: if not next:
return return
args = parse_qs(urlparse(next).query) args = parse_qs(urlparse(next).query)
del args['access_token'] del args["access_token"]
def put_object(self, parent_object, connection_name, **data): def put_object(self, parent_object, connection_name, **data):
"""Writes the given object to the graph, connected to the given parent. """Writes the given object to the graph, connected to the given parent.
@ -181,7 +189,8 @@ class GraphAPI(object):
return self.request( return self.request(
"{0}/{1}/{2}".format(self.version, parent_object, connection_name), "{0}/{1}/{2}".format(self.version, parent_object, connection_name),
post_args=data, post_args=data,
method="POST") method="POST",
)
def put_comment(self, object_id, message): def put_comment(self, object_id, message):
"""Writes the given comment on the given post.""" """Writes the given comment on the given post."""
@ -193,11 +202,15 @@ class GraphAPI(object):
def delete_object(self, id): def delete_object(self, id):
"""Deletes the object with the given ID from the graph.""" """Deletes the object with the given ID from the graph."""
self.request("{0}/{1}".format(self.version, id), method="DELETE") return self.request(
"{0}/{1}".format(self.version, id), method="DELETE"
)
def delete_request(self, user_id, request_id): def delete_request(self, user_id, request_id):
"""Deletes the Request with the given ID for the given user.""" """Deletes the Request with the given ID for the given user."""
self.request("{0}_{1}".format(request_id, user_id), method="DELETE") return self.request(
"{0}_{1}".format(request_id, user_id), method="DELETE"
)
def put_photo(self, image, album_path="me/photos", **kwargs): def put_photo(self, image, album_path="me/photos", **kwargs):
""" """
@ -211,7 +224,8 @@ class GraphAPI(object):
"{0}/{1}".format(self.version, album_path), "{0}/{1}".format(self.version, album_path),
post_args=kwargs, post_args=kwargs,
files={"source": image}, files={"source": image},
method="POST") method="POST",
)
def get_version(self): def get_version(self):
"""Fetches the current version number of the Graph API being used.""" """Fetches the current version number of the Graph API being used."""
@ -222,7 +236,8 @@ class GraphAPI(object):
FACEBOOK_GRAPH_URL + self.version + "/me", FACEBOOK_GRAPH_URL + self.version + "/me",
params=args, params=args,
timeout=self.timeout, timeout=self.timeout,
proxies=self.proxies) proxies=self.proxies,
)
except requests.HTTPError as e: except requests.HTTPError as e:
response = json.loads(e.read()) response = json.loads(e.read())
raise GraphAPIError(response) raise GraphAPIError(response)
@ -235,7 +250,8 @@ class GraphAPI(object):
raise GraphAPIError("API version number not available") raise GraphAPIError("API version number not available")
def request( def request(
self, path, args=None, post_args=None, files=None, method=None): self, path, args=None, post_args=None, files=None, method=None
):
"""Fetches the given path in the Graph API. """Fetches the given path in the Graph API.
We translate args to a valid query string. If post_args is We translate args to a valid query string. If post_args is
@ -266,19 +282,22 @@ class GraphAPI(object):
params=args, params=args,
data=post_args, data=post_args,
proxies=self.proxies, proxies=self.proxies,
files=files) files=files,
)
except requests.HTTPError as e: except requests.HTTPError as e:
response = json.loads(e.read()) response = json.loads(e.read())
raise GraphAPIError(response) raise GraphAPIError(response)
headers = response.headers headers = response.headers
if 'json' in headers['content-type']: if "json" in headers["content-type"]:
result = response.json() result = response.json()
elif 'image/' in headers['content-type']: elif "image/" in headers["content-type"]:
mimetype = headers['content-type'] mimetype = headers["content-type"]
result = {"data": response.content, result = {
"mime-type": mimetype, "data": response.content,
"url": response.url} "mime-type": mimetype,
"url": response.url,
}
elif "access_token" in parse_qs(response.text): elif "access_token" in parse_qs(response.text):
query_str = parse_qs(response.text) query_str = parse_qs(response.text)
if "access_token" in query_str: if "access_token" in query_str:
@ -288,7 +307,7 @@ class GraphAPI(object):
else: else:
raise GraphAPIError(response.json()) raise GraphAPIError(response.json())
else: else:
raise GraphAPIError('Maintype was not text, image, or querystring') raise GraphAPIError("Maintype was not text, image, or querystring")
if result and isinstance(result, dict) and result.get("error"): if result and isinstance(result, dict) and result.get("error"):
raise GraphAPIError(result) raise GraphAPIError(result)
@ -305,15 +324,19 @@ class GraphAPI(object):
if offline: if offline:
return "{0}|{1}".format(app_id, app_secret) return "{0}|{1}".format(app_id, app_secret)
else: else:
args = {'grant_type': 'client_credentials', args = {
'client_id': app_id, "grant_type": "client_credentials",
'client_secret': app_secret} "client_id": app_id,
"client_secret": app_secret,
}
return self.request("{0}/oauth/access_token".format(self.version), return self.request(
args=args)["access_token"] "{0}/oauth/access_token".format(self.version), args=args
)["access_token"]
def get_access_token_from_code( def get_access_token_from_code(
self, code, redirect_uri, app_id, app_secret): self, code, redirect_uri, app_id, app_secret
):
"""Get an access token from the "code" returned from an OAuth dialog. """Get an access token from the "code" returned from an OAuth dialog.
Returns a dict containing the user-specific access token and its Returns a dict containing the user-specific access token and its
@ -324,10 +347,12 @@ class GraphAPI(object):
"code": code, "code": code,
"redirect_uri": redirect_uri, "redirect_uri": redirect_uri,
"client_id": app_id, "client_id": app_id,
"client_secret": app_secret} "client_secret": app_secret,
}
return self.request( return self.request(
"{0}/oauth/access_token".format(self.version), args) "{0}/oauth/access_token".format(self.version), args
)
def extend_access_token(self, app_id, app_secret): def extend_access_token(self, app_id, app_secret):
""" """
@ -343,8 +368,9 @@ class GraphAPI(object):
"fb_exchange_token": self.access_token, "fb_exchange_token": self.access_token,
} }
return self.request("{0}/oauth/access_token".format(self.version), return self.request(
args=args) "{0}/oauth/access_token".format(self.version), args=args
)
def debug_access_token(self, token, app_id, app_secret): def debug_access_token(self, token, app_id, app_secret):
""" """
@ -359,10 +385,22 @@ class GraphAPI(object):
""" """
args = { args = {
"input_token": token, "input_token": token,
"access_token": "{0}|{1}".format(app_id, app_secret) "access_token": "{0}|{1}".format(app_id, app_secret),
} }
return self.request(self.version + "/" + "debug_token", args=args) return self.request(self.version + "/" + "debug_token", args=args)
def get_auth_url(self, app_id, canvas_url, perms=None, **kwargs):
"""Build a URL to create an OAuth dialog."""
url = "{0}{1}/{2}".format(
FACEBOOK_WWW_URL, self.version, FACEBOOK_OAUTH_DIALOG_PATH
)
args = {"client_id": app_id, "redirect_uri": canvas_url}
if perms:
args["scope"] = ",".join(perms)
args.update(kwargs)
return url + urlencode(args)
class GraphAPIError(Exception): class GraphAPIError(Exception):
def __init__(self, result): def __init__(self, result):
@ -417,7 +455,8 @@ def get_user_from_cookie(cookies, app_id, app_secret):
return None return None
try: try:
result = GraphAPI().get_access_token_from_code( result = GraphAPI().get_access_token_from_code(
parsed_request["code"], "", app_id, app_secret) parsed_request["code"], "", app_id, app_secret
)
except GraphAPIError: except GraphAPIError:
return None return None
result["uid"] = parsed_request["user_id"] result["uid"] = parsed_request["user_id"]
@ -435,12 +474,14 @@ def parse_signed_request(signed_request, app_secret):
""" """
try: try:
encoded_sig, payload = map(str, signed_request.split('.', 1)) encoded_sig, payload = map(str, signed_request.split(".", 1))
sig = base64.urlsafe_b64decode(encoded_sig + "=" * sig = base64.urlsafe_b64decode(
((4 - len(encoded_sig) % 4) % 4)) encoded_sig + "=" * ((4 - len(encoded_sig) % 4) % 4)
data = base64.urlsafe_b64decode(payload + "=" * )
((4 - len(payload) % 4) % 4)) data = base64.urlsafe_b64decode(
payload + "=" * ((4 - len(payload) % 4) % 4)
)
except IndexError: except IndexError:
# Signed request was malformed. # Signed request was malformed.
return False return False
@ -451,28 +492,19 @@ def parse_signed_request(signed_request, app_secret):
# Signed request had a corrupted payload. # Signed request had a corrupted payload.
return False return False
data = json.loads(data.decode('ascii')) data = json.loads(data.decode("ascii"))
if data.get('algorithm', '').upper() != 'HMAC-SHA256': if data.get("algorithm", "").upper() != "HMAC-SHA256":
return False return False
# HMAC can only handle ascii (byte) strings # HMAC can only handle ascii (byte) strings
# https://bugs.python.org/issue5285 # https://bugs.python.org/issue5285
app_secret = app_secret.encode('ascii') app_secret = app_secret.encode("ascii")
payload = payload.encode('ascii') payload = payload.encode("ascii")
expected_sig = hmac.new(app_secret, expected_sig = hmac.new(
msg=payload, app_secret, msg=payload, digestmod=hashlib.sha256
digestmod=hashlib.sha256).digest() ).digest()
if sig != expected_sig: if sig != expected_sig:
return False return False
return data return data
def auth_url(app_id, canvas_url, perms=None, **kwargs):
url = FACEBOOK_OAUTH_DIALOG_URL
kvps = {'client_id': app_id, 'redirect_uri': canvas_url}
if perms:
kvps['scope'] = ",".join(perms)
kvps.update(kwargs)
return url + urlencode(kvps)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# Copyright 2015 Mobolic # Copyright 2015-2018 Mobolic
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain # not use this file except in compliance with the License. You may obtain
@ -14,4 +14,4 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__version__ = "3.0.0-alpha" __version__ = "3.1.0"