Skip to content

Commit 6418e7d

Browse files
committed
- Implemented transaction ID in guest client requests and fixed 404 error
- Removed the dependency on requests - Fixed a bug in login with TOTP - Added Tweet.bookmark_count and Tweet.bookmarked"
1 parent 3e922d6 commit 6418e7d

File tree

7 files changed

+50
-19
lines changed

7 files changed

+50
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.json

examples/listen_for_new_tweets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def callback(tweet: Tweet) -> None:
1818

1919

2020
async def get_latest_tweet() -> Tweet:
21-
return await client.get_user_tweets(USER_ID, 'Replies')[0]
21+
return (await client.get_user_tweets(USER_ID, 'Replies'))[0]
2222

2323

2424
async def main() -> NoReturn:

twikit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
A Python library for interacting with the Twitter API.
88
"""
99

10-
__version__ = '2.2.1'
10+
__version__ = '2.2.2'
1111

1212
import asyncio
1313
import os

twikit/client/client.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -436,18 +436,6 @@ async def login(
436436
})
437437
return flow.response
438438

439-
await flow.execute_task({
440-
'subtask_id': 'AccountDuplicationCheck',
441-
'check_logged_in_account': {
442-
'link': 'AccountDuplicationCheck_false'
443-
}
444-
})
445-
446-
if not flow.response['subtasks']:
447-
return
448-
449-
self._user_id = find_dict(flow.response, 'id_str', find_one=True)[0]
450-
451439
if flow.task_id == 'LoginTwoFactorAuthChallenge':
452440
if totp_secret is None:
453441
print(find_dict(flow.response, 'secondary_text', find_one=True)[0]['text'])
@@ -463,6 +451,18 @@ async def login(
463451
}
464452
})
465453

454+
await flow.execute_task({
455+
'subtask_id': 'AccountDuplicationCheck',
456+
'check_logged_in_account': {
457+
'link': 'AccountDuplicationCheck_false'
458+
}
459+
})
460+
461+
if not flow.response['subtasks']:
462+
return
463+
464+
self._user_id = find_dict(flow.response, 'id_str', find_one=True)[0]
465+
466466
return flow.response
467467

468468
async def logout(self) -> Response:

twikit/guest/client.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import warnings
55
from functools import partial
66
from typing import Any, Literal
7+
from urllib.parse import urlparse
78

89
from httpx import AsyncClient, AsyncHTTPTransport, Response
910
from httpx._utils import URLPattern
1011

1112
from ..client.gql import GQLClient
1213
from ..client.v11 import V11Client
13-
from ..constants import TOKEN
14+
from ..constants import DOMAIN, TOKEN
1415
from ..errors import (
1516
BadRequest,
1617
Forbidden,
@@ -22,6 +23,7 @@
2223
Unauthorized
2324
)
2425
from ..utils import Result, find_dict, find_entry_by_type, httpx_transport_to_url
26+
from ..x_client_transaction import ClientTransaction
2527
from .tweet import Tweet
2628
from .user import User
2729

@@ -48,7 +50,6 @@ def tweet_from_data(client: GuestClient, data: dict) -> Tweet:
4850
return Tweet(client, tweet_data, User(client, user_data))
4951

5052

51-
5253
class GuestClient:
5354
"""
5455
A client for interacting with the Twitter API as a guest.
@@ -71,7 +72,7 @@ class GuestClient:
7172

7273
def __init__(
7374
self,
74-
language: str | None = None,
75+
language: str = 'en-US',
7576
proxy: str | None = None,
7677
**kwargs
7778
) -> None:
@@ -93,6 +94,7 @@ def __init__(
9394
self._guest_token: str | None = None # set when activate method is called
9495
self.gql = GQLClient(self)
9596
self.v11 = V11Client(self)
97+
self.client_transaction = ClientTransaction()
9698

9799
async def request(
98100
self,
@@ -102,7 +104,23 @@ async def request(
102104
**kwargs
103105
) -> tuple[dict | Any, Response]:
104106
':meta private:'
105-
response = await self.http.request(method, url, **kwargs)
107+
headers = kwargs.pop('headers', {})
108+
109+
if not self.client_transaction.home_page_response:
110+
cookies_backup = dict(self.http.cookies).copy()
111+
ct_headers = {
112+
'Accept-Language': f'{self.language},{self.language.split("-")[0]};q=0.9',
113+
'Cache-Control': 'no-cache',
114+
'Referer': f'https://{DOMAIN}',
115+
'User-Agent': self._user_agent
116+
}
117+
await self.client_transaction.init(self.http, ct_headers)
118+
self.http.cookies = cookies_backup
119+
120+
tid = self.client_transaction.generate_transaction_id(method=method, path=urlparse(url).path)
121+
headers['X-Client-Transaction-Id'] = tid
122+
123+
response = await self.http.request(method, url, headers=headers, **kwargs)
106124

107125
try:
108126
response_data = response.json()
@@ -167,7 +185,7 @@ def _base_headers(self) -> dict[str, str]:
167185
'authorization': f'Bearer {self._token}',
168186
'content-type': 'application/json',
169187
'X-Twitter-Active-User': 'yes',
170-
'Referer': 'https://twitter.com/',
188+
'Referer': f'https://{DOMAIN}',
171189
}
172190

173191
if self.language is not None:

twikit/guest/tweet.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ class Tweet:
5353
The state of the tweet views.
5454
retweet_count : :class:`int`
5555
The count of retweets for the tweet.
56+
bookmark_count : :class:`int`
57+
The count of bookmarks for the tweet.
58+
bookmarked : :class:`bool`
59+
Indicates if the tweet is bookmarked.
5660
place : :class:`.Place` | None
5761
The location associated with the tweet.
5862
editable_until_msecs : :class:`int`
@@ -107,6 +111,8 @@ def __init__(self, client: GuestClient, data: dict, user: User = None) -> None:
107111
self.favorited: bool = legacy['favorited']
108112
self.retweet_count: int = legacy['retweet_count']
109113
self._place_data = legacy.get('place')
114+
self.bookmark_count: int = legacy.get('bookmark_count')
115+
self.bookmarked: bool = legacy.get('bookmarked')
110116
self.editable_until_msecs: int = data['edit_control'].get('editable_until_msecs')
111117
self.is_translatable: bool = data.get('is_translatable')
112118
self.is_edit_eligible: bool = data['edit_control'].get('is_edit_eligible')

twikit/tweet.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class Tweet:
5959
The state of the tweet views.
6060
retweet_count : :class:`int`
6161
The count of retweets for the tweet.
62+
bookmark_count : :class:`int`
63+
The count of bookmarks for the tweet.
64+
bookmarked : :class:`bool`
65+
Indicates if the tweet is bookmarked.
6266
place : :class:`.Place` | None
6367
The location associated with the tweet.
6468
editable_until_msecs : :class:`int`
@@ -116,6 +120,8 @@ def __init__(self, client: Client, data: dict, user: User = None) -> None:
116120
self.favorited: bool = legacy['favorited']
117121
self.retweet_count: int = legacy['retweet_count']
118122
self._place_data = legacy.get('place')
123+
self.bookmark_count: int = legacy.get('bookmark_count')
124+
self.bookmarked: bool = legacy.get('bookmarked')
119125
self.editable_until_msecs: int = data['edit_control'].get('editable_until_msecs')
120126
self.is_translatable: bool = data.get('is_translatable')
121127
self.is_edit_eligible: bool = data['edit_control'].get('is_edit_eligible')

0 commit comments

Comments
 (0)