Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
d60 authored Apr 23, 2024
1 parent 539b1a3 commit 66e2912
Show file tree
Hide file tree
Showing 8 changed files with 417 additions and 42 deletions.
3 changes: 2 additions & 1 deletion twikit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
A Python library for interacting with the Twitter API.
"""

__version__ = '1.5.5'
__version__ = '1.5.6'

from .bookmark import BookmarkFolder
from .client import Client
from .community import (Community, CommunityCreator, CommunityMember,
CommunityRule)
Expand Down
202 changes: 184 additions & 18 deletions twikit/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from fake_useragent import UserAgent
from httpx import Response

from .bookmark import BookmarkFolder
from .community import Community, CommunityMember
from .errors import (
CouldNotTweet,
Expand All @@ -30,6 +31,7 @@
from .tweet import CommunityNote, Poll, ScheduledTweet, Tweet
from .user import User
from .utils import (
BOOKMARK_FOLDER_TIMELINE_FEATURES,
COMMUNITY_TWEETS_FEATURES,
COMMUNITY_NOTE_FEATURES,
JOIN_COMMUNITY_FEATURES,
Expand Down Expand Up @@ -1432,6 +1434,8 @@ def get_tweet_by_id(
show_replies = None
# Reply to reply
for reply in entry['content']['items'][1:]:
if 'tweetcomposer' in reply['entryId']:
continue
if 'tweet' in find_dict(reply, 'result'):
reply = reply['tweet']
if 'tweet' in reply.get('entryId'):
Expand Down Expand Up @@ -2134,14 +2138,18 @@ def delete_retweet(self, tweet_id: str) -> Response:
)
return response

def bookmark_tweet(self, tweet_id: str) -> Response:
def bookmark_tweet(
self, tweet_id: str, folder_id: str | None = None
) -> Response:
"""
Adds the tweet to bookmarks.
Parameters
----------
tweet_id : :class:`str`
The ID of the tweet to be bookmarked.
folder_id : :class:`str` | None, default=None
The ID of the folder to add the bookmark to.
Returns
-------
Expand All @@ -2152,18 +2160,20 @@ def bookmark_tweet(self, tweet_id: str) -> Response:
--------
>>> tweet_id = '...'
>>> client.bookmark_tweet(tweet_id)
See Also
--------
.bookmark_tweet
"""
variables = {'tweet_id': tweet_id}
if folder_id is None:
endpoint = Endpoint.CREATE_BOOKMARK
else:
endpoint = Endpoint.BOOKMARK_TO_FOLDER
variables['bookmark_collection_id'] = folder_id

data = {
'variables': {'tweet_id': tweet_id},
'variables': variables,
'queryId': get_query_id(Endpoint.CREATE_BOOKMARK)
}
response = self.http.post(
Endpoint.CREATE_BOOKMARK,
endpoint,
json=data,
headers=self._base_headers
)
Expand Down Expand Up @@ -2204,17 +2214,18 @@ def delete_bookmark(self, tweet_id: str) -> Response:
return response

def get_bookmarks(
self, count: int = 20, cursor: str | None = None
self, count: int = 20,
cursor: str | None = None, folder_id: str | None = None
) -> Result[Tweet]:
"""
Retrieves bookmarks from the authenticated user's Twitter account.
Parameters
----------
count : :class:`int`, default=20
The number of bookmarks to retrieve (default is 20).
cursor : :class:`str`, default=None
A cursor to paginate through the bookmarks (default is None).
The number of bookmarks to retrieve.
folder_id : :class:`str` | None, default=None
Folder to retrieve bookmarks.
Returns
-------
Expand All @@ -2240,17 +2251,24 @@ def get_bookmarks(
'count': count,
'includePromotedContent': True
}
if folder_id is None:
endpoint = Endpoint.BOOKMARKS
features = FEATURES | {
'graphql_timeline_v2_bookmark_timeline': True
}
else:
endpoint = Endpoint.BOOKMARK_FOLDER_TIMELINE
variables['bookmark_collection_id'] = folder_id
features = BOOKMARK_FOLDER_TIMELINE_FEATURES

if cursor is not None:
variables['cursor'] = cursor
features = FEATURES | {
'graphql_timeline_v2_bookmark_timeline': True
}
params = flatten_params({
'variables': variables,
'features': features
})
response = self.http.get(
Endpoint.BOOKMARKS,
endpoint,
params=params,
headers=self._base_headers
).json()
Expand All @@ -2260,7 +2278,13 @@ def get_bookmarks(
return Result([])
items = items_[0]
next_cursor = items[-1]['content']['value']
previous_cursor = items[-2]['content']['value']
if folder_id is None:
previous_cursor = items[-2]['content']['value']
fetch_previous_result = partial(self.get_bookmarks, count,
previous_cursor, folder_id)
else:
previous_cursor = None
fetch_previous_result = None

results = []
for item in items:
Expand All @@ -2272,9 +2296,9 @@ def get_bookmarks(

return Result(
results,
partial(self.get_bookmarks, count, next_cursor),
partial(self.get_bookmarks, count, next_cursor, folder_id),
next_cursor,
partial(self.get_bookmarks, count, previous_cursor),
fetch_previous_result,
previous_cursor
)

Expand Down Expand Up @@ -2302,6 +2326,148 @@ def delete_all_bookmarks(self) -> Response:
)
return response

def get_bookmark_folders(
self, cursor: str | None = None
) -> Result[BookmarkFolder]:
"""
Retrieves bookmark folders.
Returns
-------
Result[:class:`BookmarkFolder`]
Result object containing a list of bookmark folders.
Examples
--------
>>> folders = client.get_bookmark_folders()
>>> print(folders)
[<BookmarkFolder id="...">, ..., <BookmarkFolder id="...">]
>>> more_folders = folders.next() # Retrieve more folders
"""
variables = {}
if cursor is not None:
variables['cursor'] = cursor
params = flatten_params({'variables': variables})
response = self.http.get(
Endpoint.BOOKMARK_FOLDERS,
params=params,
headers=self._base_headers
).json()

slice = find_dict(response, 'bookmark_collections_slice')[0]
results = []
for item in slice['items']:
results.append(BookmarkFolder(self, item))

if 'next_cursor' in slice['slice_info']:
next_cursor = slice['slice_info']['next_cursor']
fetch_next_result = partial(self.get_bookmark_folders, next_cursor)
else:
next_cursor = None
fetch_next_result = None

return Result(
results,
fetch_next_result,
next_cursor
)

def edit_bookmark_folder(
self, folder_id: str, name: str
) -> BookmarkFolder:
"""
Edits a bookmark folder.
Parameters
----------
folder_id : :class:`str`
ID of the folder to edit.
name : :class:`str`
New name for the folder.
Returns
-------
:class:`BookmarkFolder`
Updated bookmark folder.
Examples
--------
>>> client.edit_bookmark_folder('123456789', 'MyFolder')
"""
variables = {
'bookmark_collection_id': folder_id,
'name': name
}
data = {
'variables': variables,
'queryId': get_query_id(Endpoint.EDIT_BOOKMARK_FOLDER)
}
response = self.http.post(
Endpoint.EDIT_BOOKMARK_FOLDER,
json=data,
headers=self._base_headers
).json()
return BookmarkFolder(
self, response['data']['bookmark_collection_update']
)

def delete_bookmark_folder(self, folder_id: str) -> Response:
"""
Deletes a bookmark folder.
Parameters
----------
folder_id : :class:`str`
ID of the folder to delete.
Returns
-------
:class:`httpx.Response`
Response returned from twitter api.
"""
variables = {
'bookmark_collection_id': folder_id
}
data = {
'variables': variables,
'queryId': get_query_id(Endpoint.DELETE_BOOKMARK_FOLDER)
}
response = self.http.post(
Endpoint.DELETE_BOOKMARK_FOLDER,
json=data,
headers=self._base_headers
)
return response

def create_bookmark_folder(self, name: str) -> BookmarkFolder:
"""Creates a bookmark folder.
Parameters
----------
name : :class:`str`
Name of the folder.
Returns
-------
:class:`BookmarkFolder`
Newly created bookmark folder.
"""
variables = {
'name': name
}
data = {
'variables': variables,
'queryId': get_query_id(Endpoint.CREATE_BOOKMARK_FOLDER)
}
response = self.http.post(
Endpoint.CREATE_BOOKMARK_FOLDER,
json=data,
headers=self._base_headers
).json()
return BookmarkFolder(
self, response['data']['bookmark_collection_create']
)

def follow_user(self, user_id: str) -> Response:
"""
Follows a user.
Expand Down
3 changes: 3 additions & 0 deletions twikit/errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import annotations


class TwitterException(Exception):
"""
Base class for Twitter API related exceptions.
Expand Down
6 changes: 4 additions & 2 deletions twikit/tweet.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,17 @@ def __init__(self, client: Client, data: dict, user: User = None) -> None:
self.reply_count: int = legacy['reply_count']
self.favorite_count: int = legacy['favorite_count']
self.favorited: bool = legacy['favorited']
self.view_count: int = data['views'].get('count') if 'views' in data else None
self.view_count: int = (data['views'].get('count')
if 'views' in data else None)
self.retweet_count: int = legacy['retweet_count']
self.editable_until_msecs: int = data['edit_control'].get(
'editable_until_msecs')
self.is_translatable: bool = data.get('is_translatable')
self.is_edit_eligible: bool = data['edit_control'].get(
'is_edit_eligible')
self.edits_remaining: int = data['edit_control'].get('edits_remaining')
self.state: str = data['views'].get('state') if 'views' in data else None
self.state: str = (data['views'].get('state')
if 'views' in data else None)
self.has_community_notes: bool = data.get('has_birdwatch_notes')

if 'birdwatch_pivot' in data:
Expand Down
1 change: 1 addition & 0 deletions twikit/twikit_async/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
if os.name == 'nt':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

from .bookmark import BookmarkFolder
from ..errors import *
from ..utils import build_query
from .client import Client
Expand Down
Loading

0 comments on commit 66e2912

Please sign in to comment.