def get_authorization_url(self, redirect_uri=None, scope=None, **kwargs): # type: (str, List, Dict) -> (str, str) """ Build authorization url to do authorize. Refer: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow :param redirect_uri: The URL that you want to redirect the person logging in back to. Note: Your redirect uri need be set to `Valid OAuth redirect URIs` items in App Dashboard. :param scope: A list of Permissions to request from the person using your app. :param kwargs: Extend args for oauth. :return: Authorization url and state. """ if not all([self.app_id, self.app_secret]): raise PyFacebookException( ErrorMessage( code=ErrorCode.MISSING_PARAMS, message="To do authorization need your app credentials")) session = self._get_oauth_session(redirect_uri=redirect_uri, scope=scope, **kwargs) authorization_url, state = session.authorization_url( url=self.authorization_url) return authorization_url, state
def exchange_insights_token(self, page_id, access_token=None): # type: (str, Optional[str]) -> str """ Use user access token to exchange page(managed by that user) access token. Refer: 1. https://developers.facebook.com/docs/pages/access-tokens 2. https://developers.facebook.com/docs/facebook-login/access-tokens :param page_id: The id for page :param access_token: user access token """ if access_token is None: access_token = self._access_token args = {"access_token": access_token, "fields": "access_token"} resp = self._request(method="GET", path="{version}/{page_id}".format( version=self.version, page_id=page_id), args=args, enforce_auth=False) data = self._parse_response(resp) if "access_token" not in data: raise PyFacebookException( ErrorMessage( code=ErrorCode.INVALID_PARAMS, message= ("Can not change page access token. Confirm: \n" "1. Your user access token has `page_show_list` or `manage_pages` permission.\n" "2. You have the target page's manage permission."))) return data["access_token"]
def _request(self, path, method="GET", args=None, post_args=None, enforce_auth=True): # type: (str, str, Optional[dict], Optional[dict], bool) -> Response """ Build the request and send request to Facebook. :param path: The path for resource on facebook. :param method: Http methods. :param args: GET parameters. :param post_args: POST parameters. :param enforce_auth: Set to True mean this request need access token. :return: The Response instance. """ if args is None: args = dict() if post_args is not None: method = "POST" if enforce_auth: if method == "POST" and "access_token" not in post_args: post_args["access_token"] = self._access_token elif method == "GET" and "access_token" not in args: args["access_token"] = self._access_token # add appsecret_proof parameter # Refer: https://developers.facebook.com/docs/graph-api/securing-requests/ if method == "POST" and "appsecret_proof" not in post_args: secret_proof = self._generate_secret_proof( self.app_secret, post_args["access_token"]) if secret_proof is not None: post_args["appsecret_proof"] = secret_proof elif method == "GET" and "appsecret_proof" not in args: secret_proof = self._generate_secret_proof( self.app_secret, args["access_token"]) if secret_proof is not None: args["appsecret_proof"] = secret_proof # check path if not path.startswith("https"): path = self.base_url + path try: response = self.session.request(method, path, timeout=self.__timeout, params=args, data=post_args, proxies=self.proxies) except requests.HTTPError as e: raise PyFacebookException( ErrorMessage(code=ErrorCode.HTTP_ERROR, message=e.args[0])) headers = response.headers self.rate_limit.set_limit(headers) if self.sleep_on_rate_limit: sleep_seconds = self.rate_limit.get_sleep_seconds( sleep_data=self.sleep_seconds_mapping) time.sleep(sleep_seconds) return response
def testErrorMessage(self): error = ErrorMessage(code=ErrorCode.HTTP_ERROR, message="error") ex = PyFacebookException(error) self.assertEqual(ex.code, 10000) self.assertEqual(ex.message, "error") self.assertEqual(ex.error_type, "PyFacebookException")
def get_page_info(self, page_id=None, # type: Optional[str] username=None, # type: Optional[str] fields=None, # type: Optional[Union[str, List, Tuple, Set]] return_json=False # type: bool ): # type: (...) -> Union[Page, dict] """ Retrieve the given page's basic info. :param page_id: The id for page. :param username: The username for page. :param fields:Comma-separated id string for data fields which you want. You can also pass this with an id list, tuple, set. :param return_json: Set to false will return instance of Page. Or return json data. Default is false. """ if page_id: target = page_id elif username: target = username else: raise PyFacebookException(ErrorMessage( code=ErrorCode.MISSING_PARAMS, message="Specify at least one of page_id or username", )) if fields is None: fields = constant.FB_PAGE_FIELDS args = { "fields": enf_comma_separated("fields", fields) } resp = self._request( method='GET', path='{0}/{1}'.format(self.version, target), args=args ) data = self._parse_response(resp) if return_json: return data else: return Page.new_from_json_dict(data)
def get_pictures( self, ids, # type: Optional[Union[str, List, Tuple, Set]] pic_type=None, # type: Optional[str] return_json=False # type: bool ): # type: (...) -> dict """ :param ids: Comma-separated id(username) string for page which you want to get. You can also pass this with an id list, tuple, set. :param pic_type: The picture type. :param return_json: Set to false will return a dict of Comment instances. Or return json data. Default is false. """ if pic_type is not None and pic_type not in constant.FB_PAGE_PICTURE_TYPE: raise PyFacebookException( ErrorMessage( code=ErrorCode.INVALID_PARAMS, message= "For field picture: pic_type must be one of the following values: {}" .format(', '.join(constant.FB_PAGE_PICTURE_TYPE)))) args = { "ids": enf_comma_separated("ids", ids), 'redirect': 0, # if set 0 the api will return json response. 'type': 'normal' if pic_type is None else pic_type, } resp = self._request(method='GET', path='{0}/picture'.format(self.version), args=args) data = self._parse_response(resp) res = {} for _id, p_data in iteritems(data): picture_data = p_data["data"] if return_json: res[_id] = picture_data else: res[_id] = ProfilePictureSource.new_from_json_dict( picture_data) return res
def get_picture(self, page_id, # type: str pic_type=None, # type: Optional[str] return_json=False # type: bool ): # type: (...) -> Union[ProfilePictureSource, dict] """ Retrieve the page's picture. :param page_id: The id for picture you want to retrieve data. :param pic_type: The picture type. :param return_json: Set to false will return a dict of Comment instances. Or return json data. Default is false. """ if pic_type is not None and pic_type not in constant.FB_PAGE_PICTURE_TYPE: raise PyFacebookException(ErrorMessage( code=ErrorCode.INVALID_PARAMS, message="For field picture: pic_type must be one of the following values: {}".format( ', '.join(constant.FB_PAGE_PICTURE_TYPE) ))) args = { 'redirect': 0, # if set 0 the api will return json response. 'type': 'normal' if pic_type is None else pic_type, } resp = self._request( method='GET', path='{0}/{1}/picture'.format(self.version, page_id), args=args ) data = self._parse_response(resp) data = replace_from_keyword_in_json(data=data) if return_json: return data['data'] else: return ProfilePictureSource.new_from_json_dict(data['data'])
def exchange_access_token(self, response, return_json=False): # type: (str, bool) -> Union[AuthAccessToken, Dict] """ :param response: The whole response url for your previous authorize step. :param return_json: Set to false will return instance of AuthAccessToken. Or return json data. Default is false. :return: """ if self.auth_session is None: raise PyFacebookException( ErrorMessage( code=ErrorCode.MISSING_PARAMS, message="exchange token should do authorize first")) self.auth_session.fetch_token(self.exchange_access_token_url, client_secret=self.app_secret, authorization_response=response) self._access_token = self.auth_session.access_token if return_json: return self.auth_session.token else: return AuthAccessToken.new_from_json_dict(self.auth_session.token)
def discovery_user_medias( self, username, # type: str fields=None, # type: Optional[Union[str, List, Tuple, Set]] since_time=None, # type: Optional[str] until_time=None, # type: Optional[str] count=10, # type: Optional[int] limit=10, # type: int return_json=False # type: bool ): # type: (...) -> List[Union[IgProMedia, dict]] """ Retrieve other business user's public medias. :param username: The username for other business user. :param fields: Comma-separated id string for data fields which you want. You can also pass this with an id list, tuple, set. :param since_time: Lower bound of the time range to the medias publish time. Format is %Y-%m-%d. If not provide, will not limit by this. :param until_time: Upper bound of the time range to the medias publish time. Format is %Y-%m-%d. If not provide, will not limit by this. :param count: The count is you want to retrieve medias. Default is 10. If you want to get all data. Set it to None. For now This may be not more than 10K. :param limit: Each request retrieve posts count from api. For medias it should no more than 500. :param return_json: Set to false will return a list instance of IgProMedia. Or return json data. Default is false. """ try: if since_time is not None: since_time = datetime.datetime.strptime(since_time, '%Y-%m-%d') if until_time is not None: until_time = datetime.datetime.strptime(until_time, '%Y-%m-%d') except (ValueError, TypeError): raise PyFacebookException( ErrorMessage( code=ErrorCode.INVALID_PARAMS, message="since_time or until_time must format as %Y-%m-%d") ) if count is not None: limit = min(limit, count) if fields is None: fields = constant.INSTAGRAM_MEDIA_PUBLIC_FIELD fields = enf_comma_separated("fields", fields) if (since_time is not None or until_time is not None) and "timestamp" not in fields: raise PyFacebookException( ErrorMessage( code=ErrorCode.MISSING_PARAMS, message= "Use the since and until must give `timestamp` field")) args = { 'path': '{0}/{1}'.format(self.version, self.instagram_business_id), 'username': username, 'limit': limit, "metric": enf_comma_separated("fields", fields), } medias = [] next_cursor = None while True: next_cursor, previous_cursor, data = self.paged_by_cursor( args=args, next_cursor=next_cursor, business_discovery=True) data = data.get('data', []) # check if the media meet the request. for item in data: begin_flag, end_flag = True, True if "timestamp" in item: timestamp = datetime.datetime.strptime( item['timestamp'][:-5], '%Y-%m-%dT%H:%M:%S') if since_time is not None: begin_flag = since_time < timestamp if until_time is not None: end_flag = until_time > timestamp if all([begin_flag, end_flag]): if return_json: medias.append(item) else: medias.append(IgProMedia.new_from_json_dict(item)) if not begin_flag: next_cursor = None break if count is not None: if len(medias) >= count: medias = medias[:count] break if next_cursor is None: break return medias
def get_user_medias( self, user_id, # type: str fields=None, # type: Optional[Union[str, List, Tuple, Set]] since_time=None, # type: Optional[str] until_time=None, # type: Optional[str] count=10, # type: Optional[int] limit=10, # type: int return_json=False # type: bool ): # type: (...) -> List[Union[IgProMedia, dict]] """ Retrieve ig user medias data by user id. :param user_id: The id for instagram business user which you want to get data. :param fields: Comma-separated id string for data fields which you want. You can also pass this with an id list, tuple, set. :param since_time: Lower bound of the time range to the medias publish time. Format is %Y-%m-%d. If not provide, will not limit by this. :param until_time: Upper bound of the time range to the medias publish time. Format is %Y-%m-%d. If not provide, will not limit by this. :param count: The count for you want to get medias. Default is 10. If need get all, set this with None. :param limit: Each request retrieve medias count from api. For medias it should no more than 500. :param return_json: Set to false will return instance of IgProUser. Or return json data. Default is false. """ try: if since_time is not None: since_time = datetime.datetime.strptime(since_time, '%Y-%m-%d') if until_time is not None: until_time = datetime.datetime.strptime(until_time, '%Y-%m-%d') except (ValueError, TypeError): raise PyFacebookException( ErrorMessage( code=ErrorCode.INVALID_PARAMS, message="since_time or until_time must format as %Y-%m-%d", )) if fields is None: fields = constant.INSTAGRAM_MEDIA_OWNER_FIELD if count is not None: limit = min(limit, count) args = { 'fields': enf_comma_separated("fields", fields), 'limit': limit } medias = [] next_cursor = None while True: next_cursor, previous_cursor, data = self.paged_by_cursor( target=user_id, resource='media', args=args, next_cursor=next_cursor) data = data.get('data', []) for item in data: begin_flag, end_flag = True, True if "timestamp" in item: timestamp = datetime.datetime.strptime( item['timestamp'][:-5], '%Y-%m-%dT%H:%M:%S') if since_time is not None: begin_flag = since_time < timestamp if until_time is not None: end_flag = until_time > timestamp if all([begin_flag, end_flag]): if return_json: medias.append(item) else: medias.append(IgProMedia.new_from_json_dict(item)) if not begin_flag: next_cursor = None break if count is not None: if len(medias) >= count: medias = medias[:count] break if next_cursor is None: break return medias
def __init__( self, app_id=None, # type: Optional[str] app_secret=None, # type: Optional[str] short_token=None, # type: Optional[str] long_term_token=None, # type: Optional[str] application_only_auth=False, # type: bool initial_access_token=True, # type: bool version=None, # type: Optional[str] timeout=None, # type: Optional[int] sleep_on_rate_limit=False, # type: bool sleep_seconds_mapping=None, # type: Dict[int, int] proxies=None, # type: Optional[dict] debug_http=False # type: bool ): # type: (...) -> None """ :param app_id: Your app id. :param app_secret: Your app secret. :param short_token: short-lived token :param long_term_token: long-lived token. :param application_only_auth: Use the `App Access Token` only. :param initial_access_token: If you want use api do authorize, set this with False. :param version: The version for the graph api. :param timeout: Request time out :param sleep_on_rate_limit: Use this will sleep between two request. :param sleep_seconds_mapping: Mapping for percent to sleep. :param proxies: Your proxies config. :param debug_http: Set to True to enable debug output from urllib when performing any HTTP requests. Defaults to False. """ self.app_id = app_id self.app_secret = app_secret self.short_token = short_token self.__timeout = timeout self.base_url = self.GRAPH_URL self.proxies = proxies self.session = requests.Session() self.sleep_on_rate_limit = sleep_on_rate_limit self.instagram_business_id = None self._debug_http = debug_http self.authorization_url = self.DEFAULT_AUTHORIZATION_URL self.exchange_access_token_url = self.DEFAULT_EXCHANGE_ACCESS_TOKEN_URL self.redirect_uri = self.DEFAULT_REDIRECT_URI self.scope = self.DEFAULT_SCOPE self.auth_session = None # Authorization session self.rate_limit = RateLimit() if version is None: # default version is last new. self.version = self.VALID_API_VERSIONS[-1] else: version = str(version) if not version.startswith('v'): version = 'v' + version version_regex = re.compile(r"^v\d.\d{1,2}$") match = version_regex.search(str(version)) if match is not None: if version not in self.VALID_API_VERSIONS: raise PyFacebookException( ErrorMessage(code=ErrorCode.INVALID_PARAMS, message="Valid API version are {}".format( ",".join(self.VALID_API_VERSIONS)))) else: self.version = version else: raise PyFacebookException( ErrorMessage( code=ErrorCode.INVALID_PARAMS, message= "Version string is invalid for {0}. You can provide with like: 5.0 or v5.0" .format(version), )) self.sleep_seconds_mapping = self._build_sleep_seconds_resource( sleep_seconds_mapping) if long_term_token: self._access_token = long_term_token elif short_token and all([self.app_id, self.app_secret]): token = self.get_long_token(app_id=self.app_id, app_secret=self.app_secret, short_token=self.short_token) self._access_token = token.access_token elif application_only_auth and all([self.app_id, app_secret]): token = self.get_app_token() self._access_token = token.access_token elif not initial_access_token and all([self.app_id, app_secret]): self._access_token = None else: raise PyFacebookException( ErrorMessage( code=ErrorCode.MISSING_PARAMS, message= ("You can initial api with three methods: \n" "1. Just provide long(short) lived token or app access token with param `long_term_token`.\n" "2. Provide a short lived token and app credentials. Api will auto exchange long term token.\n" "3. Provide app credentials and with application_only_auth set to true. " "Api will auto get and use app access token.\n" "4. Provide app credentials and prepare for do authorize(This will not retrieve access token)" ))) if debug_http: from six.moves import http_client http_client.HTTPConnection.debuglevel = 1 logging.basicConfig( ) # you need to initialize logging, otherwise you will not see anything from requests logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True
def exchange_insights_token(self, page_id, access_token=None): raise PyFacebookException( ErrorMessage(code=ErrorCode.NOT_SUPPORT_METHOD, message="Method not support by this api."))
def get_token_info(self, input_token=None, return_json=False): raise PyFacebookException( ErrorMessage(code=ErrorCode.NOT_SUPPORT_METHOD, message="Method not support by this api."))