Skip to content

Commit

Permalink
Add support for managing Tweets with API v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Harmon758 committed Nov 3, 2021
1 parent f653e53 commit 7884e3a
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 0 deletions.
132 changes: 132 additions & 0 deletions cassettes/test_create_and_delete_tweet.yaml
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
+--------------------------------------------------------------+----------------------------------------+
| `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` |
Expand Down Expand Up @@ -156,6 +162,9 @@
.. _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
Expand Down Expand Up @@ -232,6 +241,13 @@ Likes

.. automethod:: Client.like

Manage Tweets
-------------

.. automethod:: Client.delete_tweet

.. automethod:: Client.create_tweet

Retweets
--------

Expand Down
6 changes: 6 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ def test_get_liked_tweets(self):
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
Expand Down
128 changes: 128 additions & 0 deletions tweepy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,134 @@ def like(self, tweet_id):
"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):
Expand Down

0 comments on commit 7884e3a

Please sign in to comment.