mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 05:01:14 -07:00
Update facebook-sdk-3.1.0
This commit is contained in:
parent
586f033347
commit
7dd3963bf2
2 changed files with 103 additions and 71 deletions
|
@ -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)
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue