Add support for managing Tweets with API v2
authorHarmon <Harmon758@gmail.com>
Wed, 3 Nov 2021 21:48:57 +0000 (16:48 -0500)
committerHarmon <Harmon758@gmail.com>
Wed, 3 Nov 2021 21:48:57 +0000 (16:48 -0500)
cassettes/test_create_and_delete_tweet.yaml [new file with mode: 0644]
docs/client.rst
tests/test_client.py
tweepy/client.py

diff --git a/cassettes/test_create_and_delete_tweet.yaml b/cassettes/test_create_and_delete_tweet.yaml
new file mode 100644 (file)
index 0000000..2c70562
--- /dev/null
@@ -0,0 +1,132 @@
+interactions:
+- request:
+    body: '{"text": "Test Tweet"}'
+    headers:
+      Accept:
+      - '*/*'
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      Content-Length:
+      - '22'
+      Content-Type:
+      - application/json
+      User-Agent:
+      - Python/3.10.0 Requests/2.26.0 Tweepy/4.2.0
+    method: POST
+    uri: https://api.twitter.com/2/tweets
+  response:
+    body:
+      string: !!binary |
+        H4sIAAAAAAAAAKpWSkksSVSyqlbKTFGyUjI0MTUzMDSyNDe2MDY1t7AwsjRW0lEqSa0oAUqGpBaX
+        KISUp6aWKNXWAgAAAP//AwBe5WlnOQAAAA==
+    headers:
+      api-version:
+      - '2.28'
+      cache-control:
+      - no-cache, no-store, max-age=0
+      content-disposition:
+      - attachment; filename=json.json
+      content-encoding:
+      - gzip
+      content-length:
+      - '82'
+      content-type:
+      - application/json; charset=utf-8
+      date:
+      - Wed, 03 Nov 2021 21:38:39 UTC
+      location:
+      - https://api.twitter.com/2/tweets/1456012973835788293
+      server:
+      - tsa_b
+      set-cookie:
+      - personalization_id="v1_E3WlXXfQ1xB5rs0qHbxukQ=="; Max-Age=63072000; Expires=Fri,
+        03 Nov 2023 21:38:39 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - guest_id=v1%3A163597551896174473; Max-Age=63072000; Expires=Fri, 03 Nov 2023
+        21:38:39 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      strict-transport-security:
+      - max-age=631138519
+      x-access-level:
+      - read-write-directmessages
+      x-connection-hash:
+      - f86af2f37d975724d196465ae7a8bb5b420f7b2bb1b541276f76c3fe0569779e
+      x-content-type-options:
+      - nosniff
+      x-frame-options:
+      - SAMEORIGIN
+      x-rate-limit-limit:
+      - '200'
+      x-rate-limit-remaining:
+      - '199'
+      x-rate-limit-reset:
+      - '1635976418'
+      x-response-time:
+      - '226'
+      x-xss-protection:
+      - '0'
+    status:
+      code: 201
+      message: Created
+- request:
+    body: null
+    headers:
+      Accept:
+      - '*/*'
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      Content-Length:
+      - '0'
+      Cookie:
+      - guest_id=v1%3A163597551896174473; personalization_id="v1_E3WlXXfQ1xB5rs0qHbxukQ=="
+      User-Agent:
+      - Python/3.10.0 Requests/2.26.0 Tweepy/4.2.0
+    method: DELETE
+    uri: https://api.twitter.com/2/tweets/1456012973835788293
+  response:
+    body:
+      string: !!binary |
+        H4sIAAAAAAAAAKpWSkksSVSyqlZKSc1JLUlNUbIqKSpNra0FAAAA//8DAD5d+0oZAAAA
+    headers:
+      api-version:
+      - '2.28'
+      cache-control:
+      - no-cache, no-store, max-age=0
+      content-disposition:
+      - attachment; filename=json.json
+      content-encoding:
+      - gzip
+      content-length:
+      - '51'
+      content-type:
+      - application/json; charset=utf-8
+      date:
+      - Wed, 03 Nov 2021 21:38:39 UTC
+      server:
+      - tsa_b
+      strict-transport-security:
+      - max-age=631138519
+      x-access-level:
+      - read-write-directmessages
+      x-connection-hash:
+      - f86af2f37d975724d196465ae7a8bb5b420f7b2bb1b541276f76c3fe0569779e
+      x-content-type-options:
+      - nosniff
+      x-frame-options:
+      - SAMEORIGIN
+      x-rate-limit-limit:
+      - '50'
+      x-rate-limit-remaining:
+      - '49'
+      x-rate-limit-reset:
+      - '1635976419'
+      x-response-time:
+      - '44'
+      x-xss-protection:
+      - '0'
+    status:
+      code: 200
+      message: OK
+version: 1
index b809647d256230a61983c590fe9e8b641d61b32b..5665dd3326f2c882cdd2cc5c6f8019ddb23580f2 100644 (file)
     +--------------------------------------------------------------+----------------------------------------+
     | `POST /2/users/:id/likes`_                                   | :meth:`Client.like`                    |
     +--------------------------------------------------------------+----------------------------------------+
+    | .. centered:: |Manage Tweets|_                                                                        |
+    +--------------------------------------------------------------+----------------------------------------+
+    | `DELETE /2/tweets/:id`_                                      | :meth:`Client.delete_tweet`            |
+    +--------------------------------------------------------------+----------------------------------------+
+    | `POST /2/tweets`_                                            | :meth:`Client.create_tweet`            |
+    +--------------------------------------------------------------+----------------------------------------+
     | .. centered:: |Retweets|_                                                                             |
     +--------------------------------------------------------------+----------------------------------------+
     | `DELETE /2/users/:id/retweets/:source_tweet_id`_             | :meth:`Client.unretweet`               |
 .. _GET /2/tweets/:id/liking_users: https://developer.twitter.com/en/docs/twitter-api/tweets/likes/api-reference/get-tweets-id-liking_users
 .. _GET /2/users/:id/liked_tweets: https://developer.twitter.com/en/docs/twitter-api/tweets/likes/api-reference/get-users-id-liked_tweets
 .. _POST /2/users/:id/likes: https://developer.twitter.com/en/docs/twitter-api/tweets/likes/api-reference/post-users-id-likes
+.. |Manage Tweets| replace:: *Manage Tweets*
+.. _DELETE /2/tweets/:id: https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/delete-tweets-id
+.. _POST /2/tweets: https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets
 .. |Retweets| replace:: *Retweets*
 .. _DELETE /2/users/:id/retweets/:source_tweet_id: https://developer.twitter.com/en/docs/twitter-api/tweets/retweets/api-reference/delete-users-id-retweets-tweet_id
 .. _GET /2/tweets/:id/retweeted_by: https://developer.twitter.com/en/docs/twitter-api/tweets/retweets/api-reference/get-tweets-id-retweeted_by
@@ -232,6 +241,13 @@ Likes
 
 .. automethod:: Client.like
 
+Manage Tweets
+-------------
+
+.. automethod:: Client.delete_tweet
+
+.. automethod:: Client.create_tweet
+
 Retweets
 --------
 
index d92c7ec938087329b78c7105d9cd3229cdfeab94..499912e79f0d478a995d7cc6969c0662330e2ef4 100644 (file)
@@ -37,6 +37,12 @@ class TweepyTestCase(unittest.TestCase):
         user_id = 783214  # User ID for @Twitter
         self.client.get_liked_tweets(user_id)
 
+    @tape.use_cassette("test_create_and_delete_tweet.yaml", serializer="yaml")
+    def test_create_and_delete_tweet(self):
+        response = self.client.create_tweet(text="Test Tweet")
+        tweet_id = response.data["id"]
+        self.client.delete_tweet(tweet_id)
+
     @tape.use_cassette("test_retweet_and_unretweet.yaml", serializer="yaml")
     def test_retweet_and_unretweet(self):
         tweet_id = 1415348607813832708  # @TwitterDev Tweet announcing API v2 Retweet endpoints
index bd23dc81866a6e1950030ee1920eb7ed3ea01c38..4a32bd73c30acf65a7f012cbbbfbd64e9f95f7a2 100644 (file)
@@ -390,6 +390,134 @@ class Client:
             "POST", route, json={"tweet_id": str(tweet_id)}, user_auth=True
         )
 
+    # Manage Tweets
+
+    def delete_tweet(self, id):
+        """Allows an authenticated user ID to delete a Tweet.
+
+        Parameters
+        ----------
+        id : Union[int, str]
+            The Tweet ID you are deleting.
+
+        Returns
+        -------
+        Union[dict, requests.Response, Response]
+
+        References
+        ----------
+        https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/delete-tweets-id
+        """
+        return self._make_request(
+            "DELETE", f"/2/tweets/{id}", user_auth=True
+        )
+
+    def create_tweet(
+        self, *, direct_message_deep_link=None, for_super_followers_only=None,
+        place_id=None, media_ids=None, media_tagged_user_ids=None,
+        poll_duration_minutes=None, poll_options=None, quote_tweet_id=None,
+        exclude_reply_user_ids=None, in_reply_to_tweet_id=None,
+        reply_settings=None, text=None
+    ):
+        """Creates a Tweet on behalf of an authenticated user.
+
+        Parameters
+        ----------
+        direct_message_deep_link : Optional[str]
+            `Tweets a link directly to a Direct Message conversation`_ with an
+            account.
+        for_super_followers_only : Optional[bool]
+            Allows you to Tweet exclusively for `Super Followers`_.
+        place_id : Optional[int]
+            Place ID being attached to the Tweet for geo location.
+        media_ids : Optional[List[int, str]]
+            A list of Media IDs being attached to the Tweet. This is only
+            required if the request includes the ``tagged_user_ids``.
+        media_tagged_user_ids : Optional[Union[int, str]]
+            A list of User IDs being tagged in the Tweet with Media. If the
+            user you're tagging doesn't have photo-tagging enabled, their names
+            won't show up in the list of tagged users even though the Tweet is
+            successfully created.
+        poll_duration_minutes : Optional[int]
+            Duration of the poll in minutes for a Tweet with a poll. This is
+            only required if the request includes ``poll.options``.
+        poll_options : Optional[List[str]]
+            A list of poll options for a Tweet with a poll.
+        quote_tweet_id : Optional[Union[int, str]]
+            Link to the Tweet being quoted.
+        exclude_reply_user_ids : Optional[List[Union[int, str]]]
+            A list of User IDs to be excluded from the reply Tweet thus
+            removing a user from a thread.
+        in_reply_to_tweet_id : Optional[Union[int, str]]
+            Tweet ID of the Tweet being replied to. This is only required if
+            the request includes the ``reply.exclude_reply_user_ids``.
+        reply_settings : Optional[str]
+            `Settings`_ to indicate who can reply to the Tweet. Limited to
+            "mentionedUsers" and "following". If the field isn’t specified, it
+            will default to everyone.
+        text : Optional[str]
+            Text of the Tweet being created. This field is required if
+            ``media.media_ids`` is not present.
+
+        Returns
+        -------
+        Union[dict, requests.Response, Response]
+
+        References
+        ----------
+        https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets
+
+        .. _Tweets a link directly to a Direct Message conversation: https://business.twitter.com/en/help/campaign-editing-and-optimization/public-to-private-conversation.html
+        .. _Super Followers: https://help.twitter.com/en/using-twitter/super-follows
+        .. _Settings: https://blog.twitter.com/en_us/topics/product/2020/new-conversation-settings-coming-to-a-tweet-near-you
+        """
+        json = {}
+
+        if direct_message_deep_link is not None:
+            json["direct_message_deep_link"] = direct_message_deep_link
+
+        if for_super_followers_only is not None:
+            json["for_super_followers_only"] = for_super_followers_only
+
+        if place_id is not None:
+            json["geo"] = {"place_id": place_id}
+
+        if media_ids is not None:
+            json["media"] = {
+                "media_ids": [str(media_id) for media_id in media_ids]
+            }
+            if media_tagged_user_ids is not None:
+                json["media"]["tagged_user_ids"] = [
+                    str(media_tagged_user_id)
+                    for media_tagged_user_id in media_tagged_user_ids
+                ]
+
+        if poll_options is not None:
+            json["poll"] = {"options": poll_options}
+            if poll_duration_minutes is not None:
+                json["poll"]["duration_minutes"] = poll_duration_minutes
+
+        if quote_tweet_id is not None:
+            json["quote_tweet_id"] = str(quote_tweet_id)
+
+        if in_reply_to_tweet_id is not None:
+            json["reply"] = {"in_reply_to_tweet_id": str(in_reply_to_tweet_id)}
+            if exclude_reply_user_ids is not None:
+                json["reply"]["exclude_reply_user_ids"] = [
+                    str(exclude_reply_user_id)
+                    for exclude_reply_user_id in exclude_reply_user_ids
+                ]
+
+        if reply_settings is not None:
+            json["reply_settings"] = reply_settings
+
+        if text is not None:
+            json["text"] = text
+
+        return self._make_request(
+            "POST", f"/2/tweets", json=json, user_auth=True
+        )
+
     # Retweets
 
     def unretweet(self, source_tweet_id):