tweepy-self 1.10.0b9__py3-none-any.whl → 1.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tweepy_self-1.10.0b9.dist-info → tweepy_self-1.11.1.dist-info}/METADATA +21 -7
 - {tweepy_self-1.10.0b9.dist-info → tweepy_self-1.11.1.dist-info}/RECORD +10 -10
 - twitter/__init__.py +1 -1
 - twitter/account.py +1 -1
 - twitter/base/session.py +4 -3
 - twitter/client.py +235 -67
 - twitter/enums.py +1 -0
 - twitter/errors.py +15 -8
 - twitter/models.py +12 -0
 - {tweepy_self-1.10.0b9.dist-info → tweepy_self-1.11.1.dist-info}/WHEEL +0 -0
 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Metadata-Version: 2.1
         
     | 
| 
       2 
2 
     | 
    
         
             
            Name: tweepy-self
         
     | 
| 
       3 
     | 
    
         
            -
            Version: 1. 
     | 
| 
      
 3 
     | 
    
         
            +
            Version: 1.11.1
         
     | 
| 
       4 
4 
     | 
    
         
             
            Summary: Twitter (selfbot) for Python!
         
     | 
| 
       5 
5 
     | 
    
         
             
            Home-page: https://github.com/alenkimov/tweepy-self
         
     | 
| 
       6 
6 
     | 
    
         
             
            Author: Alen
         
     | 
| 
         @@ -32,12 +32,19 @@ Description-Content-Type: text/markdown 
     | 
|
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         
             
            A modern, easy to use, feature-rich, and async ready API wrapper for Twitter's user API written in Python.
         
     | 
| 
       34 
34 
     | 
    
         | 
| 
      
 35 
     | 
    
         
            +
            _NEW!_ Менеджер аккаунтов с базой данных:
         
     | 
| 
      
 36 
     | 
    
         
            +
            - [tweepy-manager](https://github.com/alenkimov/tweepy-manager)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
       35 
38 
     | 
    
         
             
            More libraries of the family:
         
     | 
| 
       36 
39 
     | 
    
         
             
            - [better-proxy](https://github.com/alenkimov/better_proxy)
         
     | 
| 
       37 
40 
     | 
    
         
             
            - [better-web3](https://github.com/alenkimov/better_web3)
         
     | 
| 
       38 
41 
     | 
    
         | 
| 
       39 
42 
     | 
    
         
             
            Отдельное спасибо [Кузнице Ботов](https://t.me/bots_forge) за код для авторизации и разморозки! Подписывайтесь на их Telegram :)
         
     | 
| 
       40 
43 
     | 
    
         | 
| 
      
 44 
     | 
    
         
            +
            Похожие библиотеки:
         
     | 
| 
      
 45 
     | 
    
         
            +
            - [twikit (sync and async)](https://github.com/d60/twikit)
         
     | 
| 
      
 46 
     | 
    
         
            +
            - [twitter-api-client (sync)](https://github.com/trevorhobenshield/twitter-api-client)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
       41 
48 
     | 
    
         
             
            ## Key Features
         
     | 
| 
       42 
49 
     | 
    
         
             
            - Modern Pythonic API using async and await.
         
     | 
| 
       43 
50 
     | 
    
         
             
            - Prevents user account automation detection.
         
     | 
| 
         @@ -84,7 +91,9 @@ logger.enable("twitter") 
     | 
|
| 
       84 
91 
     | 
    
         
             
            `level="DEBUG"` позволяет увидеть информацию обо всех запросах.
         
     | 
| 
       85 
92 
     | 
    
         | 
| 
       86 
93 
     | 
    
         
             
            ### Аккаунт
         
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
      
 94 
     | 
    
         
            +
            `twitter.Account`
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
            #### Статусы аккаунта
         
     | 
| 
       88 
97 
     | 
    
         
             
            - `UNKNOWN` - Статус аккаунта не установлен. Это статус по умолчанию.
         
     | 
| 
       89 
98 
     | 
    
         
             
            - `BAD_TOKEN` - Неверный или мертвый токен.
         
     | 
| 
       90 
99 
     | 
    
         
             
            - `SUSPENDED` - Действие учетной записи приостановлено. Тем не менее возможен запрос данных, а также авторизация через OAuth и OAuth2.
         
     | 
| 
         @@ -92,18 +101,23 @@ logger.enable("twitter") 
     | 
|
| 
       92 
101 
     | 
    
         
             
            - `CONSENT_LOCKED` - Учетная запись заморожена (лок). Условия для разморозки неизвестны.
         
     | 
| 
       93 
102 
     | 
    
         
             
            - `GOOD` - Аккаунт в порядке.
         
     | 
| 
       94 
103 
     | 
    
         | 
| 
      
 104 
     | 
    
         
            +
            Метод `Client.establish_status()` устанавливает статус аккаунта.
         
     | 
| 
      
 105 
     | 
    
         
            +
            Также статус аккаунта может изменить любое взаимодействие с Twitter.
         
     | 
| 
      
 106 
     | 
    
         
            +
            Поэтому, во время работы может внезапно быть вызвано исключение семейства `twitter.errors.BadAccount`.
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
       95 
108 
     | 
    
         
             
            Не каждое взаимодействие с Twitter достоверно определяет статус аккаунта.
         
     | 
| 
       96 
     | 
    
         
            -
            Например, простой запрос данных об аккаунте честно вернет  
     | 
| 
      
 109 
     | 
    
         
            +
            Например, простой запрос данных об аккаунте честно вернет данные и не изменит статус, даже если действие вашей учетной записи приостановлено (`SUSPENDED`).
         
     | 
| 
       97 
110 
     | 
    
         | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
      
 111 
     | 
    
         
            +
            ### Клиент
         
     | 
| 
      
 112 
     | 
    
         
            +
            `twitter.Client`
         
     | 
| 
       99 
113 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
      
 114 
     | 
    
         
            +
            #### Настройка
         
     | 
| 
      
 115 
     | 
    
         
            +
            Клиент может быть сконфигурирован перед работой. Он принимает в себя следующие параметры:
         
     | 
| 
       102 
116 
     | 
    
         
             
            - `wait_on_rate_limit` Если включено, то при достижении Rate Limit будет ждать, вместо того, чтобы выбрасывать исключение. Включено по умолчанию.
         
     | 
| 
       103 
117 
     | 
    
         
             
            - `capsolver_api_key` API ключ сервиса [CapSolver](https://dashboard.capsolver.com/passport/register?inviteCode=m-aE3NeBGZLU). Нужен для автоматической разморозки аккаунта.
         
     | 
| 
       104 
118 
     | 
    
         
             
            - `max_unlock_attempts` Максимальное количество попыток разморозки аккаунта. По умолчанию: 5.
         
     | 
| 
       105 
119 
     | 
    
         
             
            - `auto_relogin` Если включено, то при невалидном токене (`BAD_TOKEN`) и предоставленных данных для авторизации (имя пользователя, пароль и totp_secret) будет произведен автоматический релогин (замена токена). Включено по умолчанию.
         
     | 
| 
       106 
     | 
    
         
            -
            - `update_account_info_on_startup` Если включено, то на старте будет автоматически запрошена информация об  
     | 
| 
      
 120 
     | 
    
         
            +
            - `update_account_info_on_startup` Если включено, то на старте будет автоматически запрошена информация об аккаунте, а также установлен его статус. Включено по умолчанию.
         
     | 
| 
       107 
121 
     | 
    
         
             
            - `**session_kwargs` Любые параметры, которые может принимать сессия `curl_cffi.requests.AsyncSession`. Например, можно передать параметр `proxy`.
         
     | 
| 
       108 
122 
     | 
    
         | 
| 
       109 
123 
     | 
    
         
             
            Пример настройки клиента:
         
     | 
| 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            twitter/__init__.py,sha256 
     | 
| 
      
 1 
     | 
    
         
            +
            twitter/__init__.py,sha256=yqTUBTKJca19jJBtmv9CML9Lfgvw1yjNew7i0f5v80Y,745
         
     | 
| 
       2 
2 
     | 
    
         
             
            twitter/_capsolver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         
     | 
| 
       3 
3 
     | 
    
         
             
            twitter/_capsolver/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         
     | 
| 
       4 
4 
     | 
    
         
             
            twitter/_capsolver/core/base.py,sha256=In3qDLgRh1z1UZLaLFgYcDEdnqW3d62PVzgEjU2S4BU,8883
         
     | 
| 
         @@ -6,18 +6,18 @@ twitter/_capsolver/core/config.py,sha256=8_eXT6N2hBheN2uCMNhqk8tLZRJjLDTYLK208fq 
     | 
|
| 
       6 
6 
     | 
    
         
             
            twitter/_capsolver/core/enum.py,sha256=ivfAEN6jrg3iaq5C3H7CuRqsvOloX1b8lF8cLa3zaiY,1741
         
     | 
| 
       7 
7 
     | 
    
         
             
            twitter/_capsolver/core/serializer.py,sha256=xPEUIPgytuw2wM1ubTY3RMhJGVyp_d3bokPTx0BjF0c,2602
         
     | 
| 
       8 
8 
     | 
    
         
             
            twitter/_capsolver/fun_captcha.py,sha256=VVbTmn08cGnvPMGdJmPxaLfAIPxyA68oTSAyEL8RWnU,10974
         
     | 
| 
       9 
     | 
    
         
            -
            twitter/account.py,sha256= 
     | 
| 
      
 9 
     | 
    
         
            +
            twitter/account.py,sha256=4ILOQR9Q9V5UbpIYYjS7_BPjpBszbQx6WcfdVWnpZdM,3180
         
     | 
| 
       10 
10 
     | 
    
         
             
            twitter/base/__init__.py,sha256=Q2ko0HeOS5tiBnDVKxxaZYetwRR3YXJ67ujL3oThGd4,141
         
     | 
| 
       11 
11 
     | 
    
         
             
            twitter/base/client.py,sha256=J_iL4ZGfwTbZ2gpjtFCbBxNgt7weJ55EeMGzYsLtjf4,500
         
     | 
| 
       12 
     | 
    
         
            -
            twitter/base/session.py,sha256= 
     | 
| 
       13 
     | 
    
         
            -
            twitter/client.py,sha256= 
     | 
| 
       14 
     | 
    
         
            -
            twitter/enums.py,sha256 
     | 
| 
       15 
     | 
    
         
            -
            twitter/errors.py,sha256= 
     | 
| 
       16 
     | 
    
         
            -
            twitter/models.py,sha256= 
     | 
| 
      
 12 
     | 
    
         
            +
            twitter/base/session.py,sha256=o4Rre_xpKUpHAqyIOJ57M3gn4_Bf2SksZi7uTF6s-8s,2102
         
     | 
| 
      
 13 
     | 
    
         
            +
            twitter/client.py,sha256=4ieUEhepYxJfSMu0k8fFtmMWU4oW9HNCL7tplyu5NRk,82365
         
     | 
| 
      
 14 
     | 
    
         
            +
            twitter/enums.py,sha256=RzQnvxEmMwGVTHZ7e3P3kAE6U9btvNZ3cqVMi7y9ItI,299
         
     | 
| 
      
 15 
     | 
    
         
            +
            twitter/errors.py,sha256=xYuwU8F-_XDGWOLvLUAT_U3sO1ZJOPQ7uOhe-SBo6vY,5610
         
     | 
| 
      
 16 
     | 
    
         
            +
            twitter/models.py,sha256=HuiZItosr1FibjkHNWZeIsEe9K52o7OBiBuvoNu0Rxc,5905
         
     | 
| 
       17 
17 
     | 
    
         
             
            twitter/utils/__init__.py,sha256=usxpfcRQ7zxTTgZ-i425tT7hIz73Pwh9FDj4t6O3dYg,663
         
     | 
| 
       18 
18 
     | 
    
         
             
            twitter/utils/file.py,sha256=Sz2KEF9DnL04aOP1XabuMYMMF4VR8dJ_KWMEVvQ666Y,1120
         
     | 
| 
       19 
19 
     | 
    
         
             
            twitter/utils/html.py,sha256=nrOJw0vUKfBaHgFaQSQIdXfvfZ8mdu84MU_s46kJTJ4,2087
         
     | 
| 
       20 
20 
     | 
    
         
             
            twitter/utils/other.py,sha256=9RIYF2AMdmNKIwClG3jBP7zlvxZPEgYfuHaIiOhURzM,1061
         
     | 
| 
       21 
     | 
    
         
            -
            tweepy_self-1. 
     | 
| 
       22 
     | 
    
         
            -
            tweepy_self-1. 
     | 
| 
       23 
     | 
    
         
            -
            tweepy_self-1. 
     | 
| 
      
 21 
     | 
    
         
            +
            tweepy_self-1.11.1.dist-info/METADATA,sha256=Eh-oQgZ1LcEmjq6MlT02n8xF-7ZdRK05Ndl5wHMrHPA,13754
         
     | 
| 
      
 22 
     | 
    
         
            +
            tweepy_self-1.11.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
         
     | 
| 
      
 23 
     | 
    
         
            +
            tweepy_self-1.11.1.dist-info/RECORD,,
         
     | 
    
        twitter/__init__.py
    CHANGED
    
    
    
        twitter/account.py
    CHANGED
    
    
    
        twitter/base/session.py
    CHANGED
    
    | 
         @@ -14,13 +14,14 @@ class BaseAsyncSession(requests.AsyncSession): 
     | 
|
| 
       14 
14 
     | 
    
         
             
                DEFAULT_HEADERS = {
         
     | 
| 
       15 
15 
     | 
    
         
             
                    "accept": "*/*",
         
     | 
| 
       16 
16 
     | 
    
         
             
                    "accept-language": "en-US,en",
         
     | 
| 
       17 
     | 
    
         
            -
                    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ 
     | 
| 
       18 
     | 
    
         
            -
                     
     | 
| 
      
 17 
     | 
    
         
            +
                    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
         
     | 
| 
      
 18 
     | 
    
         
            +
                    'Priority': 'u=1, i',
         
     | 
| 
      
 19 
     | 
    
         
            +
                    "sec-ch-ua": '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
         
     | 
| 
       19 
20 
     | 
    
         
             
                    "sec-ch-ua-platform": '"Windows"',
         
     | 
| 
       20 
21 
     | 
    
         
             
                    "sec-ch-ua-mobile": "?0",
         
     | 
| 
       21 
22 
     | 
    
         
             
                    "sec-fetch-dest": "empty",
         
     | 
| 
       22 
23 
     | 
    
         
             
                    "sec-fetch-mode": "cors",
         
     | 
| 
       23 
     | 
    
         
            -
                    "sec-fetch-site": " 
     | 
| 
      
 24 
     | 
    
         
            +
                    "sec-fetch-site": "ssame-site",
         
     | 
| 
       24 
25 
     | 
    
         
             
                    "connection": "keep-alive",
         
     | 
| 
       25 
26 
     | 
    
         
             
                }
         
     | 
| 
       26 
27 
     | 
    
         
             
                DEFAULT_IMPERSONATE = requests.BrowserType.chrome120
         
     | 
    
        twitter/client.py
    CHANGED
    
    | 
         @@ -22,12 +22,12 @@ from .errors import ( 
     | 
|
| 
       22 
22 
     | 
    
         
             
                RateLimited,
         
     | 
| 
       23 
23 
     | 
    
         
             
                ServerError,
         
     | 
| 
       24 
24 
     | 
    
         
             
                BadAccount,
         
     | 
| 
       25 
     | 
    
         
            -
                 
     | 
| 
       26 
     | 
    
         
            -
                 
     | 
| 
       27 
     | 
    
         
            -
                 
     | 
| 
       28 
     | 
    
         
            -
                 
     | 
| 
      
 25 
     | 
    
         
            +
                BadAccountToken,
         
     | 
| 
      
 26 
     | 
    
         
            +
                AccountLocked,
         
     | 
| 
      
 27 
     | 
    
         
            +
                AccountConsentLocked,
         
     | 
| 
      
 28 
     | 
    
         
            +
                AccountSuspended,
         
     | 
| 
      
 29 
     | 
    
         
            +
                AccountNotFound,
         
     | 
| 
       29 
30 
     | 
    
         
             
            )
         
     | 
| 
       30 
     | 
    
         
            -
            from .utils import to_json
         
     | 
| 
       31 
31 
     | 
    
         
             
            from .base import BaseHTTPClient
         
     | 
| 
       32 
32 
     | 
    
         
             
            from .account import Account, AccountStatus
         
     | 
| 
       33 
33 
     | 
    
         
             
            from .models import User, Tweet, Media, Subtask
         
     | 
| 
         @@ -41,17 +41,17 @@ from .utils import ( 
     | 
|
| 
       41 
41 
     | 
    
         
             
            class Client(BaseHTTPClient):
         
     | 
| 
       42 
42 
     | 
    
         
             
                _BEARER_TOKEN = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
         
     | 
| 
       43 
43 
     | 
    
         
             
                _DEFAULT_HEADERS = {
         
     | 
| 
       44 
     | 
    
         
            -
                    "authority": " 
     | 
| 
       45 
     | 
    
         
            -
                    "origin": "https:// 
     | 
| 
      
 44 
     | 
    
         
            +
                    "authority": "x.com",
         
     | 
| 
      
 45 
     | 
    
         
            +
                    "origin": "https://x.com",
         
     | 
| 
       46 
46 
     | 
    
         
             
                    "x-twitter-active-user": "yes",
         
     | 
| 
       47 
47 
     | 
    
         
             
                    "x-twitter-client-language": "en",
         
     | 
| 
       48 
48 
     | 
    
         
             
                }
         
     | 
| 
       49 
     | 
    
         
            -
                _GRAPHQL_URL = "https:// 
     | 
| 
      
 49 
     | 
    
         
            +
                _GRAPHQL_URL = "https://x.com/i/api/graphql"
         
     | 
| 
       50 
50 
     | 
    
         
             
                _ACTION_TO_QUERY_ID = {
         
     | 
| 
       51 
51 
     | 
    
         
             
                    "CreateRetweet": "ojPdsZsimiJrUGLR1sjUtA",
         
     | 
| 
       52 
52 
     | 
    
         
             
                    "FavoriteTweet": "lI07N6Otwv1PhnEgXILM7A",
         
     | 
| 
       53 
53 
     | 
    
         
             
                    "UnfavoriteTweet": "ZYKSe-w7KEslx3JhSIk5LA",
         
     | 
| 
       54 
     | 
    
         
            -
                    "CreateTweet": " 
     | 
| 
      
 54 
     | 
    
         
            +
                    "CreateTweet": "oB-5XsHNAbjvARJEc8CZFw",
         
     | 
| 
       55 
55 
     | 
    
         
             
                    "TweetResultByRestId": "V3vfsYzNEyD9tsf4xoFRgw",
         
     | 
| 
       56 
56 
     | 
    
         
             
                    "ModerateTweet": "p'jF:GVqCjTcZol0xcBJjw",
         
     | 
| 
       57 
57 
     | 
    
         
             
                    "DeleteTweet": "VaenaVgh5q5ih7kvyVjgtg",
         
     | 
| 
         @@ -62,9 +62,9 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       62 
62 
     | 
    
         
             
                    "Followers": "3yX7xr2hKjcZYnXt6cU6lQ",
         
     | 
| 
       63 
63 
     | 
    
         
             
                    "UserByScreenName": "G3KGOASz96M-Qu0nwmGXNg",
         
     | 
| 
       64 
64 
     | 
    
         
             
                    "UsersByRestIds": "itEhGywpgX9b3GJCzOtSrA",
         
     | 
| 
       65 
     | 
    
         
            -
                    "Viewer": " 
     | 
| 
      
 65 
     | 
    
         
            +
                    "Viewer": "-876iyxD1O_0X0BqeykjZA",
         
     | 
| 
       66 
66 
     | 
    
         
             
                }
         
     | 
| 
       67 
     | 
    
         
            -
                _CAPTCHA_URL = "https:// 
     | 
| 
      
 67 
     | 
    
         
            +
                _CAPTCHA_URL = "https://x.com/account/access"
         
     | 
| 
       68 
68 
     | 
    
         
             
                _CAPTCHA_SITE_KEY = "0152B4EB-D2DC-460A-89A1-629838B529C9"
         
     | 
| 
       69 
69 
     | 
    
         | 
| 
       70 
70 
     | 
    
         
             
                @classmethod
         
     | 
| 
         @@ -95,6 +95,8 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       95 
95 
     | 
    
         
             
                    self.auto_relogin = auto_relogin
         
     | 
| 
       96 
96 
     | 
    
         
             
                    self._update_account_info_on_startup = update_account_info_on_startup
         
     | 
| 
       97 
97 
     | 
    
         | 
| 
      
 98 
     | 
    
         
            +
                    self.gql = GQLClient(self)
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
       98 
100 
     | 
    
         
             
                async def __aenter__(self):
         
     | 
| 
       99 
101 
     | 
    
         
             
                    await self.on_startup()
         
     | 
| 
       100 
102 
     | 
    
         
             
                    return await super().__aenter__()
         
     | 
| 
         @@ -109,21 +111,25 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       109 
111 
     | 
    
         
             
                    wait_on_rate_limit: bool = None,
         
     | 
| 
       110 
112 
     | 
    
         
             
                    **kwargs,
         
     | 
| 
       111 
113 
     | 
    
         
             
                ) -> tuple[requests.Response, Any]:
         
     | 
| 
       112 
     | 
    
         
            -
                    cookies = kwargs["cookies"] = kwargs.get("cookies" 
     | 
| 
       113 
     | 
    
         
            -
                    headers = kwargs["headers"] = kwargs.get("headers" 
     | 
| 
       114 
     | 
    
         
            -
             
     | 
| 
      
 114 
     | 
    
         
            +
                    cookies = kwargs["cookies"] = kwargs.get("cookies", {})
         
     | 
| 
      
 115 
     | 
    
         
            +
                    headers = kwargs["headers"] = kwargs.get("headers", {})
         
     | 
| 
       115 
116 
     | 
    
         
             
                    if bearer:
         
     | 
| 
       116 
117 
     | 
    
         
             
                        headers["authorization"] = f"Bearer {self._BEARER_TOKEN}"
         
     | 
| 
       117 
     | 
    
         
            -
                        # headers["x-twitter-auth-type"] = "OAuth2Session"
         
     | 
| 
       118 
118 
     | 
    
         | 
| 
       119 
119 
     | 
    
         
             
                    if auth:
         
     | 
| 
       120 
120 
     | 
    
         
             
                        if not self.account.auth_token:
         
     | 
| 
       121 
121 
     | 
    
         
             
                            raise ValueError("No auth_token. Login before")
         
     | 
| 
       122 
122 
     | 
    
         | 
| 
       123 
123 
     | 
    
         
             
                        cookies["auth_token"] = self.account.auth_token
         
     | 
| 
      
 124 
     | 
    
         
            +
                        headers["x-twitter-auth-type"] = "OAuth2Session"
         
     | 
| 
       124 
125 
     | 
    
         
             
                        if self.account.ct0:
         
     | 
| 
       125 
126 
     | 
    
         
             
                            cookies["ct0"] = self.account.ct0
         
     | 
| 
       126 
127 
     | 
    
         
             
                            headers["x-csrf-token"] = self.account.ct0
         
     | 
| 
      
 128 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 129 
     | 
    
         
            +
                        if "auth_token" in cookies:
         
     | 
| 
      
 130 
     | 
    
         
            +
                            del cookies["auth_token"]
         
     | 
| 
      
 131 
     | 
    
         
            +
                        if "x-twitter-auth-type" in headers:
         
     | 
| 
      
 132 
     | 
    
         
            +
                            del headers["x-twitter-auth-type"]
         
     | 
| 
       127 
133 
     | 
    
         | 
| 
       128 
134 
     | 
    
         
             
                    # fmt: off
         
     | 
| 
       129 
135 
     | 
    
         
             
                    log_message = (f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
         
     | 
| 
         @@ -152,7 +158,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       152 
158 
     | 
    
         
             
                                 f"\nResponse data: {data}")
         
     | 
| 
       153 
159 
     | 
    
         
             
                    # fmt: on
         
     | 
| 
       154 
160 
     | 
    
         | 
| 
       155 
     | 
    
         
            -
                    if ct0 := self._session.cookies.get("ct0", domain=". 
     | 
| 
      
 161 
     | 
    
         
            +
                    if ct0 := self._session.cookies.get("ct0", domain=".x.com"):
         
     | 
| 
       156 
162 
     | 
    
         
             
                        self.account.ct0 = ct0
         
     | 
| 
       157 
163 
     | 
    
         | 
| 
       158 
164 
     | 
    
         
             
                    auth_token = self._session.cookies.get("auth_token")
         
     | 
| 
         @@ -172,9 +178,9 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       172 
178 
     | 
    
         
             
                        if isinstance(data, dict) and "errors" in data:
         
     | 
| 
       173 
179 
     | 
    
         
             
                            exc = HTTPException(response, data)
         
     | 
| 
       174 
180 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
                            if 141 in exc.api_codes:
         
     | 
| 
      
 181 
     | 
    
         
            +
                            if 141 in exc.api_codes or 37 in exc.api_codes:
         
     | 
| 
       176 
182 
     | 
    
         
             
                                self.account.status = AccountStatus.SUSPENDED
         
     | 
| 
       177 
     | 
    
         
            -
                                raise  
     | 
| 
      
 183 
     | 
    
         
            +
                                raise AccountSuspended(exc, self.account)
         
     | 
| 
       178 
184 
     | 
    
         | 
| 
       179 
185 
     | 
    
         
             
                            if 326 in exc.api_codes:
         
     | 
| 
       180 
186 
     | 
    
         
             
                                for error_data in exc.api_errors:
         
     | 
| 
         @@ -184,23 +190,29 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       184 
190 
     | 
    
         
             
                                        == "/i/flow/consent_flow"
         
     | 
| 
       185 
191 
     | 
    
         
             
                                    ):
         
     | 
| 
       186 
192 
     | 
    
         
             
                                        self.account.status = AccountStatus.CONSENT_LOCKED
         
     | 
| 
       187 
     | 
    
         
            -
                                        raise  
     | 
| 
      
 193 
     | 
    
         
            +
                                        raise AccountConsentLocked(exc, self.account)
         
     | 
| 
       188 
194 
     | 
    
         | 
| 
       189 
195 
     | 
    
         
             
                                self.account.status = AccountStatus.LOCKED
         
     | 
| 
       190 
     | 
    
         
            -
                                raise  
     | 
| 
      
 196 
     | 
    
         
            +
                                raise AccountLocked(exc, self.account)
         
     | 
| 
       191 
197 
     | 
    
         
             
                            raise exc
         
     | 
| 
       192 
198 
     | 
    
         | 
| 
       193 
199 
     | 
    
         
             
                        return response, data
         
     | 
| 
       194 
200 
     | 
    
         | 
| 
       195 
201 
     | 
    
         
             
                    if response.status_code == 400:
         
     | 
| 
       196 
     | 
    
         
            -
                         
     | 
| 
      
 202 
     | 
    
         
            +
                        exc = BadRequest(response, data)
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                        if 399 in exc.api_codes:
         
     | 
| 
      
 205 
     | 
    
         
            +
                            self.account.status = AccountStatus.NOT_FOUND
         
     | 
| 
      
 206 
     | 
    
         
            +
                            raise AccountNotFound(exc, self.account)
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
                        raise exc
         
     | 
| 
       197 
209 
     | 
    
         | 
| 
       198 
210 
     | 
    
         
             
                    if response.status_code == 401:
         
     | 
| 
       199 
211 
     | 
    
         
             
                        exc = Unauthorized(response, data)
         
     | 
| 
       200 
212 
     | 
    
         | 
| 
       201 
213 
     | 
    
         
             
                        if 32 in exc.api_codes:
         
     | 
| 
       202 
214 
     | 
    
         
             
                            self.account.status = AccountStatus.BAD_TOKEN
         
     | 
| 
       203 
     | 
    
         
            -
                            raise  
     | 
| 
      
 215 
     | 
    
         
            +
                            raise BadAccountToken(exc, self.account)
         
     | 
| 
       204 
216 
     | 
    
         | 
| 
       205 
217 
     | 
    
         
             
                        raise exc
         
     | 
| 
       206 
218 
     | 
    
         | 
| 
         @@ -209,7 +221,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       209 
221 
     | 
    
         | 
| 
       210 
222 
     | 
    
         
             
                        if 64 in exc.api_codes:
         
     | 
| 
       211 
223 
     | 
    
         
             
                            self.account.status = AccountStatus.SUSPENDED
         
     | 
| 
       212 
     | 
    
         
            -
                            raise  
     | 
| 
      
 224 
     | 
    
         
            +
                            raise AccountSuspended(exc, self.account)
         
     | 
| 
       213 
225 
     | 
    
         | 
| 
       214 
226 
     | 
    
         
             
                        if 326 in exc.api_codes:
         
     | 
| 
       215 
227 
     | 
    
         
             
                            for error_data in exc.api_errors:
         
     | 
| 
         @@ -218,10 +230,10 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       218 
230 
     | 
    
         
             
                                    and error_data.get("bounce_location") == "/i/flow/consent_flow"
         
     | 
| 
       219 
231 
     | 
    
         
             
                                ):
         
     | 
| 
       220 
232 
     | 
    
         
             
                                    self.account.status = AccountStatus.CONSENT_LOCKED
         
     | 
| 
       221 
     | 
    
         
            -
                                    raise  
     | 
| 
      
 233 
     | 
    
         
            +
                                    raise AccountConsentLocked(exc, self.account)
         
     | 
| 
       222 
234 
     | 
    
         | 
| 
       223 
235 
     | 
    
         
             
                            self.account.status = AccountStatus.LOCKED
         
     | 
| 
       224 
     | 
    
         
            -
                            raise  
     | 
| 
      
 236 
     | 
    
         
            +
                            raise AccountLocked(exc, self.account)
         
     | 
| 
       225 
237 
     | 
    
         | 
| 
       226 
238 
     | 
    
         
             
                        raise exc
         
     | 
| 
       227 
239 
     | 
    
         | 
| 
         @@ -261,19 +273,20 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       261 
273 
     | 
    
         
             
                    *,
         
     | 
| 
       262 
274 
     | 
    
         
             
                    auto_unlock: bool = True,
         
     | 
| 
       263 
275 
     | 
    
         
             
                    auto_relogin: bool = None,
         
     | 
| 
      
 276 
     | 
    
         
            +
                    rerequest_on_bad_ct0: bool = True,
         
     | 
| 
       264 
277 
     | 
    
         
             
                    **kwargs,
         
     | 
| 
       265 
278 
     | 
    
         
             
                ) -> tuple[requests.Response, Any]:
         
     | 
| 
       266 
279 
     | 
    
         
             
                    try:
         
     | 
| 
       267 
280 
     | 
    
         
             
                        return await self._request(method, url, **kwargs)
         
     | 
| 
       268 
281 
     | 
    
         | 
| 
       269 
     | 
    
         
            -
                    except  
     | 
| 
      
 282 
     | 
    
         
            +
                    except AccountLocked:
         
     | 
| 
       270 
283 
     | 
    
         
             
                        if not self.capsolver_api_key or not auto_unlock:
         
     | 
| 
       271 
284 
     | 
    
         
             
                            raise
         
     | 
| 
       272 
285 
     | 
    
         | 
| 
       273 
286 
     | 
    
         
             
                        await self.unlock()
         
     | 
| 
       274 
287 
     | 
    
         
             
                        return await self._request(method, url, **kwargs)
         
     | 
| 
       275 
288 
     | 
    
         | 
| 
       276 
     | 
    
         
            -
                    except  
     | 
| 
      
 289 
     | 
    
         
            +
                    except BadAccountToken:
         
     | 
| 
       277 
290 
     | 
    
         
             
                        if auto_relogin is None:
         
     | 
| 
       278 
291 
     | 
    
         
             
                            auto_relogin = self.auto_relogin
         
     | 
| 
       279 
292 
     | 
    
         
             
                        if (
         
     | 
| 
         @@ -284,11 +297,17 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       284 
297 
     | 
    
         
             
                            raise
         
     | 
| 
       285 
298 
     | 
    
         | 
| 
       286 
299 
     | 
    
         
             
                        await self.relogin()
         
     | 
| 
       287 
     | 
    
         
            -
                        return await self. 
     | 
| 
      
 300 
     | 
    
         
            +
                        return await self.request(method, url, auto_relogin=False, **kwargs)
         
     | 
| 
       288 
301 
     | 
    
         | 
| 
       289 
302 
     | 
    
         
             
                    except Forbidden as exc:
         
     | 
| 
       290 
     | 
    
         
            -
                        if  
     | 
| 
       291 
     | 
    
         
            -
                             
     | 
| 
      
 303 
     | 
    
         
            +
                        if (
         
     | 
| 
      
 304 
     | 
    
         
            +
                            rerequest_on_bad_ct0
         
     | 
| 
      
 305 
     | 
    
         
            +
                            and 353 in exc.api_codes
         
     | 
| 
      
 306 
     | 
    
         
            +
                            and "ct0" in exc.response.cookies
         
     | 
| 
      
 307 
     | 
    
         
            +
                        ):
         
     | 
| 
      
 308 
     | 
    
         
            +
                            return await self.request(
         
     | 
| 
      
 309 
     | 
    
         
            +
                                method, url, rerequest_on_bad_ct0=False, **kwargs
         
     | 
| 
      
 310 
     | 
    
         
            +
                            )
         
     | 
| 
       292 
311 
     | 
    
         
             
                        else:
         
     | 
| 
       293 
312 
     | 
    
         
             
                            raise
         
     | 
| 
       294 
313 
     | 
    
         | 
| 
         @@ -307,7 +326,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       307 
326 
     | 
    
         
             
                    scope: str,
         
     | 
| 
       308 
327 
     | 
    
         
             
                    response_type: str,
         
     | 
| 
       309 
328 
     | 
    
         
             
                ) -> str:
         
     | 
| 
       310 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 329 
     | 
    
         
            +
                    url = "https://x.com/i/api/2/oauth2/authorize"
         
     | 
| 
       311 
330 
     | 
    
         
             
                    querystring = {
         
     | 
| 
       312 
331 
     | 
    
         
             
                        "client_id": client_id,
         
     | 
| 
       313 
332 
     | 
    
         
             
                        "code_challenge": code_challenge,
         
     | 
| 
         @@ -329,7 +348,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       329 
348 
     | 
    
         
             
                    headers = {"content-type": "application/x-www-form-urlencoded"}
         
     | 
| 
       330 
349 
     | 
    
         
             
                    await self.request(
         
     | 
| 
       331 
350 
     | 
    
         
             
                        "POST",
         
     | 
| 
       332 
     | 
    
         
            -
                        "https:// 
     | 
| 
      
 351 
     | 
    
         
            +
                        "https://x.com/i/api/2/oauth2/authorize",
         
     | 
| 
       333 
352 
     | 
    
         
             
                        headers=headers,
         
     | 
| 
       334 
353 
     | 
    
         
             
                        data=data,
         
     | 
| 
       335 
354 
     | 
    
         
             
                    )
         
     | 
| 
         @@ -373,7 +392,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       373 
392 
     | 
    
         | 
| 
       374 
393 
     | 
    
         
             
                    :return: Response: html страница привязки приложения (аутентификации) старого типа.
         
     | 
| 
       375 
394 
     | 
    
         
             
                    """
         
     | 
| 
       376 
     | 
    
         
            -
                    url = "https://api. 
     | 
| 
      
 395 
     | 
    
         
            +
                    url = "https://api.x.com/oauth/authenticate"
         
     | 
| 
       377 
396 
     | 
    
         
             
                    oauth_params["oauth_token"] = oauth_token
         
     | 
| 
       378 
397 
     | 
    
         
             
                    response, _ = await self.request("GET", url, params=oauth_params)
         
     | 
| 
       379 
398 
     | 
    
         | 
| 
         @@ -391,7 +410,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       391 
410 
     | 
    
         
             
                    authenticity_token: str,
         
     | 
| 
       392 
411 
     | 
    
         
             
                    redirect_after_login_url: str,
         
     | 
| 
       393 
412 
     | 
    
         
             
                ) -> requests.Response:
         
     | 
| 
       394 
     | 
    
         
            -
                    url = "https://api. 
     | 
| 
      
 413 
     | 
    
         
            +
                    url = "https://api.x.com/oauth/authorize"
         
     | 
| 
       395 
414 
     | 
    
         
             
                    params = {
         
     | 
| 
       396 
415 
     | 
    
         
             
                        "redirect_after_login": redirect_after_login_url,
         
     | 
| 
       397 
416 
     | 
    
         
             
                        "authenticity_token": authenticity_token,
         
     | 
| 
         @@ -421,7 +440,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       421 
440 
     | 
    
         
             
                    return authenticity_token, redirect_url
         
     | 
| 
       422 
441 
     | 
    
         | 
| 
       423 
442 
     | 
    
         
             
                async def _update_account_username(self):
         
     | 
| 
       424 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 443 
     | 
    
         
            +
                    url = "https://x.com/i/api/1.1/account/settings.json"
         
     | 
| 
       425 
444 
     | 
    
         
             
                    response, response_json = await self.request("POST", url)
         
     | 
| 
       426 
445 
     | 
    
         
             
                    self.account.username = response_json["screen_name"]
         
     | 
| 
       427 
446 
     | 
    
         | 
| 
         @@ -447,9 +466,9 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       447 
466 
     | 
    
         
             
                        "withAuxiliaryUserLabels": False,
         
     | 
| 
       448 
467 
     | 
    
         
             
                    }
         
     | 
| 
       449 
468 
     | 
    
         
             
                    params = {
         
     | 
| 
       450 
     | 
    
         
            -
                        "variables":  
     | 
| 
       451 
     | 
    
         
            -
                        "features":  
     | 
| 
       452 
     | 
    
         
            -
                        "fieldToggles":  
     | 
| 
      
 469 
     | 
    
         
            +
                        "variables": variables,
         
     | 
| 
      
 470 
     | 
    
         
            +
                        "features": features,
         
     | 
| 
      
 471 
     | 
    
         
            +
                        "fieldToggles": field_toggles,
         
     | 
| 
       453 
472 
     | 
    
         
             
                    }
         
     | 
| 
       454 
473 
     | 
    
         
             
                    response, data = await self.request("GET", url, params=params)
         
     | 
| 
       455 
474 
     | 
    
         
             
                    if not data["data"]:
         
     | 
| 
         @@ -536,7 +555,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       536 
555 
     | 
    
         | 
| 
       537 
556 
     | 
    
         
             
                    :return: Media
         
     | 
| 
       538 
557 
     | 
    
         
             
                    """
         
     | 
| 
       539 
     | 
    
         
            -
                    url = "https://upload. 
     | 
| 
      
 558 
     | 
    
         
            +
                    url = "https://upload.x.com/1.1/media/upload.json"
         
     | 
| 
       540 
559 
     | 
    
         
             
                    payload = {"media_data": base64.b64encode(image)}
         
     | 
| 
       541 
560 
     | 
    
         
             
                    for attempt in range(attempts):
         
     | 
| 
       542 
561 
     | 
    
         
             
                        try:
         
     | 
| 
         @@ -561,7 +580,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       561 
580 
     | 
    
         
             
                                raise
         
     | 
| 
       562 
581 
     | 
    
         | 
| 
       563 
582 
     | 
    
         
             
                async def _follow_action(self, action: str, user_id: int | str) -> bool:
         
     | 
| 
       564 
     | 
    
         
            -
                    url = f"https:// 
     | 
| 
      
 583 
     | 
    
         
            +
                    url = f"https://x.com/i/api/1.1/friendships/{action}.json"
         
     | 
| 
       565 
584 
     | 
    
         
             
                    params = {
         
     | 
| 
       566 
585 
     | 
    
         
             
                        "include_profile_interstitial_type": "1",
         
     | 
| 
       567 
586 
     | 
    
         
             
                        "include_blocking": "1",
         
     | 
| 
         @@ -691,7 +710,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       691 
710 
     | 
    
         
             
                    return is_deleted
         
     | 
| 
       692 
711 
     | 
    
         | 
| 
       693 
712 
     | 
    
         
             
                async def pin_tweet(self, tweet_id: str | int) -> bool:
         
     | 
| 
       694 
     | 
    
         
            -
                    url = "https://api. 
     | 
| 
      
 713 
     | 
    
         
            +
                    url = "https://api.x.com/1.1/account/pin_tweet.json"
         
     | 
| 
       695 
714 
     | 
    
         
             
                    data = {
         
     | 
| 
       696 
715 
     | 
    
         
             
                        "tweet_mode": "extended",
         
     | 
| 
       697 
716 
     | 
    
         
             
                        "id": str(tweet_id),
         
     | 
| 
         @@ -741,9 +760,12 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       741 
760 
     | 
    
         
             
                        "longform_notetweets_consumption_enabled": True,
         
     | 
| 
       742 
761 
     | 
    
         
             
                        "responsive_web_twitter_article_tweet_consumption_enabled": True,
         
     | 
| 
       743 
762 
     | 
    
         
             
                        "tweet_awards_web_tipping_enabled": False,
         
     | 
| 
      
 763 
     | 
    
         
            +
                        "creator_subscriptions_quote_tweet_preview_enabled": False,
         
     | 
| 
       744 
764 
     | 
    
         
             
                        "longform_notetweets_rich_text_read_enabled": True,
         
     | 
| 
       745 
765 
     | 
    
         
             
                        "longform_notetweets_inline_media_enabled": True,
         
     | 
| 
      
 766 
     | 
    
         
            +
                        "articles_preview_enabled": True,
         
     | 
| 
       746 
767 
     | 
    
         
             
                        "rweb_video_timestamps_enabled": True,
         
     | 
| 
      
 768 
     | 
    
         
            +
                        "rweb_tipjar_consumption_enabled": True,
         
     | 
| 
       747 
769 
     | 
    
         
             
                        "responsive_web_graphql_exclude_directive_enabled": True,
         
     | 
| 
       748 
770 
     | 
    
         
             
                        "verified_phone_label_enabled": False,
         
     | 
| 
       749 
771 
     | 
    
         
             
                        "freedom_of_speech_not_reach_fetch_enabled": True,
         
     | 
| 
         @@ -866,7 +888,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       866 
888 
     | 
    
         
             
                    """
         
     | 
| 
       867 
889 
     | 
    
         
             
                    :return: Raw vote information
         
     | 
| 
       868 
890 
     | 
    
         
             
                    """
         
     | 
| 
       869 
     | 
    
         
            -
                    url = "https://caps. 
     | 
| 
      
 891 
     | 
    
         
            +
                    url = "https://caps.x.com/v2/capi/passthrough/1"
         
     | 
| 
       870 
892 
     | 
    
         
             
                    params = {
         
     | 
| 
       871 
893 
     | 
    
         
             
                        "twitter:string:card_uri": f"card://{card_id}",
         
     | 
| 
       872 
894 
     | 
    
         
             
                        "twitter:long:original_tweet_id": str(tweet_id),
         
     | 
| 
         @@ -915,8 +937,8 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       915 
937 
     | 
    
         
             
                        "responsive_web_enhance_cards_enabled": False,
         
     | 
| 
       916 
938 
     | 
    
         
             
                    }
         
     | 
| 
       917 
939 
     | 
    
         
             
                    params = {
         
     | 
| 
       918 
     | 
    
         
            -
                        "variables":  
     | 
| 
       919 
     | 
    
         
            -
                        "features":  
     | 
| 
      
 940 
     | 
    
         
            +
                        "variables": variables,
         
     | 
| 
      
 941 
     | 
    
         
            +
                        "features": features,
         
     | 
| 
       920 
942 
     | 
    
         
             
                    }
         
     | 
| 
       921 
943 
     | 
    
         
             
                    response, response_json = await self.request("GET", url, params=params)
         
     | 
| 
       922 
944 
     | 
    
         | 
| 
         @@ -1008,8 +1030,8 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1008 
1030 
     | 
    
         
             
                        "responsive_web_enhance_cards_enabled": False,
         
     | 
| 
       1009 
1031 
     | 
    
         
             
                    }
         
     | 
| 
       1010 
1032 
     | 
    
         
             
                    query = {
         
     | 
| 
       1011 
     | 
    
         
            -
                        "variables":  
     | 
| 
       1012 
     | 
    
         
            -
                        "features":  
     | 
| 
      
 1033 
     | 
    
         
            +
                        "variables": variables,
         
     | 
| 
      
 1034 
     | 
    
         
            +
                        "features": features,
         
     | 
| 
       1013 
1035 
     | 
    
         
             
                    }
         
     | 
| 
       1014 
1036 
     | 
    
         
             
                    response, data = await self.request("GET", url, params=query)
         
     | 
| 
       1015 
1037 
     | 
    
         
             
                    instructions = data["data"]["threaded_conversation_with_injections_v2"]["instructions"]  # type: ignore
         
     | 
| 
         @@ -1053,7 +1075,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1053 
1075 
     | 
    
         
             
                        "responsive_web_media_download_video_enabled": False,
         
     | 
| 
       1054 
1076 
     | 
    
         
             
                        "responsive_web_enhance_cards_enabled": False,
         
     | 
| 
       1055 
1077 
     | 
    
         
             
                    }
         
     | 
| 
       1056 
     | 
    
         
            -
                    params = {"variables":  
     | 
| 
      
 1078 
     | 
    
         
            +
                    params = {"variables": variables, "features": features}
         
     | 
| 
       1057 
1079 
     | 
    
         
             
                    response, data = await self.request("GET", url, params=params)
         
     | 
| 
       1058 
1080 
     | 
    
         | 
| 
       1059 
1081 
     | 
    
         
             
                    instructions = data["data"]["user"]["result"]["timeline_v2"]["timeline"][
         
     | 
| 
         @@ -1081,7 +1103,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1081 
1103 
     | 
    
         
             
                    """
         
     | 
| 
       1082 
1104 
     | 
    
         
             
                    :return: Image URL
         
     | 
| 
       1083 
1105 
     | 
    
         
             
                    """
         
     | 
| 
       1084 
     | 
    
         
            -
                    url = f"https://api. 
     | 
| 
      
 1106 
     | 
    
         
            +
                    url = f"https://api.x.com/1.1/account/update_profile_{type}.json"
         
     | 
| 
       1085 
1107 
     | 
    
         
             
                    params = {
         
     | 
| 
       1086 
1108 
     | 
    
         
             
                        "media_id": str(media_id),
         
     | 
| 
       1087 
1109 
     | 
    
         
             
                        "include_profile_interstitial_type": "1",
         
     | 
| 
         @@ -1116,7 +1138,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1116 
1138 
     | 
    
         
             
                    return await self._update_profile_image("banner", media_id)
         
     | 
| 
       1117 
1139 
     | 
    
         | 
| 
       1118 
1140 
     | 
    
         
             
                async def change_username(self, username: str) -> bool:
         
     | 
| 
       1119 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 1141 
     | 
    
         
            +
                    url = "https://x.com/i/api/1.1/account/settings.json"
         
     | 
| 
       1120 
1142 
     | 
    
         
             
                    payload = {"screen_name": username}
         
     | 
| 
       1121 
1143 
     | 
    
         
             
                    response, data = await self.request("POST", url, data=payload)
         
     | 
| 
       1122 
1144 
     | 
    
         
             
                    new_username = data["screen_name"]
         
     | 
| 
         @@ -1131,7 +1153,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1131 
1153 
     | 
    
         
             
                    if not self.account.password:
         
     | 
| 
       1132 
1154 
     | 
    
         
             
                        raise ValueError(f"Specify the current password before changing it")
         
     | 
| 
       1133 
1155 
     | 
    
         | 
| 
       1134 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 1156 
     | 
    
         
            +
                    url = "https://x.com/i/api/i/account/change_password.json"
         
     | 
| 
       1135 
1157 
     | 
    
         
             
                    payload = {
         
     | 
| 
       1136 
1158 
     | 
    
         
             
                        "current_password": self.account.password,
         
     | 
| 
       1137 
1159 
     | 
    
         
             
                        "password": password,
         
     | 
| 
         @@ -1155,7 +1177,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1155 
1177 
     | 
    
         
             
                    if name is None and description is None:
         
     | 
| 
       1156 
1178 
     | 
    
         
             
                        raise ValueError("Specify at least one param")
         
     | 
| 
       1157 
1179 
     | 
    
         | 
| 
       1158 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 1180 
     | 
    
         
            +
                    url = "https://x.com/i/api/1.1/account/update_profile.json"
         
     | 
| 
       1159 
1181 
     | 
    
         
             
                    # Создаем словарь data, включая в него только те ключи, для которых значения не равны None
         
     | 
| 
       1160 
1182 
     | 
    
         
             
                    payload = {
         
     | 
| 
       1161 
1183 
     | 
    
         
             
                        k: v
         
     | 
| 
         @@ -1180,7 +1202,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1180 
1202 
     | 
    
         
             
                    return updated
         
     | 
| 
       1181 
1203 
     | 
    
         | 
| 
       1182 
1204 
     | 
    
         
             
                async def establish_status(self):
         
     | 
| 
       1183 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 1205 
     | 
    
         
            +
                    url = "https://x.com/i/api/1.1/account/update_profile.json"
         
     | 
| 
       1184 
1206 
     | 
    
         
             
                    try:
         
     | 
| 
       1185 
1207 
     | 
    
         
             
                        await self.request("POST", url, auto_unlock=False, auto_relogin=False)
         
     | 
| 
       1186 
1208 
     | 
    
         
             
                        self.account.status = AccountStatus.GOOD
         
     | 
| 
         @@ -1195,7 +1217,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1195 
1217 
     | 
    
         
             
                    visibility: Literal["self", "mutualfollow"] = "self",
         
     | 
| 
       1196 
1218 
     | 
    
         
             
                    year_visibility: Literal["self"] = "self",
         
     | 
| 
       1197 
1219 
     | 
    
         
             
                ) -> bool:
         
     | 
| 
       1198 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 1220 
     | 
    
         
            +
                    url = "https://x.com/i/api/1.1/account/update_profile.json"
         
     | 
| 
       1199 
1221 
     | 
    
         
             
                    payload = {
         
     | 
| 
       1200 
1222 
     | 
    
         
             
                        "birthdate_day": day,
         
     | 
| 
       1201 
1223 
     | 
    
         
             
                        "birthdate_month": month,
         
     | 
| 
         @@ -1220,7 +1242,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1220 
1242 
     | 
    
         
             
                    """
         
     | 
| 
       1221 
1243 
     | 
    
         
             
                    :return: Event data
         
     | 
| 
       1222 
1244 
     | 
    
         
             
                    """
         
     | 
| 
       1223 
     | 
    
         
            -
                    url = "https://api. 
     | 
| 
      
 1245 
     | 
    
         
            +
                    url = "https://api.x.com/1.1/direct_messages/events/new.json"
         
     | 
| 
       1224 
1246 
     | 
    
         
             
                    payload = {
         
     | 
| 
       1225 
1247 
     | 
    
         
             
                        "event": {
         
     | 
| 
       1226 
1248 
     | 
    
         
             
                            "type": "message_create",
         
     | 
| 
         @@ -1242,7 +1264,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1242 
1264 
     | 
    
         | 
| 
       1243 
1265 
     | 
    
         
             
                    :return: Event data
         
     | 
| 
       1244 
1266 
     | 
    
         
             
                    """
         
     | 
| 
       1245 
     | 
    
         
            -
                    url = f"https://api. 
     | 
| 
      
 1267 
     | 
    
         
            +
                    url = f"https://api.x.com/2/dm_conversations/{conversation_id}/messages"
         
     | 
| 
       1246 
1268 
     | 
    
         
             
                    payload = {"text": text}
         
     | 
| 
       1247 
1269 
     | 
    
         
             
                    response, response_json = await self.request("POST", url, json=payload)
         
     | 
| 
       1248 
1270 
     | 
    
         
             
                    event_data = response_json["event"]
         
     | 
| 
         @@ -1252,7 +1274,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1252 
1274 
     | 
    
         
             
                    """
         
     | 
| 
       1253 
1275 
     | 
    
         
             
                    :return: Messages data
         
     | 
| 
       1254 
1276 
     | 
    
         
             
                    """
         
     | 
| 
       1255 
     | 
    
         
            -
                    url = "https:// 
     | 
| 
      
 1277 
     | 
    
         
            +
                    url = "https://x.com/i/api/1.1/dm/inbox_initial_state.json"
         
     | 
| 
       1256 
1278 
     | 
    
         
             
                    params = {
         
     | 
| 
       1257 
1279 
     | 
    
         
             
                        "nsfw_filtering_enabled": "false",
         
     | 
| 
       1258 
1280 
     | 
    
         
             
                        "filter_low_quality": "false",
         
     | 
| 
         @@ -1394,7 +1416,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1394 
1416 
     | 
    
         
             
                            verification_string=token,
         
     | 
| 
       1395 
1417 
     | 
    
         
             
                        )
         
     | 
| 
       1396 
1418 
     | 
    
         | 
| 
       1397 
     | 
    
         
            -
                        if response.url == "https:// 
     | 
| 
      
 1419 
     | 
    
         
            +
                        if response.url == "https://x.com/?lang=en":
         
     | 
| 
       1398 
1420 
     | 
    
         
             
                            break
         
     | 
| 
       1399 
1421 
     | 
    
         | 
| 
       1400 
1422 
     | 
    
         
             
                        (
         
     | 
| 
         @@ -1424,7 +1446,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1424 
1446 
     | 
    
         
             
                    await self.establish_status()
         
     | 
| 
       1425 
1447 
     | 
    
         | 
| 
       1426 
1448 
     | 
    
         
             
                async def update_backup_code(self):
         
     | 
| 
       1427 
     | 
    
         
            -
                    url = "https://api. 
     | 
| 
      
 1449 
     | 
    
         
            +
                    url = "https://api.x.com/1.1/account/backup_code.json"
         
     | 
| 
       1428 
1450 
     | 
    
         
             
                    response, response_json = await self.request("GET", url)
         
     | 
| 
       1429 
1451 
     | 
    
         
             
                    self.account.backup_code = response_json["codes"][0]
         
     | 
| 
       1430 
1452 
     | 
    
         | 
| 
         @@ -1432,7 +1454,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1432 
1454 
     | 
    
         
             
                    """
         
     | 
| 
       1433 
1455 
     | 
    
         
             
                    :return: flow_token and subtasks
         
     | 
| 
       1434 
1456 
     | 
    
         
             
                    """
         
     | 
| 
       1435 
     | 
    
         
            -
                    url = "https://api. 
     | 
| 
      
 1457 
     | 
    
         
            +
                    url = "https://api.x.com/1.1/onboarding/task.json"
         
     | 
| 
       1436 
1458 
     | 
    
         
             
                    response, data = await self.request("POST", url, **request_kwargs)
         
     | 
| 
       1437 
1459 
     | 
    
         
             
                    subtasks = [
         
     | 
| 
       1438 
1460 
     | 
    
         
             
                        Subtask.from_raw_data(subtask_data) for subtask_data in data["subtasks"]
         
     | 
| 
         @@ -1609,6 +1631,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1609 
1631 
     | 
    
         
             
                async def _viewer(self):
         
     | 
| 
       1610 
1632 
     | 
    
         
             
                    url, query_id = self._action_to_url("Viewer")
         
     | 
| 
       1611 
1633 
     | 
    
         
             
                    features = {
         
     | 
| 
      
 1634 
     | 
    
         
            +
                        "rweb_tipjar_consumption_enabled": True,
         
     | 
| 
       1612 
1635 
     | 
    
         
             
                        "responsive_web_graphql_exclude_directive_enabled": True,
         
     | 
| 
       1613 
1636 
     | 
    
         
             
                        "verified_phone_label_enabled": False,
         
     | 
| 
       1614 
1637 
     | 
    
         
             
                        "creator_subscriptions_tweet_preview_api_enabled": True,
         
     | 
| 
         @@ -1633,12 +1656,12 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1633 
1656 
     | 
    
         | 
| 
       1634 
1657 
     | 
    
         
             
                    :return: guest_token
         
     | 
| 
       1635 
1658 
     | 
    
         
             
                    """
         
     | 
| 
       1636 
     | 
    
         
            -
                     
     | 
| 
       1637 
     | 
    
         
            -
             
     | 
| 
       1638 
     | 
    
         
            -
             
     | 
| 
       1639 
     | 
    
         
            -
             
     | 
| 
       1640 
     | 
    
         
            -
                     
     | 
| 
       1641 
     | 
    
         
            -
                    return guest_token
         
     | 
| 
      
 1659 
     | 
    
         
            +
                    response, data = await self._request(
         
     | 
| 
      
 1660 
     | 
    
         
            +
                        "POST",
         
     | 
| 
      
 1661 
     | 
    
         
            +
                        "https://api.x.com/1.1/guest/activate.json",
         
     | 
| 
      
 1662 
     | 
    
         
            +
                        auth=False,
         
     | 
| 
      
 1663 
     | 
    
         
            +
                    )
         
     | 
| 
      
 1664 
     | 
    
         
            +
                    return data["guest_token"]
         
     | 
| 
       1642 
1665 
     | 
    
         | 
| 
       1643 
1666 
     | 
    
         
             
                async def _login(self) -> bool:
         
     | 
| 
       1644 
1667 
     | 
    
         
             
                    update_backup_code = False
         
     | 
| 
         @@ -1733,6 +1756,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1733 
1756 
     | 
    
         
             
                            else:
         
     | 
| 
       1734 
1757 
     | 
    
         
             
                                raise
         
     | 
| 
       1735 
1758 
     | 
    
         | 
| 
      
 1759 
     | 
    
         
            +
                    await self._viewer()
         
     | 
| 
       1736 
1760 
     | 
    
         
             
                    await self._complete_subtask(flow_token, [])
         
     | 
| 
       1737 
1761 
     | 
    
         
             
                    return update_backup_code
         
     | 
| 
       1738 
1762 
     | 
    
         | 
| 
         @@ -1750,7 +1774,6 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1750 
1774 
     | 
    
         
             
                        raise ValueError("No password")
         
     | 
| 
       1751 
1775 
     | 
    
         | 
| 
       1752 
1776 
     | 
    
         
             
                    update_backup_code = await self._login()
         
     | 
| 
       1753 
     | 
    
         
            -
                    await self._viewer()
         
     | 
| 
       1754 
1777 
     | 
    
         | 
| 
       1755 
1778 
     | 
    
         
             
                    if update_backup_code:
         
     | 
| 
       1756 
1779 
     | 
    
         
             
                        await self.update_backup_code()
         
     | 
| 
         @@ -1774,7 +1797,7 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1774 
1797 
     | 
    
         
             
                    if not self.account.id:
         
     | 
| 
       1775 
1798 
     | 
    
         
             
                        await self.update_account_info()
         
     | 
| 
       1776 
1799 
     | 
    
         | 
| 
       1777 
     | 
    
         
            -
                    url = f"https:// 
     | 
| 
      
 1800 
     | 
    
         
            +
                    url = f"https://x.com/i/api/1.1/strato/column/User/{self.account.id}/account-security/twoFactorAuthSettings2"
         
     | 
| 
       1778 
1801 
     | 
    
         
             
                    response, data = await self.request("GET", url)
         
     | 
| 
       1779 
1802 
     | 
    
         
             
                    # fmt: off
         
     | 
| 
       1780 
1803 
     | 
    
         
             
                    return "Totp" in [method_data["twoFactorType"] for method_data in data["methods"]]
         
     | 
| 
         @@ -1926,3 +1949,148 @@ class Client(BaseHTTPClient): 
     | 
|
| 
       1926 
1949 
     | 
    
         
             
                        raise ValueError("Password required to enable TOTP")
         
     | 
| 
       1927 
1950 
     | 
    
         | 
| 
       1928 
1951 
     | 
    
         
             
                    await self._enable_totp()
         
     | 
| 
      
 1952 
     | 
    
         
            +
             
     | 
| 
      
 1953 
     | 
    
         
            +
             
     | 
| 
      
 1954 
     | 
    
         
            +
            class GQLClient:
         
     | 
| 
      
 1955 
     | 
    
         
            +
                _GRAPHQL_URL = "https://x.com/i/api/graphql"
         
     | 
| 
      
 1956 
     | 
    
         
            +
                _OPERATION_TO_QUERY_ID = {
         
     | 
| 
      
 1957 
     | 
    
         
            +
                    "CreateRetweet": "ojPdsZsimiJrUGLR1sjUtA",
         
     | 
| 
      
 1958 
     | 
    
         
            +
                    "FavoriteTweet": "lI07N6Otwv1PhnEgXILM7A",
         
     | 
| 
      
 1959 
     | 
    
         
            +
                    "UnfavoriteTweet": "ZYKSe-w7KEslx3JhSIk5LA",
         
     | 
| 
      
 1960 
     | 
    
         
            +
                    "CreateTweet": "v0en1yVV-Ybeek8ClmXwYw",
         
     | 
| 
      
 1961 
     | 
    
         
            +
                    "TweetResultByRestId": "V3vfsYzNEyD9tsf4xoFRgw",
         
     | 
| 
      
 1962 
     | 
    
         
            +
                    "ModerateTweet": "p'jF:GVqCjTcZol0xcBJjw",
         
     | 
| 
      
 1963 
     | 
    
         
            +
                    "DeleteTweet": "VaenaVgh5q5ih7kvyVjgtg",
         
     | 
| 
      
 1964 
     | 
    
         
            +
                    "UserTweets": "V1ze5q3ijDS1VeLwLY0m7g",
         
     | 
| 
      
 1965 
     | 
    
         
            +
                    "TweetDetail": "VWFGPVAGkZMGRKGe3GFFnA",
         
     | 
| 
      
 1966 
     | 
    
         
            +
                    "ProfileSpotlightsQuery": "9zwVLJ48lmVUk8u_Gh9DmA",
         
     | 
| 
      
 1967 
     | 
    
         
            +
                    "Following": "t-BPOrMIduGUJWO_LxcvNQ",
         
     | 
| 
      
 1968 
     | 
    
         
            +
                    "Followers": "3yX7xr2hKjcZYnXt6cU6lQ",
         
     | 
| 
      
 1969 
     | 
    
         
            +
                    "UserByScreenName": "G3KGOASz96M-Qu0nwmGXNg",
         
     | 
| 
      
 1970 
     | 
    
         
            +
                    "UsersByRestIds": "itEhGywpgX9b3GJCzOtSrA",
         
     | 
| 
      
 1971 
     | 
    
         
            +
                    "Viewer": "-876iyxD1O_0X0BqeykjZA",
         
     | 
| 
      
 1972 
     | 
    
         
            +
                }
         
     | 
| 
      
 1973 
     | 
    
         
            +
                _DEFAULT_VARIABLES = {
         
     | 
| 
      
 1974 
     | 
    
         
            +
                    "count": 1000,
         
     | 
| 
      
 1975 
     | 
    
         
            +
                    "withSafetyModeUserFields": True,
         
     | 
| 
      
 1976 
     | 
    
         
            +
                    "includePromotedContent": True,
         
     | 
| 
      
 1977 
     | 
    
         
            +
                    "withQuickPromoteEligibilityTweetFields": True,
         
     | 
| 
      
 1978 
     | 
    
         
            +
                    "withVoice": True,
         
     | 
| 
      
 1979 
     | 
    
         
            +
                    "withV2Timeline": True,
         
     | 
| 
      
 1980 
     | 
    
         
            +
                    "withDownvotePerspective": False,
         
     | 
| 
      
 1981 
     | 
    
         
            +
                    "withBirdwatchNotes": True,
         
     | 
| 
      
 1982 
     | 
    
         
            +
                    "withCommunity": True,
         
     | 
| 
      
 1983 
     | 
    
         
            +
                    "withSuperFollowsUserFields": True,
         
     | 
| 
      
 1984 
     | 
    
         
            +
                    "withReactionsMetadata": False,
         
     | 
| 
      
 1985 
     | 
    
         
            +
                    "withReactionsPerspective": False,
         
     | 
| 
      
 1986 
     | 
    
         
            +
                    "withSuperFollowsTweetFields": True,
         
     | 
| 
      
 1987 
     | 
    
         
            +
                    "isMetatagsQuery": False,
         
     | 
| 
      
 1988 
     | 
    
         
            +
                    "withReplays": True,
         
     | 
| 
      
 1989 
     | 
    
         
            +
                    "withClientEventToken": False,
         
     | 
| 
      
 1990 
     | 
    
         
            +
                    "withAttachments": True,
         
     | 
| 
      
 1991 
     | 
    
         
            +
                    "withConversationQueryHighlights": True,
         
     | 
| 
      
 1992 
     | 
    
         
            +
                    "withMessageQueryHighlights": True,
         
     | 
| 
      
 1993 
     | 
    
         
            +
                    "withMessages": True,
         
     | 
| 
      
 1994 
     | 
    
         
            +
                }
         
     | 
| 
      
 1995 
     | 
    
         
            +
                _DEFAULT_FEATURES = {
         
     | 
| 
      
 1996 
     | 
    
         
            +
                    "c9s_tweet_anatomy_moderator_badge_enabled": True,
         
     | 
| 
      
 1997 
     | 
    
         
            +
                    "responsive_web_home_pinned_timelines_enabled": True,
         
     | 
| 
      
 1998 
     | 
    
         
            +
                    "blue_business_profile_image_shape_enabled": True,
         
     | 
| 
      
 1999 
     | 
    
         
            +
                    "creator_subscriptions_tweet_preview_api_enabled": True,
         
     | 
| 
      
 2000 
     | 
    
         
            +
                    "freedom_of_speech_not_reach_fetch_enabled": True,
         
     | 
| 
      
 2001 
     | 
    
         
            +
                    "graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
         
     | 
| 
      
 2002 
     | 
    
         
            +
                    "graphql_timeline_v2_bookmark_timeline": True,
         
     | 
| 
      
 2003 
     | 
    
         
            +
                    "hidden_profile_likes_enabled": True,
         
     | 
| 
      
 2004 
     | 
    
         
            +
                    "highlights_tweets_tab_ui_enabled": True,
         
     | 
| 
      
 2005 
     | 
    
         
            +
                    "interactive_text_enabled": True,
         
     | 
| 
      
 2006 
     | 
    
         
            +
                    "longform_notetweets_consumption_enabled": True,
         
     | 
| 
      
 2007 
     | 
    
         
            +
                    "longform_notetweets_inline_media_enabled": True,
         
     | 
| 
      
 2008 
     | 
    
         
            +
                    "longform_notetweets_rich_text_read_enabled": True,
         
     | 
| 
      
 2009 
     | 
    
         
            +
                    "longform_notetweets_richtext_consumption_enabled": True,
         
     | 
| 
      
 2010 
     | 
    
         
            +
                    "profile_foundations_tweet_stats_enabled": True,
         
     | 
| 
      
 2011 
     | 
    
         
            +
                    "profile_foundations_tweet_stats_tweet_frequency": True,
         
     | 
| 
      
 2012 
     | 
    
         
            +
                    "responsive_web_birdwatch_note_limit_enabled": True,
         
     | 
| 
      
 2013 
     | 
    
         
            +
                    "responsive_web_edit_tweet_api_enabled": True,
         
     | 
| 
      
 2014 
     | 
    
         
            +
                    "responsive_web_enhance_cards_enabled": False,
         
     | 
| 
      
 2015 
     | 
    
         
            +
                    "responsive_web_graphql_exclude_directive_enabled": True,
         
     | 
| 
      
 2016 
     | 
    
         
            +
                    "responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
         
     | 
| 
      
 2017 
     | 
    
         
            +
                    "responsive_web_graphql_timeline_navigation_enabled": True,
         
     | 
| 
      
 2018 
     | 
    
         
            +
                    "responsive_web_media_download_video_enabled": False,
         
     | 
| 
      
 2019 
     | 
    
         
            +
                    "responsive_web_text_conversations_enabled": False,
         
     | 
| 
      
 2020 
     | 
    
         
            +
                    "responsive_web_twitter_article_data_v2_enabled": True,
         
     | 
| 
      
 2021 
     | 
    
         
            +
                    "responsive_web_twitter_article_tweet_consumption_enabled": False,
         
     | 
| 
      
 2022 
     | 
    
         
            +
                    "responsive_web_twitter_blue_verified_badge_is_enabled": True,
         
     | 
| 
      
 2023 
     | 
    
         
            +
                    "rweb_lists_timeline_redesign_enabled": True,
         
     | 
| 
      
 2024 
     | 
    
         
            +
                    "spaces_2022_h2_clipping": True,
         
     | 
| 
      
 2025 
     | 
    
         
            +
                    "spaces_2022_h2_spaces_communities": True,
         
     | 
| 
      
 2026 
     | 
    
         
            +
                    "standardized_nudges_misinfo": True,
         
     | 
| 
      
 2027 
     | 
    
         
            +
                    "subscriptions_verification_info_verified_since_enabled": True,
         
     | 
| 
      
 2028 
     | 
    
         
            +
                    "tweet_awards_web_tipping_enabled": False,
         
     | 
| 
      
 2029 
     | 
    
         
            +
                    "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": True,
         
     | 
| 
      
 2030 
     | 
    
         
            +
                    "tweetypie_unmention_optimization_enabled": True,
         
     | 
| 
      
 2031 
     | 
    
         
            +
                    "verified_phone_label_enabled": False,
         
     | 
| 
      
 2032 
     | 
    
         
            +
                    "vibe_api_enabled": True,
         
     | 
| 
      
 2033 
     | 
    
         
            +
                    "view_counts_everywhere_api_enabled": True,
         
     | 
| 
      
 2034 
     | 
    
         
            +
                    "hidden_profile_subscriptions_enabled": True,
         
     | 
| 
      
 2035 
     | 
    
         
            +
                    "subscriptions_verification_info_is_identity_verified_enabled": True,
         
     | 
| 
      
 2036 
     | 
    
         
            +
                }
         
     | 
| 
      
 2037 
     | 
    
         
            +
             
     | 
| 
      
 2038 
     | 
    
         
            +
                @classmethod
         
     | 
| 
      
 2039 
     | 
    
         
            +
                def _operation_to_url(cls, operation: str) -> tuple[str, str]:
         
     | 
| 
      
 2040 
     | 
    
         
            +
                    """
         
     | 
| 
      
 2041 
     | 
    
         
            +
                    :return: URL and Query ID
         
     | 
| 
      
 2042 
     | 
    
         
            +
                    """
         
     | 
| 
      
 2043 
     | 
    
         
            +
                    query_id = cls._OPERATION_TO_QUERY_ID[operation]
         
     | 
| 
      
 2044 
     | 
    
         
            +
                    url = f"{cls._GRAPHQL_URL}/{query_id}/{operation}"
         
     | 
| 
      
 2045 
     | 
    
         
            +
                    return url, query_id
         
     | 
| 
      
 2046 
     | 
    
         
            +
             
     | 
| 
      
 2047 
     | 
    
         
            +
                def __init__(self, client: Client):
         
     | 
| 
      
 2048 
     | 
    
         
            +
                    self._client = client
         
     | 
| 
      
 2049 
     | 
    
         
            +
             
     | 
| 
      
 2050 
     | 
    
         
            +
                async def gql_request(
         
     | 
| 
      
 2051 
     | 
    
         
            +
                    self, method, operation, **kwargs
         
     | 
| 
      
 2052 
     | 
    
         
            +
                ) -> tuple[requests.Response, dict]:
         
     | 
| 
      
 2053 
     | 
    
         
            +
                    url, query_id = self._operation_to_url(operation)
         
     | 
| 
      
 2054 
     | 
    
         
            +
             
     | 
| 
      
 2055 
     | 
    
         
            +
                    if method == "POST":
         
     | 
| 
      
 2056 
     | 
    
         
            +
                        payload = kwargs["json"] = kwargs.get("json") or {}
         
     | 
| 
      
 2057 
     | 
    
         
            +
                        payload["queryId"] = query_id
         
     | 
| 
      
 2058 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 2059 
     | 
    
         
            +
                        params = kwargs["params"] = kwargs.get("params") or {}
         
     | 
| 
      
 2060 
     | 
    
         
            +
                        ...
         
     | 
| 
      
 2061 
     | 
    
         
            +
             
     | 
| 
      
 2062 
     | 
    
         
            +
                    response, data = await self._client.request(method, url, **kwargs)
         
     | 
| 
      
 2063 
     | 
    
         
            +
                    return response, data["data"]
         
     | 
| 
      
 2064 
     | 
    
         
            +
             
     | 
| 
      
 2065 
     | 
    
         
            +
                async def user_by_username(self, username: str) -> User | None:
         
     | 
| 
      
 2066 
     | 
    
         
            +
                    features = self._DEFAULT_FEATURES
         
     | 
| 
      
 2067 
     | 
    
         
            +
                    variables = self._DEFAULT_VARIABLES
         
     | 
| 
      
 2068 
     | 
    
         
            +
                    variables["screen_name"] = username
         
     | 
| 
      
 2069 
     | 
    
         
            +
                    params = {
         
     | 
| 
      
 2070 
     | 
    
         
            +
                        "variables": variables,
         
     | 
| 
      
 2071 
     | 
    
         
            +
                        "features": features,
         
     | 
| 
      
 2072 
     | 
    
         
            +
                    }
         
     | 
| 
      
 2073 
     | 
    
         
            +
                    response, data = await self.gql_request(
         
     | 
| 
      
 2074 
     | 
    
         
            +
                        "GET", "UserByScreenName", params=params
         
     | 
| 
      
 2075 
     | 
    
         
            +
                    )
         
     | 
| 
      
 2076 
     | 
    
         
            +
                    return User.from_raw_data(data["user"]["result"]) if data else None
         
     | 
| 
      
 2077 
     | 
    
         
            +
             
     | 
| 
      
 2078 
     | 
    
         
            +
                async def users_by_ids(
         
     | 
| 
      
 2079 
     | 
    
         
            +
                    self, user_ids: Iterable[str | int]
         
     | 
| 
      
 2080 
     | 
    
         
            +
                ) -> dict[int : User | Account]:
         
     | 
| 
      
 2081 
     | 
    
         
            +
                    features = self._DEFAULT_FEATURES
         
     | 
| 
      
 2082 
     | 
    
         
            +
                    variables = self._DEFAULT_VARIABLES
         
     | 
| 
      
 2083 
     | 
    
         
            +
                    variables["userIds"] = list({str(user_id) for user_id in user_ids})
         
     | 
| 
      
 2084 
     | 
    
         
            +
                    params = {
         
     | 
| 
      
 2085 
     | 
    
         
            +
                        "variables": variables,
         
     | 
| 
      
 2086 
     | 
    
         
            +
                        "features": features,
         
     | 
| 
      
 2087 
     | 
    
         
            +
                    }
         
     | 
| 
      
 2088 
     | 
    
         
            +
                    response, data = await self.gql_request("GET", "UsersByRestIds", params=params)
         
     | 
| 
      
 2089 
     | 
    
         
            +
             
     | 
| 
      
 2090 
     | 
    
         
            +
                    users = {}
         
     | 
| 
      
 2091 
     | 
    
         
            +
                    for user_data in data["users"]:
         
     | 
| 
      
 2092 
     | 
    
         
            +
                        user = User.from_raw_data(user_data["result"])
         
     | 
| 
      
 2093 
     | 
    
         
            +
                        users[user.id] = user
         
     | 
| 
      
 2094 
     | 
    
         
            +
                        if user.id == self._client.account.id:
         
     | 
| 
      
 2095 
     | 
    
         
            +
                            users[self._client.account.id] = self._client.account
         
     | 
| 
      
 2096 
     | 
    
         
            +
                    return users
         
     | 
    
        twitter/enums.py
    CHANGED
    
    
    
        twitter/errors.py
    CHANGED
    
    | 
         @@ -13,10 +13,11 @@ __all__ = [ 
     | 
|
| 
       13 
13 
     | 
    
         
             
                "RateLimited",
         
     | 
| 
       14 
14 
     | 
    
         
             
                "ServerError",
         
     | 
| 
       15 
15 
     | 
    
         
             
                "BadAccount",
         
     | 
| 
       16 
     | 
    
         
            -
                " 
     | 
| 
       17 
     | 
    
         
            -
                " 
     | 
| 
       18 
     | 
    
         
            -
                " 
     | 
| 
       19 
     | 
    
         
            -
                " 
     | 
| 
      
 16 
     | 
    
         
            +
                "BadAccountToken",
         
     | 
| 
      
 17 
     | 
    
         
            +
                "AccountLocked",
         
     | 
| 
      
 18 
     | 
    
         
            +
                "AccountConsentLocked",
         
     | 
| 
      
 19 
     | 
    
         
            +
                "AccountSuspended",
         
     | 
| 
      
 20 
     | 
    
         
            +
                "AccountNotFound",
         
     | 
| 
       20 
21 
     | 
    
         
             
            ]
         
     | 
| 
       21 
22 
     | 
    
         | 
| 
       22 
23 
     | 
    
         | 
| 
         @@ -149,7 +150,7 @@ class BadAccount(TwitterException): 
     | 
|
| 
       149 
150 
     | 
    
         
             
                    super().__init__(exception_message)
         
     | 
| 
       150 
151 
     | 
    
         | 
| 
       151 
152 
     | 
    
         | 
| 
       152 
     | 
    
         
            -
            class  
     | 
| 
      
 153 
     | 
    
         
            +
            class BadAccountToken(BadAccount):
         
     | 
| 
       153 
154 
     | 
    
         
             
                def __init__(self, http_exception: "HTTPException", account: Account):
         
     | 
| 
       154 
155 
     | 
    
         
             
                    exception_message = (
         
     | 
| 
       155 
156 
     | 
    
         
             
                        "Bad Twitter account's auth_token. Relogin to get new token."
         
     | 
| 
         @@ -157,7 +158,7 @@ class BadToken(BadAccount): 
     | 
|
| 
       157 
158 
     | 
    
         
             
                    super().__init__(http_exception, account, exception_message)
         
     | 
| 
       158 
159 
     | 
    
         | 
| 
       159 
160 
     | 
    
         | 
| 
       160 
     | 
    
         
            -
            class  
     | 
| 
      
 161 
     | 
    
         
            +
            class AccountLocked(BadAccount):
         
     | 
| 
       161 
162 
     | 
    
         
             
                def __init__(self, http_exception: "HTTPException", account: Account):
         
     | 
| 
       162 
163 
     | 
    
         
             
                    exception_message = (
         
     | 
| 
       163 
164 
     | 
    
         
             
                        f"Twitter account is locked."
         
     | 
| 
         @@ -166,13 +167,19 @@ class Locked(BadAccount): 
     | 
|
| 
       166 
167 
     | 
    
         
             
                    super().__init__(http_exception, account, exception_message)
         
     | 
| 
       167 
168 
     | 
    
         | 
| 
       168 
169 
     | 
    
         | 
| 
       169 
     | 
    
         
            -
            class  
     | 
| 
      
 170 
     | 
    
         
            +
            class AccountConsentLocked(BadAccount):
         
     | 
| 
       170 
171 
     | 
    
         
             
                def __init__(self, http_exception: "HTTPException", account: Account):
         
     | 
| 
       171 
172 
     | 
    
         
             
                    exception_message = f"Twitter account is locked."
         
     | 
| 
       172 
173 
     | 
    
         
             
                    super().__init__(http_exception, account, exception_message)
         
     | 
| 
       173 
174 
     | 
    
         | 
| 
       174 
175 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
            class  
     | 
| 
      
 176 
     | 
    
         
            +
            class AccountSuspended(BadAccount):
         
     | 
| 
       176 
177 
     | 
    
         
             
                def __init__(self, http_exception: "HTTPException", account: Account):
         
     | 
| 
       177 
178 
     | 
    
         
             
                    exception_message = f"Twitter account is suspended."
         
     | 
| 
       178 
179 
     | 
    
         
             
                    super().__init__(http_exception, account, exception_message)
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
            class AccountNotFound(BadAccount):
         
     | 
| 
      
 183 
     | 
    
         
            +
                def __init__(self, http_exception: "HTTPException", account: Account):
         
     | 
| 
      
 184 
     | 
    
         
            +
                    exception_message = f"Twitter account not found or deleted."
         
     | 
| 
      
 185 
     | 
    
         
            +
                    super().__init__(http_exception, account, exception_message)
         
     | 
    
        twitter/models.py
    CHANGED
    
    | 
         @@ -26,6 +26,9 @@ class Media(BaseModel): 
     | 
|
| 
       26 
26 
     | 
    
         
             
                def __str__(self):
         
     | 
| 
       27 
27 
     | 
    
         
             
                    return str(self.id)
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
                def __hash__(self):
         
     | 
| 
      
 30 
     | 
    
         
            +
                    return hash(self.id)
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       29 
32 
     | 
    
         | 
| 
       30 
33 
     | 
    
         
             
            class User(BaseModel):
         
     | 
| 
       31 
34 
     | 
    
         
             
                # fmt: off
         
     | 
| 
         @@ -46,6 +49,9 @@ class User(BaseModel): 
     | 
|
| 
       46 
49 
     | 
    
         
             
                def __repr__(self):
         
     | 
| 
       47 
50 
     | 
    
         
             
                    return f"{self.__class__.__name__}(id={self.id}, username={self.username})"
         
     | 
| 
       48 
51 
     | 
    
         | 
| 
      
 52 
     | 
    
         
            +
                def __hash__(self):
         
     | 
| 
      
 53 
     | 
    
         
            +
                    return hash(self.id)
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
       49 
55 
     | 
    
         
             
                @classmethod
         
     | 
| 
       50 
56 
     | 
    
         
             
                def from_raw_data(cls, data: dict):
         
     | 
| 
       51 
57 
     | 
    
         
             
                    legacy = data["legacy"]
         
     | 
| 
         @@ -105,6 +111,9 @@ class Tweet(BaseModel): 
     | 
|
| 
       105 
111 
     | 
    
         
             
                def __repr__(self):
         
     | 
| 
       106 
112 
     | 
    
         
             
                    return f"{self.__class__.__name__}(id={self.id}, user_id={self.user.id})"
         
     | 
| 
       107 
113 
     | 
    
         | 
| 
      
 114 
     | 
    
         
            +
                def __hash__(self):
         
     | 
| 
      
 115 
     | 
    
         
            +
                    return hash(self.id)
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
       108 
117 
     | 
    
         
             
                @property
         
     | 
| 
       109 
118 
     | 
    
         
             
                def short_text(self) -> str:
         
     | 
| 
       110 
119 
     | 
    
         
             
                    return f"{self.text[:32]}..." if len(self.text) > 16 else self.text
         
     | 
| 
         @@ -162,6 +171,9 @@ class Subtask(BaseModel): 
     | 
|
| 
       162 
171 
     | 
    
         
             
                detail_text: Optional[str] = None
         
     | 
| 
       163 
172 
     | 
    
         
             
                raw_data: dict
         
     | 
| 
       164 
173 
     | 
    
         | 
| 
      
 174 
     | 
    
         
            +
                def __hash__(self):
         
     | 
| 
      
 175 
     | 
    
         
            +
                    return hash(self.id)
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
       165 
177 
     | 
    
         
             
                @classmethod
         
     | 
| 
       166 
178 
     | 
    
         
             
                def from_raw_data(cls, data: dict) -> "Subtask":
         
     | 
| 
       167 
179 
     | 
    
         
             
                    task = {"id": data["subtask_id"]}
         
     | 
| 
         
            File without changes
         
     |