Skip to content

Commit

Permalink
- Implemented transaction ID in guest client requests and fixed 404 e…
Browse files Browse the repository at this point in the history
…rror

- Removed the dependency on requests
- Fixed a bug in login with TOTP
- Added Tweet.bookmark_count and Tweet.bookmarked"
  • Loading branch information
d60 committed Jan 29, 2025
1 parent 3e922d6 commit 6418e7d
Showing 7 changed files with 50 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.json
2 changes: 1 addition & 1 deletion examples/listen_for_new_tweets.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ def callback(tweet: Tweet) -> None:


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


async def main() -> NoReturn:
2 changes: 1 addition & 1 deletion twikit/__init__.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
A Python library for interacting with the Twitter API.
"""

__version__ = '2.2.1'
__version__ = '2.2.2'

import asyncio
import os
24 changes: 12 additions & 12 deletions twikit/client/client.py
Original file line number Diff line number Diff line change
@@ -436,18 +436,6 @@ async def login(
})
return flow.response

await flow.execute_task({
'subtask_id': 'AccountDuplicationCheck',
'check_logged_in_account': {
'link': 'AccountDuplicationCheck_false'
}
})

if not flow.response['subtasks']:
return

self._user_id = find_dict(flow.response, 'id_str', find_one=True)[0]

if flow.task_id == 'LoginTwoFactorAuthChallenge':
if totp_secret is None:
print(find_dict(flow.response, 'secondary_text', find_one=True)[0]['text'])
@@ -463,6 +451,18 @@ async def login(
}
})

await flow.execute_task({
'subtask_id': 'AccountDuplicationCheck',
'check_logged_in_account': {
'link': 'AccountDuplicationCheck_false'
}
})

if not flow.response['subtasks']:
return

self._user_id = find_dict(flow.response, 'id_str', find_one=True)[0]

return flow.response

async def logout(self) -> Response:
28 changes: 23 additions & 5 deletions twikit/guest/client.py
Original file line number Diff line number Diff line change
@@ -4,13 +4,14 @@
import warnings
from functools import partial
from typing import Any, Literal
from urllib.parse import urlparse

from httpx import AsyncClient, AsyncHTTPTransport, Response
from httpx._utils import URLPattern

from ..client.gql import GQLClient
from ..client.v11 import V11Client
from ..constants import TOKEN
from ..constants import DOMAIN, TOKEN
from ..errors import (
BadRequest,
Forbidden,
@@ -22,6 +23,7 @@
Unauthorized
)
from ..utils import Result, find_dict, find_entry_by_type, httpx_transport_to_url
from ..x_client_transaction import ClientTransaction
from .tweet import Tweet
from .user import User

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



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

def __init__(
self,
language: str | None = None,
language: str = 'en-US',
proxy: str | None = None,
**kwargs
) -> None:
@@ -93,6 +94,7 @@ def __init__(
self._guest_token: str | None = None # set when activate method is called
self.gql = GQLClient(self)
self.v11 = V11Client(self)
self.client_transaction = ClientTransaction()

async def request(
self,
@@ -102,7 +104,23 @@ async def request(
**kwargs
) -> tuple[dict | Any, Response]:
':meta private:'
response = await self.http.request(method, url, **kwargs)
headers = kwargs.pop('headers', {})

if not self.client_transaction.home_page_response:
cookies_backup = dict(self.http.cookies).copy()
ct_headers = {
'Accept-Language': f'{self.language},{self.language.split("-")[0]};q=0.9',
'Cache-Control': 'no-cache',
'Referer': f'https://{DOMAIN}',
'User-Agent': self._user_agent
}
await self.client_transaction.init(self.http, ct_headers)
self.http.cookies = cookies_backup

tid = self.client_transaction.generate_transaction_id(method=method, path=urlparse(url).path)
headers['X-Client-Transaction-Id'] = tid

response = await self.http.request(method, url, headers=headers, **kwargs)

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

if self.language is not None:
6 changes: 6 additions & 0 deletions twikit/guest/tweet.py
Original file line number Diff line number Diff line change
@@ -53,6 +53,10 @@ class Tweet:
The state of the tweet views.
retweet_count : :class:`int`
The count of retweets for the tweet.
bookmark_count : :class:`int`
The count of bookmarks for the tweet.
bookmarked : :class:`bool`
Indicates if the tweet is bookmarked.
place : :class:`.Place` | None
The location associated with the tweet.
editable_until_msecs : :class:`int`
@@ -107,6 +111,8 @@ def __init__(self, client: GuestClient, data: dict, user: User = None) -> None:
self.favorited: bool = legacy['favorited']
self.retweet_count: int = legacy['retweet_count']
self._place_data = legacy.get('place')
self.bookmark_count: int = legacy.get('bookmark_count')
self.bookmarked: bool = legacy.get('bookmarked')
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')
6 changes: 6 additions & 0 deletions twikit/tweet.py
Original file line number Diff line number Diff line change
@@ -59,6 +59,10 @@ class Tweet:
The state of the tweet views.
retweet_count : :class:`int`
The count of retweets for the tweet.
bookmark_count : :class:`int`
The count of bookmarks for the tweet.
bookmarked : :class:`bool`
Indicates if the tweet is bookmarked.
place : :class:`.Place` | None
The location associated with the tweet.
editable_until_msecs : :class:`int`
@@ -116,6 +120,8 @@ def __init__(self, client: Client, data: dict, user: User = None) -> None:
self.favorited: bool = legacy['favorited']
self.retweet_count: int = legacy['retweet_count']
self._place_data = legacy.get('place')
self.bookmark_count: int = legacy.get('bookmark_count')
self.bookmarked: bool = legacy.get('bookmarked')
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')

0 comments on commit 6418e7d

Please sign in to comment.