From 6ad2f7ca6609d7ea0990714ac55f9a9140c775ef Mon Sep 17 00:00:00 2001 From: ikaroskun Date: Tue, 21 Nov 2023 19:40:28 +0800 Subject: [PATCH 1/3] feat(tweets): :sparkles: Add api to get tweet retweets Closes #144 --- pytwitter/api.py | 57 +++++++++++++++++++++++++++++++++++++++++ pytwitter/rate_limit.py | 7 +++++ 2 files changed, 64 insertions(+) diff --git a/pytwitter/api.py b/pytwitter/api.py index 35182e4..8b546e5 100644 --- a/pytwitter/api.py +++ b/pytwitter/api.py @@ -1272,6 +1272,63 @@ def get_tweet_quote_tweets( return_json=return_json, ) + def get_tweet_retweet_tweets( + self, + tweet_id: str, + *, + pagination_token: Optional[str] = None, + max_results: Optional[int] = None, + expansions: Optional[Union[str, List, Tuple]] = None, + tweet_fields: Optional[Union[str, List, Tuple]] = None, + media_fields: Optional[Union[str, List, Tuple]] = None, + place_fields: Optional[Union[str, List, Tuple]] = None, + poll_fields: Optional[Union[str, List, Tuple]] = None, + user_fields: Optional[Union[str, List, Tuple]] = None, + return_json: bool = False, + ) -> Union[dict, md.Response]: + """ + Returns the Retweets for a given Tweet ID. + + :param tweet_id: Unique identifier of the Tweet to request. + :param pagination_token: Token for the pagination. + :param max_results: The maximum number of results to be returned per page. Number between 5 and the 100. + By default, each page will return 100 results. + :param expansions: Fields for the expansions. + :param tweet_fields: Fields for the tweet object. + :param media_fields: Fields for the media object, Expansion required. + :param place_fields: Fields for the place object, Expansion required. + :param poll_fields: Fields for the poll object, Expansion required. + :param user_fields: Fields for the user object, Expansion required. + :param return_json: Type for returned data. If you set True JSON data will be returned. + :return: + - data: data for the target tweets. + - includes: expansions data. + - meta: pagination details + """ + args = { + "expansions": enf_comma_separated(name="expansions", value=expansions), + "tweet.fields": enf_comma_separated( + name="tweet_fields", value=tweet_fields + ), + "user.fields": enf_comma_separated(name="user_fields", value=user_fields), + "media.fields": enf_comma_separated( + name="media_fields", value=media_fields + ), + "place.fields": enf_comma_separated( + name="place_fields", value=place_fields + ), + "poll.fields": enf_comma_separated(name="poll_fields", value=poll_fields), + "max_results": max_results, + "pagination_token": pagination_token, + } + return self._get( + url=f"{self.BASE_URL_V2}/tweets/{tweet_id}/retweets", + params=args, + cls=md.Tweet, + multi=True, + return_json=return_json, + ) + def get_tweet_retweeted_users( self, tweet_id: str, diff --git a/pytwitter/rate_limit.py b/pytwitter/rate_limit.py index d7a5dbb..4a72604 100644 --- a/pytwitter/rate_limit.py +++ b/pytwitter/rate_limit.py @@ -99,6 +99,12 @@ def get_limit(self, auth_type, method="GET"): LIMIT_USER_GET=75, LIMIT_APP_GET=75, ) +TWEET_RETWEET_TWEETS = Endpoint( + resource="/tweets/:id/retweets", + regex=re.compile(r"/tweets/\d+/retweets"), + LIMIT_USER_GET=75, + LIMIT_APP_GET=75, +) USER_TWEET_RETWEET = Endpoint( resource="/users/:id/retweets", regex=re.compile(r"/users/\d+/retweets"), @@ -357,6 +363,7 @@ def get_limit(self, auth_type, method="GET"): regex=re.compile(r"/dm_conversations"), LIMIT_APP_GET=200, ) + MEDIA_UPLOAD = Endpoint( resource="/media/upload.json", regex=re.compile(r"/media/upload.json"), From fb13504f29c9a74e15c7ea5013659e214b6ae11d Mon Sep 17 00:00:00 2001 From: ikaroskun Date: Tue, 21 Nov 2023 19:47:49 +0800 Subject: [PATCH 2/3] test(tweets): :white_check_mark: Add tests to get tweet retweets --- pytwitter/api.py | 2 +- .../apis/tweet/tweet_retweet_tweets_resp.json | 1 + tests/apis/test_tweets.py | 34 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 testdata/apis/tweet/tweet_retweet_tweets_resp.json diff --git a/pytwitter/api.py b/pytwitter/api.py index 8b546e5..7430acc 100644 --- a/pytwitter/api.py +++ b/pytwitter/api.py @@ -1272,7 +1272,7 @@ def get_tweet_quote_tweets( return_json=return_json, ) - def get_tweet_retweet_tweets( + def get_tweet_retweeted_tweets( self, tweet_id: str, *, diff --git a/testdata/apis/tweet/tweet_retweet_tweets_resp.json b/testdata/apis/tweet/tweet_retweet_tweets_resp.json new file mode 100644 index 0000000..518ebbd --- /dev/null +++ b/testdata/apis/tweet/tweet_retweet_tweets_resp.json @@ -0,0 +1 @@ +{"data":[{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":0},"created_at":"2023-11-16T02:20:14.000Z","edit_history_tweet_ids":["1724975633908789418"],"author_id":"1220870789794353155","id":"1724975633908789418","conversation_id":"1724975633908789418","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"},{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":0},"created_at":"2023-11-15T15:02:58.000Z","edit_history_tweet_ids":["1724805194280730931"],"author_id":"1703568081849913344","id":"1724805194280730931","conversation_id":"1724805194280730931","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"},{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":0},"created_at":"2023-11-15T14:26:42.000Z","edit_history_tweet_ids":["1724796066238018027"],"author_id":"1508209224643649537","id":"1724796066238018027","conversation_id":"1724796066238018027","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"},{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":0},"created_at":"2023-11-14T19:45:14.000Z","edit_history_tweet_ids":["1724513843391627715"],"author_id":"1520172548704522242","id":"1724513843391627715","conversation_id":"1724513843391627715","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"},{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":0},"created_at":"2023-11-11T10:27:12.000Z","edit_history_tweet_ids":["1723286245676322909"],"author_id":"1597464349","id":"1723286245676322909","conversation_id":"1723286245676322909","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"},{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":0},"created_at":"2023-11-10T19:10:57.000Z","edit_history_tweet_ids":["1723055663817818369"],"author_id":"1634677469629300737","id":"1723055663817818369","conversation_id":"1723055663817818369","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"},{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":2},"created_at":"2023-11-05T15:05:53.000Z","edit_history_tweet_ids":["1721182049565081649"],"author_id":"1580496415","id":"1721182049565081649","conversation_id":"1721182049565081649","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"},{"public_metrics":{"retweet_count":49,"reply_count":0,"like_count":0,"quote_count":0,"bookmark_count":0,"impression_count":0},"created_at":"2023-11-04T13:56:26.000Z","edit_history_tweet_ids":["1720802185506881778"],"author_id":"1543819489367666688","id":"1720802185506881778","conversation_id":"1720802185506881778","text":"RT @XDevelopers: You can now monitor your usage programmatically using the new Usage endpoint in the X API v2 🎉\n\nWe are working on new feat…"}],"includes":{"users":[{"created_at":"2020-01-25T00:47:45.000Z","username":"365Pathway","name":"Pathway 365","id":"1220870789794353155"},{"created_at":"2023-09-18T00:34:33.000Z","username":"dodemoeewaa","name":"ヤル気出ない𑁍𓏸𓈒#音楽(❁︎´꒳`❁︎)好き","id":"1703568081849913344"},{"created_at":"2022-03-27T22:28:16.000Z","username":"XPL1999","name":"XPL.779 🇻🇳","id":"1508209224643649537"},{"created_at":"2022-04-29T22:46:30.000Z","username":"apoolofthoughts","name":"Old Bird","id":"1520172548704522242"},{"created_at":"2013-07-16T03:47:14.000Z","username":"gurinder0005","name":"Ann 🔥🎊✨","id":"1597464349"},{"created_at":"2023-03-11T22:07:56.000Z","username":"2lidiaV","name":"lidia💙🌺🍭","id":"1634677469629300737"},{"created_at":"2013-07-09T14:08:07.000Z","username":"nurahmad73","name":"Monica","id":"1580496415"},{"created_at":"2022-07-04T04:50:33.000Z","username":"thePrajeet","name":"Prajeet","id":"1543819489367666688"}]},"meta":{"result_count":8,"next_token":"next_token"}} \ No newline at end of file diff --git a/tests/apis/test_tweets.py b/tests/apis/test_tweets.py index dc2b6a1..504d85d 100644 --- a/tests/apis/test_tweets.py +++ b/tests/apis/test_tweets.py @@ -200,6 +200,40 @@ def test_get_tweet_quote_tweets(api, helpers): assert resp.includes.users[0].id == "29757971" +@responses.activate +def test_get_tweet_retweeted_tweets(api, helpers): + tweets_data = helpers.load_json_data( + "testdata/apis/tweet/tweet_retweet_tweets_resp.json" + ) + tweet_id = "1720506615714213927" + responses.add( + responses.GET, + url=f"https://api.twitter.com/2/tweets/{tweet_id}/retweets", + json=tweets_data, + ) + + resp = api.get_tweet_retweeted_tweets( + tweet_id=tweet_id, + expansions=["author_id"], + tweet_fields=[ + "created_at", + "author_id", + "conversation_id", + "public_metrics", + ], + user_fields=[ + "id", + "name", + "username", + "created_at", + ], + max_results=10, + ) + assert len(resp.data) == 8 + assert resp.data[0].id == "1724975633908789418" + assert resp.includes.users[0].id == "1220870789794353155" + + @responses.activate def test_get_tweets_count(api, helpers): recent_counts_data = helpers.load_json_data( From c75c981dc4847a9e4efacea138f8852bd9afe7e2 Mon Sep 17 00:00:00 2001 From: ikaroskun Date: Tue, 21 Nov 2023 19:51:05 +0800 Subject: [PATCH 3/3] docs(tweets): :white_check_mark: Add docs to get tweet retweets --- README.rst | 1 + docs/docs/usage/tweets/retweet.md | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.rst b/README.rst index 15c7037..6fa5a40 100644 --- a/README.rst +++ b/README.rst @@ -88,6 +88,7 @@ Now covers these features: - Tweet lookup - Manage Tweets - Quote Tweets + - Retweet Tweets - Timelines - Search Tweets - Tweet counts diff --git a/docs/docs/usage/tweets/retweet.md b/docs/docs/usage/tweets/retweet.md index df2f0ba..9f3017c 100644 --- a/docs/docs/usage/tweets/retweet.md +++ b/docs/docs/usage/tweets/retweet.md @@ -27,3 +27,10 @@ my_api.remove_retweet_tweet(user_id=my_api.auth_user_id, tweet_id="target tweet api.get_tweet_retweeted_users(tweet_id="target tweet id") # Response(data=[User(id='1301152652357595137', name='realllkk520', username='realllkk520')]) ``` + +## Tweet retweeted tweets + +```python +api.get_tweet_retweeted_tweets(tweet_id="target tweet id") +# Response(data=[Tweet(id=1724975633908789418, text=RT @XDevelopers: You can now monitor your usage...), Tweet(id=1724805194280730931, text=RT @XDevelopers: You can now monitor your usage...)]) +``` \ No newline at end of file