diff --git a/twikit/__init__.py b/twikit/__init__.py index f77bb128..082bc822 100644 --- a/twikit/__init__.py +++ b/twikit/__init__.py @@ -7,7 +7,7 @@ A Python library for interacting with the Twitter API. """ -__version__ = '2.2.0' +__version__ = '2.2.1' import asyncio import os diff --git a/twikit/client/client.py b/twikit/client/client.py index ee03690e..6da42a6f 100644 --- a/twikit/client/client.py +++ b/twikit/client/client.py @@ -90,7 +90,7 @@ class Client: def __init__( self, - language: str | None = None, + language: str = 'en-US', proxy: str | None = None, captcha_solver: Capsolver | None = None, user_agent: str | None = None, @@ -1620,6 +1620,34 @@ async def get_tweet_by_id( return tweet + async def get_tweets_by_ids(self, ids: list[str]) -> list[Tweet]: + """ + Retrieve multiple tweets by IDs. + + Parameters + ---------- + ids : list[:class:`str`] + A list of tweet IDs to retrieve. + + Returns + ------- + list[:class:`Tweet`] + List of tweets. + + Examples + -------- + >>> tweet_ids = ['1111111111', '1111111112', '111111113'] + >>> tweets = await client.get_tweets_by_ids(tweet_ids) + >>> print(tweets) + [, , ] + """ + response, _ = await self.gql.tweet_results_by_rest_ids(ids) + tweet_results = response['data']['tweetResult'] + results = [] + for tweet_result in tweet_results: + results.append(tweet_from_data(self, tweet_result)) + return results + async def get_scheduled_tweets(self) -> list[ScheduledTweet]: """ Retrieves scheduled tweets. diff --git a/twikit/client/gql.py b/twikit/client/gql.py index 8fd27e40..54d7d570 100644 --- a/twikit/client/gql.py +++ b/twikit/client/gql.py @@ -13,6 +13,7 @@ NOTE_TWEET_FEATURES, SIMILAR_POSTS_FEATURES, TWEET_RESULT_BY_REST_ID_FEATURES, + TWEET_RESULTS_BY_REST_IDS_FEATURES, USER_FEATURES, USER_HIGHLIGHTS_TWEETS_FEATURES ) @@ -97,6 +98,7 @@ def url(path): MEMBERS_SLICE_TIMELINE_QUERY = url('KDAssJ5lafCy-asH4wm1dw/membersSliceTimeline_Query') MODERATORS_SLICE_TIMELINE_QUERY = url('9KI_r8e-tgp3--N5SZYVjg/moderatorsSliceTimeline_Query') COMMUNITY_TWEET_SEARCH_MODULE_QUERY = url('5341rmzzvdjqfmPKfoHUBw/CommunityTweetSearchModuleQuery') + TWEET_RESULTS_BY_REST_IDS = url('PTN9HhBAlpoCTHfspDgqLA/TweetResultsByRestIds') class GQLClient: @@ -670,6 +672,16 @@ async def community_tweet_search_module_query(self, community_id, query, count, variables['cursor'] = cursor return await self.gql_get(Endpoint.COMMUNITY_TWEET_SEARCH_MODULE_QUERY, variables, COMMUNITY_TWEETS_FEATURES) + async def tweet_results_by_rest_ids(self, tweet_ids): + variables = { + 'tweetIds': tweet_ids, + 'includePromotedContent': True, + 'withBirdwatchNotes': True, + 'withVoice': True, + 'withCommunity': True + } + return await self.gql_get(Endpoint.TWEET_RESULTS_BY_REST_IDS, variables, TWEET_RESULTS_BY_REST_IDS_FEATURES) + #################### # For guest client #################### diff --git a/twikit/constants.py b/twikit/constants.py index 277be9f3..3d7dbb28 100644 --- a/twikit/constants.py +++ b/twikit/constants.py @@ -227,3 +227,34 @@ 'longform_notetweets_inline_media_enabled': True, 'responsive_web_enhance_cards_enabled': False } + +TWEET_RESULTS_BY_REST_IDS_FEATURES = { + 'creator_subscriptions_tweet_preview_api_enabled': True, + 'premium_content_api_read_enabled': False, + 'communities_web_enable_tweet_community_results_fetch': True, + 'c9s_tweet_anatomy_moderator_badge_enabled': True, + 'responsive_web_grok_analyze_button_fetch_trends_enabled': False, + 'responsive_web_grok_analyze_post_followups_enabled': True, + 'responsive_web_grok_share_attachment_enabled': True, + 'articles_preview_enabled': True, + 'responsive_web_edit_tweet_api_enabled': True, + 'graphql_is_translatable_rweb_tweet_is_translatable_enabled': True, + 'view_counts_everywhere_api_enabled': True, + 'longform_notetweets_consumption_enabled': True, + 'responsive_web_twitter_article_tweet_consumption_enabled': True, + 'tweet_awards_web_tipping_enabled': False, + 'creator_subscriptions_quote_tweet_preview_enabled': False, + 'freedom_of_speech_not_reach_fetch_enabled': True, + 'standardized_nudges_misinfo': True, + 'tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled': True, + 'rweb_video_timestamps_enabled': True, + 'longform_notetweets_rich_text_read_enabled': True, + 'longform_notetweets_inline_media_enabled': True, + 'profile_label_improvements_pcf_label_in_post_enabled': False, + 'rweb_tipjar_consumption_enabled': True, + 'responsive_web_graphql_exclude_directive_enabled': True, + 'verified_phone_label_enabled': False, + 'responsive_web_graphql_skip_user_profile_image_extensions_enabled': False, + 'responsive_web_graphql_timeline_navigation_enabled': True, + 'responsive_web_enhance_cards_enabled': False +}