Skip to content

Commit df9a28f

Browse files
authored
Merge pull request #113 from MerleLiuKun/feat-bookmarks
Feat bookmarks
2 parents 1e0929f + 1aa82d3 commit df9a28f

File tree

7 files changed

+186
-4
lines changed

7 files changed

+186
-4
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Now cover features:
9595
- Volume streams
9696
- Retweets
9797
- Likes
98+
- Bookmarks
9899
- Hide replies
99100

100101
- Users
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Bookmarks are a core feature of the Twitter app that allows you to “save” Tweets and easily access them later. With these endpoints, you can retrieve, create, delete or build solutions to manage your Bookmarks via the API.
2+
3+
You can get more information for this at [docs](https://developer.twitter.com/en/docs/twitter-api/tweets/bookmarks/introduction)
4+
5+
## Manage Bookmarks
6+
7+
### Bookmark tweet
8+
9+
Causes the user ID of an authenticated user identified in the path parameter to Bookmark the target Tweet provided in the request body.
10+
11+
```python
12+
my_api.bookmark_tweet(user_id="1301152652357595137", tweet_id="1511645952418885636")
13+
# {'data': {'bookmarked': True}}
14+
```
15+
16+
### Remove bookmark tweet
17+
18+
Allows a user or authenticated user ID to remove a Bookmark of a Tweet.
19+
20+
```python
21+
my_api.bookmark_tweet_remove(user_id="1301152652357595137", tweet_id="1511645952418885636")
22+
# {'data': {'bookmarked': False}}
23+
```
24+
25+
## Bookmarks lookup
26+
27+
Allows you to get information about a authenticated user’s 800 most recent bookmarked Tweets.
28+
29+
```python
30+
my_api.get_bookmark_tweets(user_id="1301152652357595137")
31+
# Response(data=[Tweet(id=1511645952418885636, text=https://t.co/Hl9Sa0uP9W)])
32+
```

docs/mkdocs.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,22 @@ nav:
3737
- Tweets:
3838
- Tweet Lookup: usage/tweets/tweet-lookup.md
3939
- Manage Tweets: usage/tweets/tweet-manage.md
40-
- Quote Tweets: usage/tweets/qutoe_tweets.md
4140
- Timelines: usage/tweets/timelines.md
4241
- Search Tweets: usage/tweets/search_tweets.md
4342
- Tweet counts: usage/tweets/tweets_counts.md
44-
- Hide replies: usage/tweets/hide_reply.md
4543
- Retweets: usage/tweets/retweet.md
44+
- Quote Tweets: usage/tweets/qutoe_tweets.md
4645
- Likes: usage/tweets/likes.md
46+
- Bookmarks: usage/tweets/bookmarks.md
47+
- Hide replies: usage/tweets/hide_reply.md
4748
- Users:
4849
- Users Lookup: usage/users/user-lookup.md
4950
- Follows: usage/users/follows.md
5051
- Blocks: usage/users/blocks.md
5152
- Mutes: usage/users/mutes.md
5253
- Spaces:
5354
- Spaces Lookup: usage/spaces/spaces-lookup.md
54-
- Spaces Search: usage/spaces/search.md
55+
- Search Spaces: usage/spaces/search.md
5556
- Lists:
5657
- List lookup: usage/lists/list-lookup.md
5758
- Manage Lists: usage/lists/manage-lists.md

pytwitter/api.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,97 @@ def unlike_tweet(self, user_id: str, tweet_id: str) -> dict:
11941194
data = self._parse_response(resp=resp)
11951195
return data
11961196

1197+
def get_bookmark_tweets(
1198+
self,
1199+
user_id: str,
1200+
*,
1201+
pagination_token: Optional[str] = None,
1202+
max_results: Optional[int] = None,
1203+
tweet_fields: Optional[Union[str, List, Tuple]] = None,
1204+
expansions: Optional[Union[str, List, Tuple]] = None,
1205+
user_fields: Optional[Union[str, List, Tuple]] = None,
1206+
media_fields: Optional[Union[str, List, Tuple]] = None,
1207+
place_fields: Optional[Union[str, List, Tuple]] = None,
1208+
poll_fields: Optional[Union[str, List, Tuple]] = None,
1209+
return_json: bool = False,
1210+
) -> Union[dict, md.Response]:
1211+
"""
1212+
Allows you to get information about an authenticated user’s 800 most recent bookmarked Tweets.
1213+
1214+
:param user_id: The user ID whose bookmark tweets you would like to retrieve.
1215+
:param expansions: Fields for the expansions.
1216+
:param pagination_token: Token for the pagination.
1217+
:param max_results: The maximum number of results to be returned per page. Number between 1 and the 1000.
1218+
By default, each page will return 100 results.
1219+
:param tweet_fields: Fields for the tweet object.
1220+
:param user_fields: Fields for the user object, Expansion required.
1221+
:param media_fields: Fields for the media object, Expansion required.
1222+
:param place_fields: Fields for the place object, Expansion required.
1223+
:param poll_fields: Fields for the poll object, Expansion required.
1224+
:param return_json: Type for returned data. If you set True JSON data will be returned.
1225+
:return:
1226+
- data: data for the tweets.
1227+
- includes: expansions data.
1228+
- meta: pagination details
1229+
"""
1230+
args = {
1231+
"expansions": enf_comma_separated(name="expansions", value=expansions),
1232+
"tweet.fields": enf_comma_separated(
1233+
name="tweet_fields", value=tweet_fields
1234+
),
1235+
"user.fields": enf_comma_separated(name="user_fields", value=user_fields),
1236+
"media.fields": enf_comma_separated(
1237+
name="media_fields", value=media_fields
1238+
),
1239+
"place.fields": enf_comma_separated(
1240+
name="place_fields", value=place_fields
1241+
),
1242+
"poll.fields": enf_comma_separated(name="poll_fields", value=poll_fields),
1243+
"max_results": max_results,
1244+
"pagination_token": pagination_token,
1245+
}
1246+
return self._get(
1247+
url=f"{self.BASE_URL_V2}/users/{user_id}/bookmarks",
1248+
params=args,
1249+
cls=md.Tweet,
1250+
multi=True,
1251+
return_json=return_json,
1252+
)
1253+
1254+
def bookmark_tweet(self, user_id, tweet_id: str) -> dict:
1255+
"""
1256+
Causes the user ID of an authenticated user identified in the path parameter to Bookmark the target Tweet provided in the request body.
1257+
1258+
:param user_id: The user ID who you are liking a Tweet on behalf of.
1259+
It must match your user ID which authorize with the access token.
1260+
:param tweet_id: The ID of the Tweet that you would to bookmark.
1261+
:return: bookmark status data
1262+
"""
1263+
resp = self._request(
1264+
url=f"{self.BASE_URL_V2}/users/{user_id}/bookmarks",
1265+
verb="POST",
1266+
json={"tweet_id": tweet_id},
1267+
)
1268+
data = self._parse_response(resp=resp)
1269+
return data
1270+
1271+
def bookmark_tweet_remove(self, user_id, tweet_id: str) -> dict:
1272+
"""
1273+
Allows a user or authenticated user ID to remove a Bookmark of a Tweet.
1274+
1275+
:param user_id: The user ID who you are removing a Like of a Tweet on behalf of.
1276+
It must match your user ID which authorize with the access token.
1277+
:param tweet_id: The ID of the Tweet that you would remove bookmark.
1278+
:return: bookmark status data
1279+
"""
1280+
1281+
resp = self._request(
1282+
url=f"{self.BASE_URL_V2}/users/{user_id}/bookmarks/{tweet_id}",
1283+
verb="DELETE",
1284+
)
1285+
data = self._parse_response(resp=resp)
1286+
return data
1287+
11971288
def hidden_reply(self, tweet_id: str, hidden: Optional[bool] = True) -> dict:
11981289
"""
11991290
Hide or un-hide a reply to a Tweet.

pytwitter/rate_limit.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,17 @@ def get_limit(self, auth_type, method="GET"):
126126
regex=re.compile(r"/users/\d+/likes/\d+"),
127127
LIMIT_USER_POST=50,
128128
)
129+
USER_BOOKMARK_TWEET = Endpoint(
130+
resource="/users/:id/bookmarks",
131+
regex=re.compile(r"/users/\d+/bookmarks"),
132+
LIMIT_USER_GET=180,
133+
LIMIT_USER_POST=50,
134+
)
135+
USER_BOOKMARK_TWEET_REMOVE = Endpoint(
136+
resource="/users/:id/bookmarks/:tweet_id",
137+
regex=re.compile(r"/users/\d+/bookmarks/\d+"),
138+
LIMIT_USER_DELETE=50,
139+
)
129140
TWEET_HIDDEN = Endpoint(
130141
resource="/tweets/:id/hidden",
131142
regex=re.compile(r"/tweets/\d+/hidden"),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"data":[{"created_at":"2021-02-18T17:12:47.000Z","source":"Twitter Web App","id":"1362449997430542337","text":"Honored to be the first developer to be featured in @TwitterDev's love fest 🥰♥️😍 https://t.co/g8TsPoZsij"},{"created_at":"2021-02-26T21:38:43.000Z","source":"Twitter Web App","id":"1365416026435854338","text":"We're so happy for our Official Partner @Brandwatch and their big news. https://t.co/3DwWBNSq0o https://t.co/bDUGbgPkKO"},{"created_at":"2020-08-20T16:41:00.000Z","source":"Twitter Web App","id":"1296487407475462144","text":"Check out this feature on @TwitterDev to learn more about how we're mining social media data to make sense of this evolving #publichealth crisis https://t.co/sIFLXRSvEX."},{"created_at":"2020-08-14T18:55:42.000Z","source":"Twitter for Android","id":"1294346980072624128","text":"I awake from five years of slumber https://t.co/OEPVyAFcfB"},{"created_at":"2020-07-14T21:38:10.000Z","source":"Twitter for iPhone","id":"1283153843367206912","text":"@wongmjane Wish we could tell you more, but I’m only a teapot 👀"}],"meta":{"result_count":5,"next_token":"zldjwdz3w6sba13nbs0mbravfipbtqvbiqplg9h0p4k"}}

tests/apis/test_tweets.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_get_tweets(api, helpers):
7070

7171

7272
@responses.activate
73-
def test_like_and_unlike_tweet(api_with_user, helpers):
73+
def test_like_and_unlike_tweet(api_with_user):
7474
user_id, tweet_id = "123456", "10987654321"
7575

7676
responses.add(
@@ -125,6 +125,51 @@ def test_tweet_liking_users(api, helpers):
125125
assert resp_json["includes"]["tweets"][0]["id"] == "1383963809702846470"
126126

127127

128+
@responses.activate
129+
def test_get_user_bookmarks(api_with_user, helpers):
130+
user_id = "2244994945"
131+
tweets_data = helpers.load_json_data(
132+
"testdata/apis/tweet/tweets_by_user_bookmark.json"
133+
)
134+
responses.add(
135+
responses.GET,
136+
url=f"https://api.twitter.com/2/users/{user_id}/bookmarks",
137+
json=tweets_data,
138+
)
139+
140+
resp = api_with_user.get_bookmark_tweets(
141+
user_id=user_id,
142+
max_results=5,
143+
expansions="attachments.media_keys",
144+
media_fields="type,duration_ms",
145+
)
146+
assert len(resp.data) == 5
147+
assert resp.data[0].id == "1362449997430542337"
148+
149+
150+
@responses.activate
151+
def test_user_bookmark_tweet_or_remove_tweet(api_with_user):
152+
user_id, tweet_id = "2244994945", "1228393702244134912"
153+
154+
responses.add(
155+
responses.POST,
156+
url=f"https://api.twitter.com/2/users/{user_id}/bookmarks",
157+
json={"data": {"bookmarked": True}},
158+
)
159+
160+
resp = api_with_user.bookmark_tweet(user_id=user_id, tweet_id=tweet_id)
161+
assert resp["data"]["bookmarked"]
162+
163+
responses.add(
164+
responses.DELETE,
165+
url=f"https://api.twitter.com/2/users/{user_id}/bookmarks/{tweet_id}",
166+
json={"data": {"bookmarked": False}},
167+
)
168+
169+
resp = api_with_user.bookmark_tweet_remove(user_id=user_id, tweet_id=tweet_id)
170+
assert not resp["data"]["bookmarked"]
171+
172+
128173
@responses.activate
129174
def test_get_tweet_quote_tweets(api, helpers):
130175
tweets_data = helpers.load_json_data(

0 commit comments

Comments
 (0)