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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions twikit/guest/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def __init__(
self._token = TOKEN
self._user_agent = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/122.0.0.0 Safari/537.36')
'Chrome/133.0.0.0 Safari/537.36')
self._guest_token: str | None = None # set when activate method is called
self.gql = GQLClient(self)
self.v11 = V11Client(self)
Expand All @@ -109,10 +109,19 @@ async def request(
if not self.client_transaction.home_page_response:
cookies_backup = dict(self.http.cookies).copy()
ct_headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': f'{self.language},{self.language.split("-")[0]};q=0.9',
'Cache-Control': 'no-cache',
'Referer': f'https://{DOMAIN}',
'User-Agent': self._user_agent
'User-Agent': self._user_agent,
'sec-ch-ua': '"Not.A/Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1'
}
await self.client_transaction.init(self.http, ct_headers)
self.http.cookies = cookies_backup
Expand Down Expand Up @@ -186,6 +195,12 @@ def _base_headers(self) -> dict[str, str]:
'content-type': 'application/json',
'X-Twitter-Active-User': 'yes',
'Referer': f'https://{DOMAIN}',
'sec-ch-ua': '"Not.A/Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
}

if self.language is not None:
Expand Down
46 changes: 36 additions & 10 deletions twikit/x_client_transaction/transaction.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import logging
import bs4
import math
import time
Expand All @@ -15,7 +16,15 @@
ON_DEMAND_FILE_REGEX = re.compile(
r"""['|\"]{1}ondemand\.s['|\"]{1}:\s*['|\"]{1}([\w]*)['|\"]{1}""", flags=(re.VERBOSE | re.MULTILINE))
INDICES_REGEX = re.compile(
r"""(\(\w{1}\[(\d{1,2})\],\s*16\))+""", flags=(re.VERBOSE | re.MULTILINE))
r"""\(?(\w+)\[(\d{1,2})\]\s*,\s*16\)?""", flags=(re.VERBOSE | re.MULTILINE))


logger = logging.getLogger(__name__)

# Fallback constants for when key extraction fails
FALLBACK_KEY = 'mentUisV_1yPzH_3IcNS_nRaF_R_b'
FALLBACK_ROW_INDEX = 2
FALLBACK_KEY_BYTES_INDICES = [13, 14, 7]


class ClientTransaction:
Expand All @@ -31,12 +40,21 @@ async def init(self, session, headers):
home_page_response = await handle_x_migration(session, headers)

self.home_page_response = self.validate_response(home_page_response)
self.DEFAULT_ROW_INDEX, self.DEFAULT_KEY_BYTES_INDICES = await self.get_indices(
self.home_page_response, session, headers)
self.key = self.get_key(response=self.home_page_response)
self.key_bytes = self.get_key_bytes(key=self.key)
self.animation_key = self.get_animation_key(
key_bytes=self.key_bytes, response=self.home_page_response)
try:
self.DEFAULT_ROW_INDEX, self.DEFAULT_KEY_BYTES_INDICES = await self.get_indices(
self.home_page_response, session, headers)
self.key = self.get_key(response=self.home_page_response)
self.key_bytes = self.get_key_bytes(key=self.key)
self.animation_key = self.get_animation_key(
key_bytes=self.key_bytes, response=self.home_page_response)
except (AttributeError, ValueError, KeyError, Exception) as e:
# Fallback to defaults to prevent complete initialization failure
self.key = FALLBACK_KEY
self.key_bytes = self.get_key_bytes(self.key)
self.DEFAULT_ROW_INDEX = FALLBACK_ROW_INDEX
self.DEFAULT_KEY_BYTES_INDICES = FALLBACK_KEY_BYTES_INDICES
self.animation_key = "0"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
logger.warning(f"Twikit Patch Initialization Warning: {e}, using fallback defaults")

async def get_indices(self, home_page_response, session, headers):
key_byte_indices = []
Expand Down Expand Up @@ -142,10 +160,18 @@ def generate_transaction_id(self, method: str, path: str, response=None, key=Non
time_now = time_now or math.floor(
(time.time() * 1000 - 1682924400 * 1000) / 1000)
time_now_bytes = [(time_now >> (i * 8)) & 0xFF for i in range(4)]
key = key or self.key or self.get_key(response)
key = key or getattr(self, 'key', None)
if not key:
try:
key = self.get_key(response)
except Exception:
key = FALLBACK_KEY
key_bytes = self.get_key_bytes(key)
animation_key = animation_key or self.animation_key or self.get_animation_key(
key_bytes, response)
try:
Comment on lines +163 to +170
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard key-byte decoding to avoid a remaining transaction-generation crash path.

Line 169 can still raise if key is malformed (for example, caller-supplied), which bypasses the intended robustness and aborts generate_transaction_id.

🔧 Proposed hardening
         key = key or getattr(self, 'key', None)
         if not key:
             try:
                 key = self.get_key(response)
             except Exception:
                 key = FALLBACK_KEY
-        key_bytes = self.get_key_bytes(key)
+        try:
+            key_bytes = self.get_key_bytes(key)
+        except Exception as e:
+            logger.warning(
+                "Twikit Patch Key Decode Warning: %s, using fallback key bytes", e
+            )
+            key = FALLBACK_KEY
+            key_bytes = getattr(self, "key_bytes", None) or self.get_key_bytes(FALLBACK_KEY)
🧰 Tools
🪛 Ruff (0.15.6)

[warning] 167-167: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@twikit/x_client_transaction/transaction.py` around lines 163 - 170, The call
to get_key_bytes(key) in generate_transaction_id can still raise if a malformed
caller-supplied key slips through; wrap the get_key_bytes(key) call in a
try/except and fall back to a safe value (e.g. derive bytes from FALLBACK_KEY or
a predefined FALLBACK_KEY_BYTES) and ensure key_bytes is always set before
proceeding; update the generate_transaction_id flow so exceptions from
get_key_bytes are caught and handled similarly to the existing get_key error
handling, referencing get_key, get_key_bytes, FALLBACK_KEY and the key_bytes
variable.

animation_key = animation_key or getattr(self, 'animation_key', None) or self.get_animation_key(
key_bytes, response)
except Exception:
animation_key = "0"
# hash_val = hashlib.sha256(f"{method}!{path}!{time_now}bird{animation_key}".encode()).digest()
hash_val = hashlib.sha256(
f"{method}!{path}!{time_now}{self.DEFAULT_KEYWORD}{animation_key}".encode()).digest()
Expand Down