from collections import namedtuple
import datetime
+
+try:
+ from functools import cache
+except ImportError: # Remove when support for Python 3.8 is dropped
+ from functools import lru_cache
+ cache = lru_cache(maxsize=None)
+
import logging
from platform import python_version
import time
User agent used when making requests to the API
"""
+ def _get_authenticating_user_id(self, *, user_auth):
+ if user_auth:
+ if self.access_token is None:
+ raise TypeError(
+ "Access Token must be provided for OAuth 1.0a User Context"
+ )
+ else:
+ return self._get_oauth_1_authenticating_user_id(
+ self.access_token
+ )
+ else:
+ if self.bearer_token is None:
+ raise TypeError(
+ "Access Token must be provided for "
+ "OAuth 2.0 Authorization Code Flow with PKCE"
+ )
+ else:
+ return self._get_oauth_2_authenticating_user_id(
+ self.bearer_token
+ )
+
+ @cache
+ def _get_oauth_1_authenticating_user_id(self, access_token):
+ return access_token.partition('-')[0]
+
+ @cache
+ def _get_oauth_2_authenticating_user_id(self, access_token):
+ original_access_token = self.bearer_token
+ original_return_type = self.return_type
+
+ self.bearer_token = access_token
+ self.return_type = dict
+ user_id = self.get_me(user_auth=False)["data"]["id"]
+
+ self.bearer_token = original_access_token
+ self.return_type = original_return_type
+
+ return user_id
+
# Hide replies
def hide_reply(self, id, *, user_auth=True):
The request succeeds with no action when the user sends a request to a
user they're not liking the Tweet or have already unliked the Tweet.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
tweet_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/tweets/likes/api-reference/delete-users-id-likes-tweet_id
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/likes/{tweet_id}"
return self._make_request(
def like(self, tweet_id, *, user_auth=True):
"""Like a Tweet.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
tweet_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/tweets/likes/api-reference/post-users-id-likes
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/likes"
return self._make_request(
user they're not Retweeting the Tweet or have already removed the
Retweet of.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
source_tweet_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/tweets/retweets/api-reference/delete-users-id-retweets-tweet_id
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/retweets/{source_tweet_id}"
return self._make_request(
def retweet(self, tweet_id, *, user_auth=True):
"""Causes the user ID to Retweet the target Tweet.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
tweet_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/tweets/retweets/api-reference/post-users-id-retweets
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/retweets"
return self._make_request(
The request succeeds with no action when the user sends a request to a
user they're not blocking or have already unblocked.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
target_user_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/blocks/api-reference/delete-users-user_id-blocking
"""
- source_user_id = self.access_token.partition('-')[0]
+ source_user_id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{source_user_id}/blocking/{target_user_id}"
return self._make_request(
Returns a list of users who are blocked by the authenticating user.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
expansions : list[str] | str | None
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/blocks/api-reference/get-users-blocking
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/blocking"
return self._make_request(
def block(self, target_user_id, *, user_auth=True):
"""Block another user.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
target_user_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/blocks/api-reference/post-users-user_id-blocking
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/blocking"
return self._make_request(
The request succeeds with no action when the authenticated user sends a
request to a user they're not following or have already unfollowed.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.2
Renamed from :meth:`Client.unfollow`
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
target_user_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/delete-users-source_id-following
"""
- source_user_id = self.access_token.partition('-')[0]
+ source_user_id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{source_user_id}/following/{target_user_id}"
return self._make_request(
request to a user they're already following, or if they're sending a
follower request to a user that does not have public Tweets.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.2
Renamed from :meth:`Client.follow`
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
target_user_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/post-users-source_user_id-following
"""
- source_user_id = self.access_token.partition('-')[0]
+ source_user_id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{source_user_id}/following"
return self._make_request(
The request succeeds with no action when the user sends a request to a
user they're not muting or have already unmuted.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
target_user_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/mutes/api-reference/delete-users-user_id-muting
"""
- source_user_id = self.access_token.partition('-')[0]
+ source_user_id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{source_user_id}/muting/{target_user_id}"
return self._make_request(
Returns a list of users who are muted by the authenticating user.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionadded:: 4.1
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
expansions : list[str] | str | None
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/mutes/api-reference/get-users-muting
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/muting"
return self._make_request(
def mute(self, target_user_id, *, user_auth=True):
"""Allows an authenticated user ID to mute the target user.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
target_user_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/users/mutes/api-reference/post-users-user_id-muting
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/muting"
return self._make_request(
def unfollow_list(self, list_id, *, user_auth=True):
"""Enables the authenticated user to unfollow a List.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionadded:: 4.2
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
list_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/lists/list-follows/api-reference/delete-users-id-followed-lists-list_id
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/followed_lists/{list_id}"
return self._make_request(
def follow_list(self, list_id, *, user_auth=True):
"""Enables the authenticated user to follow a List.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionadded:: 4.2
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
list_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/lists/list-follows/api-reference/post-users-id-followed-lists
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/followed_lists"
return self._make_request(
def unpin_list(self, list_id, *, user_auth=True):
"""Enables the authenticated user to unpin a List.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionadded:: 4.2
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
list_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/lists/pinned-lists/api-reference/delete-users-id-pinned-lists-list_id
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/pinned_lists/{list_id}"
return self._make_request(
Returns the Lists pinned by a specified user.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionadded:: 4.4
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
expansions : list[str] | str | None
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/lists/pinned-lists/api-reference/get-users-id-pinned_lists
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/pinned_lists"
return self._make_request(
def pin_list(self, list_id, *, user_auth=True):
"""Enables the authenticated user to pin a List.
+ .. note::
+
+ When using OAuth 2.0 Authorization Code Flow with PKCE with
+ ``user_auth=False``, a request is made beforehand to Twitter's API
+ to determine the authenticating user's ID. This is cached and only
+ done once per :class:`Client` instance for each access token used.
+
.. versionadded:: 4.2
.. versionchanged:: 4.5
Added ``user_auth`` parameter
+ .. versionchanged:: 4.8
+ Added support for using OAuth 2.0 Authorization Code Flow with PKCE
+
+ .. versionchanged:: 4.8
+ Changed to raise :class:`TypeError` when the access token isn't set
+
Parameters
----------
list_id : int | str
user_auth : bool
Whether or not to use OAuth 1.0a User Context to authenticate
+ Raises
+ ------
+ TypeError
+ If the access token isn't set
+
Returns
-------
dict | requests.Response | Response
----------
https://developer.twitter.com/en/docs/twitter-api/lists/pinned-lists/api-reference/post-users-id-pinned-lists
"""
- id = self.access_token.partition('-')[0]
+ id = self._get_authenticating_user_id(user_auth=user_auth)
route = f"/2/users/{id}/pinned_lists"
return self._make_request(