Add Client.unretweet, Client.get_retweeters, and Client.retweet
authorHarmon <Harmon758@gmail.com>
Mon, 20 Sep 2021 22:45:53 +0000 (17:45 -0500)
committerHarmon <Harmon758@gmail.com>
Mon, 20 Sep 2021 22:45:53 +0000 (17:45 -0500)
cassettes/test_get_retweeters.yaml [new file with mode: 0644]
cassettes/test_retweet_and_unretweet.yaml [new file with mode: 0644]
docs/client.rst
tests/test_client.py
tweepy/client.py

diff --git a/cassettes/test_get_retweeters.yaml b/cassettes/test_get_retweeters.yaml
new file mode 100644 (file)
index 0000000..5098367
--- /dev/null
@@ -0,0 +1,85 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Accept:
+      - '*/*'
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      User-Agent:
+      - Python/3.9.6 Requests/2.25.1 Tweepy/4.0.0-alpha
+    method: GET
+    uri: https://api.twitter.com/2/tweets/1415348607813832708/retweeted_by
+  response:
+    body:
+      string: !!binary |
+        H4sIAAAAAAAAAIxWTW8cRRD9K5PlmkP39Hdu6107IbZDsJ1ICNCqvdPemXhmejMfJpvIEo4iRYAi
+        LkQCLghxAgmJ5LS5cOGPrPABOPEXqFnHmZoJSBxs9VZNv+6ufu9VPxpEtrKDax8+GiTR4NqAcqaZ
+        ZMIoHmqhQirV4Oogt5mDZGKzsi4WFiJ16YpuVFESEqHZ4PTqJRbVQhtKEMLYHdoyLpwLbtsq6eJE
+        k3kTezNdh4yxUITt7Fvex8Etb+PuxCp2OQRzyKLFeWi4CYWQioSKMEnRNt6vfZXkkSu6QPffhFsY
+        xhQRlLKQUSa1JFS2MMMoqRY22LOf2KqLNCovchep9khUUGm4lNQY2JPAh9vxJy7YT7J5mkxhahev
+        XMePFjxzNMQlhkNqRTTX1ChljGFoe/txcs9WcTBMe5V+nRh2qiWkZISEWioOhaOixbld2KOjOg32
+        Y1tkvcuP4TpdPvXdovGQG0WZElqHkilG0Tn9cR3ZSeROukBtGMOEXHMpQqaJ4IIw0sLM/fTYVSeu
+        KF0XaIIzqFCcCsOIUMxwxjVhvMXaBt71UNYhqkk5LZI53MYbICi4klRKxdDV1VNbduenTcjP51Ms
+        B2FkqJg07czzs8fnZ1+fnz0/P3vSBbgxm8S2qjplBUFxqYE/ijHClaZoD6vlN6vly9Xy6Wr53erV
+        Z8341ePV8ovV8isYf1SPNduE/2O9gcfdNR8sJqV7sCh96bECFPBfGxMaIrkAsqH9v7Na/rBavlgt
+        v12v/HK9i59Xy+9Xy+er5S/rn0/gm+46N21ZPnTHFi1CQgnCgAq12De9C/aSae9i7vnSzeO0aBLt
+        pQgZGlCW0Ij9NrVxsD7sqDnslkBj3Y43xyhuUHz4P+Kqu7ls0QySsmzWxuTTjFEoH6GUU26YQird
+        rWObZTYKtmxyvwu324T2kUxDqbTSzGikpz3v54WrXDoFnB4Jk2M3OXaLQ2+LqEUBW5Sk0ZbC1cpj
+        H4ziOp8Ffz379Y+nn//54qeeBzWfTJsvWigmgJLKaIQ0r/Oqb6yuTIDLvsgdUhIjWkHTkLSdu2Xj
+        JAtuAD1s/pbPQOyoyaO6Ki6bDsWRWWVJ8bDnUVkdR+tw9hDrGIpJwDlRc7LgwCNbFD5N3+owESSn
+        r3NIG41BQaMSinJQNtgx6g6pzYMd1+MvMCM/dHnqEH059FqwJs6Rvw3zqPjtx+Cug33b3naanGOX
+        KbQbE3JGFJGaKPBdsE1EExvV/aJexMCggVOE4ZsBdsB0TI8ytsHYp7MFktQmlpFM8v/IjHvdZ/ze
+        zvUP0OnB0ohiHGv/xsKVzgMXt5Mep7eLkc9cha3DEDi5NKh4OXQ3myY9HlxG52mJi0Yo9CmODxvs
+        Brs+ToI7ZbBf5zANn1mhsekpZP0xWFHHs4GfDNo9iCSEVo25eujsEWglg311gXCihYKnTCigWi3A
+        9SQF47gOrS72ea/Jz5rc7DKFNCOVgBcRJuphfa9u/oINm1e+r7x6FmeFRSXjUG4CzzoknTtxHcV1
+        7z1n82mSz5ISGB+VHTfU4PcUGimjhAAWco+DJOtdeBOZHDo3RxsgQgitJDawW7ZyQdvamMB3NsL3
+        9y+kyMssqWJwI/y00mDZoCZUqN0LzKZhjIxC4w00Nu3CW1vs7+WXv3/6rI0QHWzsDEfbwc67dzeB
+        aMODg829K1eu9Iw7s94f2wW8q8EmPr46AMbDI/3RoHBlnVaTqQeTHVxj+vT0HwAAAP//AwAYEu+6
+        wQsAAA==
+    headers:
+      api-version:
+      - '2.24'
+      cache-control:
+      - no-cache, no-store, max-age=0
+      content-disposition:
+      - attachment; filename=json.json
+      content-encoding:
+      - gzip
+      content-length:
+      - '1315'
+      content-type:
+      - application/json; charset=utf-8
+      date:
+      - Mon, 20 Sep 2021 22:41:51 UTC
+      server:
+      - tsa_b
+      set-cookie:
+      - personalization_id="v1_Qr440OWOLwfIbswvEJOdCg=="; Max-Age=63072000; Expires=Wed,
+        20 Sep 2023 22:41:51 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - guest_id=v1%3A163217771075899222; Max-Age=63072000; Expires=Wed, 20 Sep 2023
+        22:41:51 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      strict-transport-security:
+      - max-age=631138519
+      x-access-level:
+      - read
+      x-connection-hash:
+      - 78f0efa18b98c6b7ef15b5fd993ba99363018161b418a3344e99dc068f5c8571
+      x-content-type-options:
+      - nosniff
+      x-frame-options:
+      - SAMEORIGIN
+      x-rate-limit-limit:
+      - '75'
+      x-rate-limit-remaining:
+      - '73'
+      x-rate-limit-reset:
+      - '1632178559'
+      x-xss-protection:
+      - '0'
+    status:
+      code: 200
+      message: OK
+version: 1
diff --git a/cassettes/test_retweet_and_unretweet.yaml b/cassettes/test_retweet_and_unretweet.yaml
new file mode 100644 (file)
index 0000000..510c79f
--- /dev/null
@@ -0,0 +1,125 @@
+interactions:
+- request:
+    body: '{"tweet_id": "1415348607813832708"}'
+    headers:
+      Accept:
+      - '*/*'
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      Content-Length:
+      - '35'
+      Content-Type:
+      - application/json
+      User-Agent:
+      - Python/3.9.6 Requests/2.25.1 Tweepy/4.0.0-alpha
+    method: POST
+    uri: https://api.twitter.com/2/users/1072250532645998596/retweets
+  response:
+    body:
+      string: !!binary |
+        H4sIAAAAAAAAAKpWSkksSVSyqlYqSi0pT00tSU1RsiopKk2trQUAAAD//wMAkF+QNhsAAAA=
+    headers:
+      api-version:
+      - '2.24'
+      cache-control:
+      - no-cache, no-store, max-age=0
+      content-disposition:
+      - attachment; filename=json.json
+      content-encoding:
+      - gzip
+      content-length:
+      - '53'
+      content-type:
+      - application/json; charset=utf-8
+      date:
+      - Mon, 20 Sep 2021 22:41:52 UTC
+      server:
+      - tsa_b
+      set-cookie:
+      - personalization_id="v1_Rj7jnMbi1jj2et9ubmiFRg=="; Max-Age=63072000; Expires=Wed,
+        20 Sep 2023 22:41:52 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - guest_id=v1%3A163217771171423840; Max-Age=63072000; Expires=Wed, 20 Sep 2023
+        22:41:52 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      strict-transport-security:
+      - max-age=631138519
+      x-access-level:
+      - read-write-directmessages
+      x-connection-hash:
+      - 568f3d770dd0d22fc050812841b57078977d685822883820ef842162c3a5c5ba
+      x-content-type-options:
+      - nosniff
+      x-frame-options:
+      - SAMEORIGIN
+      x-rate-limit-limit:
+      - '50'
+      x-rate-limit-remaining:
+      - '48'
+      x-rate-limit-reset:
+      - '1632178531'
+      x-xss-protection:
+      - '0'
+    status:
+      code: 200
+      message: OK
+- request:
+    body: null
+    headers:
+      Accept:
+      - '*/*'
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      Content-Length:
+      - '0'
+      Cookie:
+      - guest_id=v1%3A163217771171423840; personalization_id="v1_Rj7jnMbi1jj2et9ubmiFRg=="
+      User-Agent:
+      - Python/3.9.6 Requests/2.25.1 Tweepy/4.0.0-alpha
+    method: DELETE
+    uri: https://api.twitter.com/2/users/1072250532645998596/retweets/1415348607813832708
+  response:
+    body:
+      string: !!binary |
+        H4sIAAAAAAAAAKpWSkksSVSyqlYqSi0pT00tSU1RskpLzClOra0FAAAA//8DAL0TO3McAAAA
+    headers:
+      api-version:
+      - '2.24'
+      cache-control:
+      - no-cache, no-store, max-age=0
+      content-disposition:
+      - attachment; filename=json.json
+      content-encoding:
+      - gzip
+      content-length:
+      - '54'
+      content-type:
+      - application/json; charset=utf-8
+      date:
+      - Mon, 20 Sep 2021 22:41:52 UTC
+      server:
+      - tsa_b
+      strict-transport-security:
+      - max-age=631138519
+      x-access-level:
+      - read-write-directmessages
+      x-connection-hash:
+      - 568f3d770dd0d22fc050812841b57078977d685822883820ef842162c3a5c5ba
+      x-content-type-options:
+      - nosniff
+      x-frame-options:
+      - SAMEORIGIN
+      x-rate-limit-limit:
+      - '50'
+      x-rate-limit-remaining:
+      - '48'
+      x-rate-limit-reset:
+      - '1632178543'
+      x-xss-protection:
+      - '0'
+    status:
+      code: 200
+      message: OK
+version: 1
index acfb2e0bdd35621f9b34d94d74d61e5e158fb3c1..4fd54be31a14ff64c7785d1f604146752452182e 100644 (file)
@@ -29,6 +29,15 @@ Likes
 
 .. automethod:: Client.like
 
+Retweets
+--------
+
+.. automethod:: Client.unretweet
+
+.. automethod:: Client.get_retweeters
+
+.. automethod:: Client.retweet
+
 Search Tweets
 -------------
 
index b052dc71fba403a84efa1b86f0bad0ccc304b489..8f6bf545cf633a387847142b88f919331683cc7c 100644 (file)
@@ -36,6 +36,17 @@ class TweepyTestCase(unittest.TestCase):
         user_id = 783214  # User ID for @Twitter
         self.client.get_liked_tweets(user_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
+        self.client.retweet(tweet_id)
+        self.client.unretweet(tweet_id)
+
+    @tape.use_cassette("test_get_retweeters.yaml", serializer="yaml")
+    def test_get_retweeters(self):
+        tweet_id = 1415348607813832708  # @TwitterDev Tweet announcing API v2 Retweet endpoints
+        self.client.get_retweeters(tweet_id)
+
     # TODO: test_search_all_tweets with access to Academic Research product track
 
     @tape.use_cassette("test_search_recent_tweets.yaml", serializer="yaml")
index 21086e22cb9f8cb1d762aacd495fbbe5292a9aeb..53ad74b54a1fdc26473a14379e57bbac5d2e20fb 100644 (file)
@@ -380,6 +380,95 @@ class Client:
             "POST", route, json={"tweet_id": str(tweet_id)}, user_auth=True
         )
 
+    # Retweets
+
+    def unretweet(self, source_tweet_id):
+        """Allows an authenticated user ID to remove the Retweet of a Tweet.
+
+        The request succeeds with no action when the user sends a request to a
+        user they're not Retweeting the Tweet or have already removed the
+        Retweet of.
+
+        Parameters
+        ----------
+        source_tweet_id : Union[int, str]
+            The ID of the Tweet that you would like to remove the Retweet of.
+
+        Returns
+        -------
+        Union[dict, requests.Response, Response]
+
+        References
+        ----------
+        https://developer.twitter.com/en/docs/twitter-api/tweets/retweets/api-reference/delete-users-id-retweets-tweet_id
+        """
+        id = self.access_token.partition('-')[0]
+        route = f"/2/users/{id}/retweets/{source_tweet_id}"
+
+        return self._make_request("DELETE", route, user_auth=True)
+
+    def get_retweeters(self, id, *, user_auth=False, **params):
+        """get_retweeters(id, *, expansions, media_fields, place_fields, \
+                          poll_fields, tweet_fields, user_fields)
+
+        Allows you to get information about who has Retweeted a Tweet.
+
+        Parameters
+        ----------
+        id : Union[int, str]
+            Tweet ID of the Tweet to request Retweeting users of.
+        expansions : Union[List[str], str]
+            :ref:`expansions_parameter`
+        media_fields : Union[List[str], str]
+            :ref:`media_fields_parameter`
+        place_fields : Union[List[str], str]
+            :ref:`place_fields_parameter`
+        poll_fields : Union[List[str], str]
+            :ref:`poll_fields_parameter`
+        tweet_fields : Union[List[str], str]
+            :ref:`tweet_fields_parameter`
+        user_fields : Union[List[str], str]
+            :ref:`user_fields_parameter`
+
+        Returns
+        -------
+        Union[dict, requests.Response, Response]
+
+        References
+        ----------
+        https://developer.twitter.com/en/docs/twitter-api/tweets/retweets/api-reference/get-tweets-id-retweeted_by
+        """
+        return self._make_request(
+            "GET", f"/2/tweets/{id}/retweeted_by", params=params,
+            endpoint_parameters=(
+                "expansions", "media.fields", "place.fields", "poll.fields",
+                "tweet.fields", "user.fields"
+            ), data_type=User, user_auth=user_auth
+        )
+
+    def retweet(self, tweet_id):
+        """Causes the user ID to Retweet the target Tweet.
+
+        Parameters
+        ----------
+        tweet_id : Union[int, str]
+            The ID of the Tweet that you would like to Retweet.
+
+        Returns
+        -------
+        Union[dict, requests.Response, Response]
+
+        References
+        ----------
+        https://developer.twitter.com/en/docs/twitter-api/tweets/retweets/api-reference/post-users-id-retweets
+        """
+        id = self.access_token.partition('-')[0]
+        route = f"/2/users/{id}/retweets"
+
+        return self._make_request(
+            "POST", route, json={"tweet_id": str(tweet_id)}, user_auth=True
+        )
+
     # Search Tweets
 
     def search_all_tweets(self, query, **params):