From c27c1379d0537f85d7459aebfccc686364013169 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 10 Jul 2017 22:34:50 -0700 Subject: [PATCH] Update Facebook for provider link only * Custom poster/metadata removed in Graph API v2.9 --- lib/facebook/__init__.py | 202 +++++++++++++++++++++------------------ lib/facebook/version.py | 6 +- plexpy/notifiers.py | 106 +++++++++++--------- 3 files changed, 175 insertions(+), 139 deletions(-) diff --git a/lib/facebook/__init__.py b/lib/facebook/__init__.py index cfadcbbb..7c4a214a 100644 --- a/lib/facebook/__init__.py +++ b/lib/facebook/__init__.py @@ -1,12 +1,13 @@ #!/usr/bin/env python # # Copyright 2010 Facebook +# Copyright 2015 Mobolic # # 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 # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -19,8 +20,7 @@ This client library is designed to support the Graph API and the official Facebook JavaScript SDK, which is the canonical way to implement Facebook authentication. Read more about the Graph API at -http://developers.facebook.com/docs/api. You can download the Facebook -JavaScript SDK at http://github.com/facebook/connect-js/. +https://developers.facebook.com/docs/graph-api. """ @@ -33,9 +33,9 @@ import json import re try: - from urllib.parse import parse_qs, urlencode + from urllib.parse import parse_qs, urlencode, urlparse except ImportError: - from urlparse import parse_qs + from urlparse import parse_qs, urlparse from urllib import urlencode from . import version @@ -43,15 +43,16 @@ from . import version __version__ = version.__version__ - -VALID_API_VERSIONS = ["2.0", "2.1", "2.2", "2.3", "2.4", "2.5"] +FACEBOOK_GRAPH_URL = "https://graph.facebook.com/" +FACEBOOK_OAUTH_DIALOG_URL = "https://www.facebook.com/dialog/oauth?" +VALID_API_VERSIONS = ["2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9"] +VALID_SEARCH_TYPES = ["page", "event", "group", "place", "placetopic", "user"] class GraphAPI(object): """A client for the Facebook Graph API. - See http://developers.facebook.com/docs/api for complete - documentation for the API. + https://developers.facebook.com/docs/graph-api The Graph API is made up of the objects in Facebook (e.g., people, pages, events, photos) and the connections between them (e.g., @@ -65,11 +66,11 @@ class GraphAPI(object): friends = graph.get_connections(user["id"], "friends") You can see a list of all of the objects and connections supported - by the API at http://developers.facebook.com/docs/reference/api/. + by the API at https://developers.facebook.com/docs/graph-api/reference/. You can obtain an access token via OAuth or by using the Facebook JavaScript SDK. See - http://developers.facebook.com/docs/authentication/ for details. + https://developers.facebook.com/docs/facebook-login for details. If you are using the JavaScript SDK, you can use the get_user_from_cookie() method below to get the OAuth access token @@ -78,13 +79,14 @@ class GraphAPI(object): """ def __init__(self, access_token=None, timeout=None, version=None, - proxies=None): + proxies=None, session=None): # The default version is only used if the version kwarg does not exist. - default_version = "2.0" + default_version = VALID_API_VERSIONS[0] self.access_token = access_token self.timeout = timeout self.proxies = proxies + self.session = session or requests.Session() if version: version_regex = re.compile("^\d\.\d$") @@ -101,9 +103,16 @@ class GraphAPI(object): else: self.version = "v" + default_version + def get_permissions(self, user_id): + """Fetches the permissions object from the graph.""" + response = self.request( + "{0}/{1}/permissions".format(self.version, user_id), {} + )["data"] + return {x["permission"] for x in response if x["status"] == "granted"} + def get_object(self, id, **args): """Fetches the given object from the graph.""" - return self.request(self.version + "/" + id, args) + return self.request("{0}/{1}".format(self.version, id), args) def get_objects(self, ids, **args): """Fetches all of the given object from the graph. @@ -114,10 +123,39 @@ class GraphAPI(object): args["ids"] = ",".join(ids) return self.request(self.version + "/", args) + def search(self, type, **args): + """Fetches all objects of a given type from the graph. + + Returns all objects of a given type from the graph as a dict. + """ + + if type not in VALID_SEARCH_TYPES: + raise GraphAPIError('Valid types are: %s' + % ', '.join(VALID_SEARCH_TYPES)) + + args["type"] = type + return self.request(self.version + "/search/", args) + def get_connections(self, id, connection_name, **args): """Fetches the connections for given object.""" return self.request( - "%s/%s/%s" % (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): + """Get all pages from a get_connections call + + This will iterate over all pages returned by a get_connections call + and yield the individual items. + """ + while True: + page = self.get_connections(id, connection_name, **args) + for post in page['data']: + yield post + next = page.get('paging', {}).get('next') + if not next: + return + args = parse_qs(urlparse(next).query) + del args['access_token'] def put_object(self, parent_object, connection_name, **data): """Writes the given object to the graph, connected to the given parent. @@ -133,41 +171,17 @@ class GraphAPI(object): post = feed["data"][0] graph.put_object(post["id"], "comments", message="First!") - See http://developers.facebook.com/docs/api#publishing for all - of the supported writeable objects. - - Certain write operations require extended permissions. For - example, publishing to a user's feed requires the - "publish_actions" permission. See - http://developers.facebook.com/docs/publishing/ for details - about publishing permissions. + Certain operations require extended permissions. See + https://developers.facebook.com/docs/facebook-login/permissions + for details about permissions. """ assert self.access_token, "Write operations require an access token" return self.request( - self.version + "/" + parent_object + "/" + connection_name, + "{0}/{1}/{2}".format(self.version, parent_object, connection_name), post_args=data, method="POST") - def put_wall_post(self, message, attachment={}, profile_id="me"): - """Writes a wall post to the given profile's wall. - - We default to writing to the authenticated user's wall if no - profile_id is specified. - - attachment adds a structured attachment to the status message - being posted to the Wall. It should be a dictionary of the form: - - {"name": "Link name" - "link": "http://www.example.com/", - "caption": "{*actor*} posted a new review", - "description": "This is a longer description of the attachment", - "picture": "http://www.example.com/thumbnail.jpg"} - - """ - return self.put_object(profile_id, "feed", message=message, - **attachment) - def put_comment(self, object_id, message): """Writes the given comment on the given post.""" return self.put_object(object_id, "comments", message=message) @@ -178,11 +192,11 @@ class GraphAPI(object): def delete_object(self, id): """Deletes the object with the given ID from the graph.""" - self.request(self.version + "/" + id, method="DELETE") + self.request("{0}/{1}".format(self.version, id), method="DELETE") def delete_request(self, user_id, request_id): """Deletes the Request with the given ID for the given user.""" - self.request("%s_%s" % (request_id, user_id), method="DELETE") + self.request("{0}_{1}".format(request_id, user_id), method="DELETE") def put_photo(self, image, album_path="me/photos", **kwargs): """ @@ -193,7 +207,7 @@ class GraphAPI(object): """ return self.request( - self.version + "/" + album_path, + "{0}/{1}".format(self.version, album_path), post_args=kwargs, files={"source": image}, method="POST") @@ -202,12 +216,12 @@ class GraphAPI(object): """Fetches the current version number of the Graph API being used.""" args = {"access_token": self.access_token} try: - response = requests.request("GET", - "https://graph.facebook.com/" + - self.version + "/me", - params=args, - timeout=self.timeout, - proxies=self.proxies) + response = self.session.request( + "GET", + FACEBOOK_GRAPH_URL + self.version + "/me", + params=args, + timeout=self.timeout, + proxies=self.proxies) except requests.HTTPError as e: response = json.loads(e.read()) raise GraphAPIError(response) @@ -228,26 +242,30 @@ class GraphAPI(object): arguments. """ - args = args or {} - + if args is None: + args = dict() if post_args is not None: method = "POST" + # Add `access_token` to post_args or args if it has not already been + # included. if self.access_token: - if post_args is not None: + # If post_args exists, we assume that args either does not exists + # or it does not need `access_token`. + if post_args and "access_token" not in post_args: post_args["access_token"] = self.access_token - else: + elif "access_token" not in args: args["access_token"] = self.access_token try: - response = requests.request(method or "GET", - "https://graph.facebook.com/" + - path, - timeout=self.timeout, - params=args, - data=post_args, - proxies=self.proxies, - files=files) + response = self.session.request( + method or "GET", + FACEBOOK_GRAPH_URL + path, + timeout=self.timeout, + params=args, + data=post_args, + proxies=self.proxies, + files=files) except requests.HTTPError as e: response = json.loads(e.read()) raise GraphAPIError(response) @@ -275,21 +293,23 @@ class GraphAPI(object): raise GraphAPIError(result) return result - def fql(self, query): - """FQL query. - - Example query: "SELECT affiliations FROM user WHERE uid = me()" - + def get_app_access_token(self, app_id, app_secret, offline=False): """ - return self.request(self.version + "/" + "fql", {"q": query}) + Get the application's access token as a string. + If offline=True, use the concatenated app ID and secret + instead of making an API call. + + """ + if offline: + return "{0}|{1}".format(app_id, app_secret) + else: + args = {'grant_type': 'client_credentials', + 'client_id': app_id, + 'client_secret': app_secret} - def get_app_access_token(self, app_id, app_secret): - """Get the application's access token as a string.""" - args = {'grant_type': 'client_credentials', - 'client_id': app_id, - 'client_secret': app_secret} - - return self.request("oauth/access_token", args=args)["access_token"] + return self.request("{0}/oauth/access_token".format(self.version), + args=args)["access_token"] def get_access_token_from_code( self, code, redirect_uri, app_id, app_secret): @@ -305,13 +325,14 @@ class GraphAPI(object): "client_id": app_id, "client_secret": app_secret} - return self.request("oauth/access_token", args) + return self.request( + "{0}/oauth/access_token".format(self.version), args) def extend_access_token(self, app_id, app_secret): """ Extends the expiration time of a valid OAuth access token. See - + """ args = { @@ -321,13 +342,14 @@ class GraphAPI(object): "fb_exchange_token": self.access_token, } - return self.request("oauth/access_token", args=args) + return self.request("{0}/oauth/access_token".format(self.version), + args=args) def debug_access_token(self, token, app_id, app_secret): """ Gets information about a user access token issued by an app. See - + We can generate the app access token by concatenating the app id and secret: ' - 'If disabled, the link will go to IMDB, TVDB, TMDb, or Last.fm instead, if available.', - 'input_type': 'checkbox' + {'label': 'TV Show Link Source', + 'value': self.config['tv_provider'], + 'name': 'facebook_tv_provider', + 'description': 'Select the source for tv show links on the info cards. Leave blank for default.', + 'input_type': 'select', + 'select_options': {'': '', + 'thetvdb': 'TheTVDB', + 'tvmaze': 'TVmaze', + 'imdb': 'IMDB', + 'trakt': 'Trakt.tv', + 'plexweb': 'Plex Web' + } + }, + {'label': 'Music Link Source', + 'value': self.config['music_provider'], + 'name': 'facebook_music_provider', + 'description': 'Select the source for music links on the info cards. Leave blank for default.', + 'input_type': 'select', + 'select_options': {'': '', + 'lastfm': 'Last.fm', + 'plexweb': 'Plex Web' + } } ]