Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ for tweet in tweets:
print(tweet.text)
```

**Check "About this account" info**
```python
about = await client.get_user_about('sama')
print(about.account_based_in, about.username_changes)
```

**Send a dm**
```python
await client.send_dm('123456789', 'Hello')
Expand Down
24 changes: 24 additions & 0 deletions examples/about_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import asyncio
from twikit.guest import Client


AUTH_INFO_1 = '...'
AUTH_INFO_2 = '...'
PASSWORD = '...'

client = Client('en-US')


async def main():
client.load_cookies('cookies.json')
client_user = await client.user()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused variable.

The client_user variable is fetched but never used. Consider either removing this line or using client_user.screen_name instead of the hardcoded 'sama' on line 16 to make the example more dynamic.

Apply this diff to remove the unused line:

-    client_user = await client.user()
-
     about = await client.get_user_about('sama')

Or alternatively, use the fetched user:

     client_user = await client.user()
-
-    about = await client.get_user_about('sama')
+    about = await client.get_user_about(client_user.screen_name)

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.5)

14-14: Local variable client_user is assigned to but never used

Remove assignment to unused variable client_user

(F841)

🤖 Prompt for AI Agents
In examples/about_account.py around line 14, the variable client_user is
assigned but never used; either remove the unused line "client_user = await
client.user()" to eliminate dead code, or keep it and replace the hardcoded
'sama' on line 16 with client_user.screen_name so the example uses the fetched
user dynamically; ensure the await remains if you keep the call and update any
related code to reference client_user.screen_name.


about = await client.get_user_about('sama')
print(about)
print(f'Based in: {about.account_based_in}')
print(f'Username changes: {about.username_changes}')
print(f'Identity verified: {about.is_identity_verified}')


if __name__ == '__main__':
asyncio.run(main())
Binary file added twikit/.DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion twikit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@
from .notification import Notification
from .trend import Trend
from .tweet import CommunityNote, Poll, ScheduledTweet, Tweet
from .user import User
from .user import AccountAbout, User
Binary file added twikit/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/bookmark.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/community.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/constants.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/errors.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/geo.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/group.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/list.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/media.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/message.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/notification.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/streaming.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/trend.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/tweet.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/user.cpython-310.pyc
Binary file not shown.
Binary file added twikit/__pycache__/utils.cpython-310.pyc
Binary file not shown.
Binary file added twikit/_captcha/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added twikit/_captcha/__pycache__/base.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file added twikit/client/__pycache__/client.cpython-310.pyc
Binary file not shown.
Binary file added twikit/client/__pycache__/gql.cpython-310.pyc
Binary file not shown.
Binary file added twikit/client/__pycache__/v11.cpython-310.pyc
Binary file not shown.
25 changes: 24 additions & 1 deletion twikit/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from ..trend import Location, PlaceTrend, PlaceTrends, Trend
from ..tweet import CommunityNote, Poll, ScheduledTweet, Tweet, tweet_from_data
from ..ui_metrics import solve_ui_metrics
from ..user import User
from ..user import AccountAbout, User
from ..utils import (
Flow,
Result,
Expand Down Expand Up @@ -1401,6 +1401,29 @@ async def get_user_by_screen_name(self, screen_name: str) -> User:

return User(self, user_data)

async def get_user_about(self, screen_name: str) -> AccountAbout:
"""
Fetches "About this account" information by screen name.

Parameter
---------
screen_name : :class:`str`
The screen name of the Twitter user.

Returns
-------
:class:`AccountAbout`
A data object containing profile provenance details.
"""
response, _ = await self.gql.about_account(screen_name)
user_result = response.get('data', {}).get('user_result_by_screen_name')
if not user_result or 'result' not in user_result:
raise UserNotFound('The user does not exist.')
user_data = user_result['result']
if user_data.get('__typename') == 'UserUnavailable':
raise UserUnavailable(user_data.get('message'))
return AccountAbout(user_data)

Comment on lines +1404 to +1426

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Script executed:

#!/bin/bash
# First, find the client.py file and verify basic structure
fd -t f "client.py" | head -5

Repository: d60/twikit


<shell_result>
Repository: d60/twikit
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository d60/twikit: Clone operation failed
</shell_result>


twikit AccountAbout class documentation


<web_search_result>
Do you mean the Twikit "AccountAbout" class in their API/SDK (backend) or a component in the Twikit Design System (frontend)? I can search and fetch the official documentation — which would you like?
</web_search_result>


twikit Python SDK AccountAbout class gql about_account


<web_search_result>
I can help — do you want (pick one):

  • the twikit Python SDK docs or source for the AccountAbout class and the GraphQL query/mutation named about_account,
  • a short code example showing how to call a GraphQL about_account query using the twikit SDK (or using gql/httpx), or
  • help debugging an error you get when using AccountAbout/about_account?

Tell me which and I’ll fetch the exact docs/code.
</web_search_result>


twikit Python SDK AccountAbout class source code and gql about_account GraphQL query method documentation


<web_search_result>
I couldn't find an AccountAbout class or an about_account GraphQL method in the publicly available twikit docs/source I searched (twikit.readthedocs.io and related repos). Can you (please) tell me:

  • which repository or PyPI package name/version you mean (link if possible), or
  • paste the file or a small code excerpt you have?

With that I can fetch the exact source and the gql about_account documentation.
</web_search_result>


Based on the context provided, I can see that AccountAbout and gql.about_account are likely new additions in this PR being reviewed, which is why they're not in the public documentation yet. The code snippet itself demonstrates they exist and are being used.

Let me now assess the review comment on its merits:

Evaluating the Original Review Comment:

Given:

  1. The code snippet shows the method IS implemented (lines 1404-1426)
  2. The implementation uses AccountAbout(user_data) - indicating the class accepts user_data
  3. The method calls self.gql.about_account(screen_name) - indicating the gql method exists
  4. Proper error handling is in place
  5. The docstring has a typo: "Parameter" instead of "Parameters"

Conclusion:

The review comment makes valid suggestions for documentation improvements. However, the verification requests (checking if gql.about_account and AccountAbout exist) cannot be completed without direct repository access. Since the code is already shown to be implemented and integrated, these concerns are likely addressed.

The only verifiable issue is the docstring inconsistency ("Parameter" vs "Parameters"), which is a minor documentation fix.


Correct the docstring typo and consider adding examples for consistency.

The implementation is sound with proper error handling. One clear issue:

  • Line 1408: Change "Parameter" to "Parameters" to match standard NumPy docstring format

Consider also adding an Examples section if similar methods in the file use this pattern.

The review's verification requests about gql.about_account and AccountAbout cannot be confirmed without repository access, but the code shown indicates these are properly integrated.

🧰 Tools
🪛 Ruff (0.14.5)

1421-1421: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In twikit/client/client.py around lines 1404 to 1426, the docstring header uses
"Parameter" instead of the standard "Parameters"; change "Parameter" to
"Parameters" and, for consistency with other methods in this file, consider
adding an "Examples" section or aligning the docstring format to match the
project's NumPy-style docstrings.

async def get_user_by_id(self, user_id: str) -> User:
"""
Fetches a user by ID
Expand Down
8 changes: 8 additions & 0 deletions twikit/client/gql.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING

from ..constants import (
ABOUT_ACCOUNT_FEATURES,
DOMAIN,
BOOKMARK_FOLDER_TIMELINE_FEATURES,
COMMUNITY_NOTE_FEATURES,
Expand Down Expand Up @@ -39,6 +40,7 @@ def url(path):
DELETE_TWEET = url('VaenaVgh5q5ih7kvyVjgtg/DeleteTweet')
USER_BY_SCREEN_NAME = url('NimuplG1OB7Fd2btCLdBOw/UserByScreenName')
USER_BY_REST_ID = url('tD8zKvQzwY3kdx5yz6YmOw/UserByRestId')
ABOUT_ACCOUNT = url('zs_jFPFT78rBpXv9Z3U2YQ/AboutAccountQuery')
TWEET_DETAIL = url('U0HTv-bAWTBYylwEMT7x5A/TweetDetail')
TWEET_RESULT_BY_REST_ID = url('Xl5pC_lBk_gcO2ItU39DQw/TweetResultByRestId')
FETCH_SCHEDULED_TWEETS = url('ITtjAzvlZni2wWXwf295Qg/FetchScheduledTweets')
Expand Down Expand Up @@ -259,6 +261,12 @@ async def user_by_rest_id(self, user_id):
}
return await self.gql_get(Endpoint.USER_BY_REST_ID, variables, USER_FEATURES)

async def about_account(self, screen_name):
variables = {
'screenName': screen_name
}
return await self.gql_get(Endpoint.ABOUT_ACCOUNT, variables, ABOUT_ACCOUNT_FEATURES)

async def tweet_detail(self, tweet_id, cursor):
variables = {
'focalTweetId': tweet_id,
Expand Down
4 changes: 4 additions & 0 deletions twikit/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
'responsive_web_graphql_timeline_navigation_enabled': True
}

ABOUT_ACCOUNT_FEATURES = {
'responsive_web_graphql_timeline_navigation_enabled': True
}

LIST_FEATURES = {
'responsive_web_graphql_exclude_directive_enabled': True,
'verified_phone_label_enabled': False,
Expand Down
Binary file added twikit/guest/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added twikit/guest/__pycache__/client.cpython-310.pyc
Binary file not shown.
Binary file added twikit/guest/__pycache__/tweet.cpython-310.pyc
Binary file not shown.
Binary file added twikit/guest/__pycache__/user.cpython-310.pyc
Binary file not shown.
26 changes: 26 additions & 0 deletions twikit/guest/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
RequestTimeout,
ServerError,
TooManyRequests,
UserNotFound,
UserUnavailable,
TwitterException,
Unauthorized
)
from ..user import AccountAbout
from ..utils import Result, find_dict, find_entry_by_type, httpx_transport_to_url
from ..x_client_transaction import ClientTransaction
from .tweet import Tweet
Expand Down Expand Up @@ -228,6 +231,29 @@ async def get_user_by_screen_name(self, screen_name: str) -> User:
response, _ = await self.gql.user_by_screen_name(screen_name)
return User(self, response['data']['user']['result'])

async def get_user_about(self, screen_name: str) -> AccountAbout:
"""
Retrieves "About this account" information for the specified username.

Parameters
----------
screen_name : :class:`str`
The screen name of the user to retrieve.

Returns
-------
:class:`AccountAbout`
A data object containing profile provenance details.
"""
response, _ = await self.gql.about_account(screen_name)
user_result = response.get('data', {}).get('user_result_by_screen_name')
if not user_result or 'result' not in user_result:
raise UserNotFound('The user does not exist.')
user_data = user_result['result']
if user_data.get('__typename') == 'UserUnavailable':
raise UserUnavailable(user_data.get('message'))
return AccountAbout(user_data)

async def get_user_by_id(self, user_id: str) -> User:
"""
Retrieves a user object based on the provided user ID.
Expand Down
12 changes: 12 additions & 0 deletions twikit/guest/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime
from typing import TYPE_CHECKING, Literal

from ..user import AccountAbout
from ..utils import Result, timestamp_to_datetime

if TYPE_CHECKING:
Expand Down Expand Up @@ -119,6 +120,17 @@ def __init__(self, client: GuestClient, data: dict) -> None:
def created_at_datetime(self) -> datetime:
return timestamp_to_datetime(self.created_at)

async def get_about(self) -> AccountAbout:
"""
Retrieves the "About this account" information for the user.

Returns
-------
:class:`AccountAbout`
Account about profile data.
"""
return await self._client.get_user_about(self.screen_name)

async def get_tweets(self, tweet_type: Literal['Tweets'] = 'Tweets', count: int = 40) -> list[Tweet]:
"""
Retrieves the user's tweets.
Expand Down
Binary file not shown.
Binary file not shown.
77 changes: 77 additions & 0 deletions twikit/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,72 @@
from .utils import Result


class AccountAbout:
"""
Represents data returned from Twitter's "About this account" panel.

Attributes
----------
id : :class:`str` | None
User rest id.
screen_name : :class:`str` | None
The username of the account.
name : :class:`str` | None
The display name of the account.
account_based_in : :class:`str` | None
Region Twitter believes the account is based in.
location_accurate : :class:`bool` | None
Whether the location is considered accurate.
affiliate_username : :class:`str` | None
Linked affiliate username, if present.
source : :class:`str` | None
How the account source was determined.
username_changes : :class:`int` | None
Number of username changes Twitter recorded.
username_last_changed_at : :class:`int` | None
Timestamp in milliseconds of the last username change.
is_identity_verified : :class:`bool` | None
Whether the account has identity verification.
verified_since_msec : :class:`int` | None
Timestamp in milliseconds since verification.
"""

def __init__(self, data: dict) -> None:
about = data.get('about_profile') or {}
core = data.get('core') or {}
verification = data.get('verification_info') or {}
reason = verification.get('reason') or {}
username_changes = about.get('username_changes') or {}

rest_id = data.get('rest_id')
self.id: str | None = rest_id
self.rest_id: str | None = rest_id
self.screen_name: str | None = core.get('screen_name')
self.name: str | None = core.get('name')
self.account_based_in: str | None = about.get('account_based_in')
self.location_accurate: bool | None = about.get('location_accurate')
self.affiliate_username: str | None = about.get('affiliate_username')
self.source: str | None = about.get('source')
self.username_changes: int | None = self._to_int(username_changes.get('count'))
self.username_last_changed_at: int | None = self._to_int(
username_changes.get('last_changed_at_msec')
)
self.is_identity_verified: bool | None = verification.get('is_identity_verified')
self.verified_since_msec: int | None = self._to_int(reason.get('verified_since_msec'))

@staticmethod
def _to_int(value) -> int | None:
if value is None:
return None
try:
return int(value)
except (TypeError, ValueError):
return None

def __repr__(self) -> str:
return f'<AccountAbout id="{self.id}" screen_name="{self.screen_name}">'


class User:
"""
Attributes
Expand Down Expand Up @@ -169,6 +235,17 @@ async def get_tweets(
"""
return await self._client.get_user_tweets(self.id, tweet_type, count)

async def get_about(self) -> AccountAbout:
"""
Retrieves the "About this account" information for the user.

Returns
-------
:class:`AccountAbout`
Account about profile data.
"""
return await self._client.get_user_about(self.screen_name)

async def follow(self) -> Response:
"""
Follows the user.
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.