|
8 | 8 | import re
|
9 | 9 | import time
|
10 | 10 | from urllib.parse import parse_qsl, urlparse
|
11 |
| -from typing import Any, Dict, List, Optional, Tuple |
| 11 | +from typing import Any, Dict, List, Optional, Tuple, Union |
12 | 12 | from warnings import warn
|
13 | 13 |
|
14 | 14 | import requests
|
@@ -505,7 +505,7 @@ def delete_object(
|
505 | 505 | def _get_oauth_session(
|
506 | 506 | self,
|
507 | 507 | redirect_uri: Optional[str] = None,
|
508 |
| - scope: Optional[List[str]] = None, |
| 508 | + scope: Optional[Union[List[str], str]] = None, |
509 | 509 | state: Optional[str] = None,
|
510 | 510 | **kwargs,
|
511 | 511 | ) -> OAuth2Session:
|
@@ -869,6 +869,129 @@ def debug_token(self, input_token: str, access_token: Optional[str] = None) -> d
|
869 | 869 | raise LibraryError({"message": "Method not support"})
|
870 | 870 |
|
871 | 871 |
|
| 872 | +class ThreadsGraphAPI(GraphAPI): |
| 873 | + GRAPH_URL = "https://graph.threads.net/" |
| 874 | + DEFAULT_SCOPE = ["threads_basic"] |
| 875 | + AUTHORIZATION_URL = "https://threads.net/oauth/authorize" |
| 876 | + EXCHANGE_ACCESS_TOKEN_URL = "https://graph.threads.net/oauth/access_token" |
| 877 | + |
| 878 | + VALID_API_VERSIONS = ["v1.0"] |
| 879 | + |
| 880 | + @staticmethod |
| 881 | + def fix_scope(scope: Optional[List[str]] = None): |
| 882 | + """ |
| 883 | + Note: After tests, the api for threads only support for comma-separated list. |
| 884 | +
|
| 885 | + :param scope: A list of permission string to request from the person using your app. |
| 886 | + :return: comma-separated scope string |
| 887 | + """ |
| 888 | + return ",".join(scope) if scope else scope |
| 889 | + |
| 890 | + def get_authorization_url( |
| 891 | + self, |
| 892 | + redirect_uri: Optional[str] = None, |
| 893 | + scope: Optional[List[str]] = None, |
| 894 | + state: Optional[str] = None, |
| 895 | + url_kwargs: Optional[Dict[str, Any]] = None, |
| 896 | + **kwargs, |
| 897 | + ) -> Tuple[str, str]: |
| 898 | + """ |
| 899 | + Build authorization url to do oauth. |
| 900 | + Refer: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow |
| 901 | +
|
| 902 | + :param redirect_uri: The URL that you want to redirect the person logging in back to. |
| 903 | + Note: Your redirect uri need be set to `Valid OAuth redirect URIs` items in App Dashboard. |
| 904 | + :param scope: A list of permission string to request from the person using your app. |
| 905 | + :param state: A CSRF token that will be passed to the redirect URL. |
| 906 | + :param url_kwargs: Additional parameters for generate authorization url. like config_id. |
| 907 | + :param kwargs: Additional parameters for oauth. |
| 908 | + :return: URL to do oauth and state |
| 909 | + """ |
| 910 | + if scope: |
| 911 | + self.scope = scope |
| 912 | + scope = self.fix_scope(self.scope) |
| 913 | + |
| 914 | + session = self._get_oauth_session( |
| 915 | + redirect_uri=redirect_uri, scope=scope, state=state, **kwargs |
| 916 | + ) |
| 917 | + url_kwargs = {} if url_kwargs is None else url_kwargs |
| 918 | + authorization_url, state = session.authorization_url( |
| 919 | + url=self.authorization_url, **url_kwargs |
| 920 | + ) |
| 921 | + return authorization_url, state |
| 922 | + |
| 923 | + def exchange_user_access_token( |
| 924 | + self, |
| 925 | + response: str, |
| 926 | + redirect_uri: Optional[str] = None, |
| 927 | + scope: Optional[List[str]] = None, |
| 928 | + state: Optional[str] = None, |
| 929 | + **kwargs, |
| 930 | + ) -> dict: |
| 931 | + """ |
| 932 | + :param response: The redirect response url for authorize redirect |
| 933 | + :param redirect_uri: Url for your redirect. |
| 934 | + :param scope: A list of permission string to request from the person using your app. |
| 935 | + :param state: A CSRF token that will be passed to the redirect URL. |
| 936 | + :param kwargs: Additional parameters for oauth. |
| 937 | + :return: |
| 938 | + """ |
| 939 | + if scope: |
| 940 | + self.scope = scope |
| 941 | + scope = self.fix_scope(self.scope) |
| 942 | + |
| 943 | + session = self._get_oauth_session( |
| 944 | + redirect_uri=redirect_uri, scope=scope, state=state, **kwargs |
| 945 | + ) |
| 946 | + |
| 947 | + session.fetch_token( |
| 948 | + self.access_token_url, |
| 949 | + client_secret=self.app_secret, |
| 950 | + authorization_response=response, |
| 951 | + include_client_id=True, |
| 952 | + ) |
| 953 | + self.access_token = session.access_token |
| 954 | + |
| 955 | + return session.token |
| 956 | + |
| 957 | + def exchange_long_lived_user_access_token(self, access_token=None) -> dict: |
| 958 | + """ |
| 959 | + Generate long-lived token by short-lived token, Long-lived token generally lasts about 60 days. |
| 960 | +
|
| 961 | + :param access_token: Short-lived user access token |
| 962 | + :return: Long-lived user access token info. |
| 963 | + """ |
| 964 | + if access_token is None: |
| 965 | + access_token = self.access_token |
| 966 | + args = { |
| 967 | + "grant_type": "th_exchange_token", |
| 968 | + "client_id": self.app_id, |
| 969 | + "client_secret": self.app_secret, |
| 970 | + "access_token": access_token, |
| 971 | + } |
| 972 | + |
| 973 | + resp = self._request( |
| 974 | + url=self.access_token_url, |
| 975 | + args=args, |
| 976 | + auth_need=False, |
| 977 | + ) |
| 978 | + data = self._parse_response(resp) |
| 979 | + return data |
| 980 | + |
| 981 | + def refresh_access_token(self, access_token: str): |
| 982 | + """ |
| 983 | + :param access_token: The valid (unexpired) long-lived Instagram User Access Token that you want to refresh. |
| 984 | + :return: New access token. |
| 985 | + """ |
| 986 | + args = {"grant_type": "th_refresh_token", "access_token": access_token} |
| 987 | + resp = self._request( |
| 988 | + url="refresh_access_token", |
| 989 | + args=args, |
| 990 | + ) |
| 991 | + data = self._parse_response(resp) |
| 992 | + return data |
| 993 | + |
| 994 | + |
872 | 995 | class ServerSentEventAPI:
|
873 | 996 | """
|
874 | 997 | Notice: Server-Sent Events are deprecated and will be removed December 31, 2023.
|
|
0 commit comments