Skip to content

fix: update search_timeline to use POST to resolve 404 error#412

Open
deltathecreatorr wants to merge 5 commits intod60:mainfrom
deltathecreatorr:main
Open

fix: update search_timeline to use POST to resolve 404 error#412
deltathecreatorr wants to merge 5 commits intod60:mainfrom
deltathecreatorr:main

Conversation

@deltathecreatorr
Copy link
Copy Markdown

@deltathecreatorr deltathecreatorr commented Mar 29, 2026

SsearchTimeline GET requests are being blocked, changed to a POST request so that the query variables are in the request body now.

In gql.py, I changed the gql_get on line 159 to gql_post and now the ability to search for tweets is operational again.

Summary by Sourcery

Update Twitter client internals to restore search functionality and align ondemand script parsing with current response format.

Bug Fixes:

  • Switch search timeline GraphQL requests from GET to POST so search works when GET is blocked.
  • Update ondemand script URL extraction to correctly resolve the current ondemand JavaScript filename and indices.

Summary by CodeRabbit

  • Chores

    • Expanded development environment ignore patterns to reduce noise from Python and virtual environment artifacts.
  • Refactor

    • Switched timeline network requests to a more robust method for improved reliability.
    • Hardened on-demand asset discovery and parsing to fail more deterministically and improve stability.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 29, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Switches GraphQL search_timeline from GET to POST to fix 404s and updates the X client transaction logic for resolving the ondemand.s JavaScript asset by using new response patterns for the file index and hash.

Sequence diagram for search_timeline using POST instead of GET

sequenceDiagram
    actor User
    participant TwikitClient
    participant TwitterGraphQLAPI

    User ->> TwikitClient: search_timeline(query, cursor, product)
    TwikitClient ->> TwikitClient: build variables and features
    TwikitClient ->> TwitterGraphQLAPI: HTTP POST /graphql SEARCH_TIMELINE
    TwitterGraphQLAPI -->> TwikitClient: 200 OK (timeline data)
    TwikitClient -->> User: search results
Loading

Updated class diagram for gql client and transaction index resolution

classDiagram
    class TwikitClient {
        +search_timeline(query, cursor, product)
        +gql_get(endpoint, variables, features)
        +gql_post(endpoint, variables, features)
    }

    class XClientTransaction {
        +ON_DEMAND_FILE_REGEX: re.Pattern
        +ON_DEMAND_HASH_PATTERN: str
        +get_indices(home_page_response, session, headers)
    }

    TwikitClient <.. XClientTransaction: uses async HTTP session
Loading

File-Level Changes

Change Details Files
Update ondemand.s asset discovery logic to match new response format and derive full filename and URL before fetching indices.
  • Replace previous ON_DEMAND_FILE_REGEX with a typed pattern that captures the numeric ondemand.s index from the response.
  • Introduce ON_DEMAND_HASH_PATTERN to capture the corresponding hexadecimal hash for the ondemand.s asset.
  • In get_indices, extract the ondemand index with the new regex, then build and apply a second regex to capture the asset hash from the same response.
  • Construct the ondemand.s asset URL using the discovered hash instead of the old direct suffix scheme and reuse existing logic to fetch and parse key byte indices from the JS file.
twikit/x_client_transaction/transaction.py
Use POST instead of GET for the search_timeline GraphQL call so variables are sent in the request body and 404 issues are resolved.
  • Change search_timeline to call gql_post instead of gql_get while keeping variables and FEATURES unchanged.
twikit/client/gql.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

Updated .gitignore with Python/environment patterns, changed GraphQL timeline requests from GET to POST (including queryId in JSON), and tightened on-demand script discovery: new hash-extraction step, stricter regexes, and fail-fast behavior when expected matches are absent.

Changes

Cohort / File(s) Summary
Configuration
\.gitignore
Added Python and environment ignore patterns: __pycache__/, *.pyc, *.pyo, *.pyd, .env, venv/, env/. Reformatted existing node_modules entry.
GraphQL Client
twikit/client/gql.py
search_timeline now uses gql_post(...) (HTTP POST with JSON payload including queryId) instead of gql_get(...). Variables/cursor handling is preserved.
Transaction Handler
twikit/x_client_transaction/transaction.py
Enforced stricter on-demand script discovery: replaced on-demand file regex, added ON_DEMAND_HASH_PATTERN, derive hex filename from page, construct ondemand.s.{filename}a.js URL, fetch script, extract INDICES_REGEX matches, and raise on missing matches or empty KEY_BYTE indices.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant ClientTxn as ClientTransaction
participant Page as PageHost
participant OnDemand as OnDemandScript
participant Parser as RegexParser

ClientTxn->>Page: GET homepage HTML
Page-->>ClientTxn: HTML (contains hash/markers)
ClientTxn->>OnDemand: GET /path/ondemand.s.{hex}a.js
OnDemand-->>ClientTxn: JS (contains indices)
ClientTxn->>Parser: Run INDICES_REGEX extraction
Parser-->>ClientTxn: indices OR error

mermaid
sequenceDiagram
participant GQL as GQLClient
participant API as GraphQLServer

GQL->>API: POST /graphql (JSON: { queryId, variables })
API-->>GQL: JSON response (timeline payload)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through patterns, neat and small,
Switched GET to POST — heard the server call.
I sniffed a hex in page and script,
Tightened my regex, made it strict.
🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: update search_timeline to use POST to resolve 404 error' accurately and specifically describes the main change in the PR (changing search_timeline from GET to POST), which is the primary modification in gql.py that restores search functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The new ON_DEMAND_FILE_REGEX.search(...).group(1) and subsequent regex.search(...).group(1) calls assume the matches always exist; consider restoring/adding if match is None handling to avoid unexpected AttributeError in cases where the response format changes.
  • You now call str(response) and str(on_demand_file_response.text) multiple times for regex searches; capturing these once into local variables (e.g., html = str(response)) will make the code clearer and slightly more efficient.
  • The commented-out legacy code in get_indices (#on_demand_file = ..., etc.) appears obsolete after the new approach; consider removing it to keep the function focused and easier to read.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `ON_DEMAND_FILE_REGEX.search(...).group(1)` and subsequent `regex.search(...).group(1)` calls assume the matches always exist; consider restoring/adding `if match is None` handling to avoid unexpected `AttributeError` in cases where the response format changes.
- You now call `str(response)` and `str(on_demand_file_response.text)` multiple times for regex searches; capturing these once into local variables (e.g., `html = str(response)`) will make the code clearer and slightly more efficient.
- The commented-out legacy code in `get_indices` (`#on_demand_file = ...`, etc.) appears obsolete after the new approach; consider removing it to keep the function focused and easier to read.

## Individual Comments

### Comment 1
<location path="twikit/x_client_transaction/transaction.py" line_range="45-50" />
<code_context>
-                str(on_demand_file_response.text))
-            for item in key_byte_indices_match:
-                key_byte_indices.append(item.group(2))
+        on_demand_file_index = ON_DEMAND_FILE_REGEX.search(str(response)).group(1)
+        regex = re.compile(ON_DEMAND_HASH_PATTERN.format(on_demand_file_index))
+        #on_demand_file = ON_DEMAND_FILE_REGEX.search(str(response))
+        #if on_demand_file:
+           #on_demand_file_url = f"https://abs.twimg.com/responsive-web/client-web/ondemand.s.{on_demand_file.group(1)}a.js"  
+        filename = regex.search(str(response)).group(1)
+        on_demand_file_url = f"https://abs.twimg.com/responsive-web/client-web/ondemand.s.{filename}a.js"
+        on_demand_file_response = await session.request(method="GET", url=on_demand_file_url, headers=headers)
</code_context>
<issue_to_address>
**issue (bug_risk):** Regular expression `.group(1)` calls can raise if no match is found, changing the previous error-handling behavior.

The previous code only used the match when `on_demand_file` was truthy and raised a clear error when indices couldn’t be extracted. The new code calls `ON_DEMAND_FILE_REGEX.search(...).group(1)` and `regex.search(...).group(1)` without checking the result, so a non‑match will raise `AttributeError` instead of a controlled, descriptive exception. Please restore an explicit check (or catch the error) and raise a clear message when the response doesn’t match the expected pattern.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread twikit/x_client_transaction/transaction.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
twikit/x_client_transaction/transaction.py (1)

47-49: Remove commented-out code.

The commented-out lines are remnants of the old implementation and should be removed for cleanliness. Git history preserves the old code if needed.

♻️ Proposed fix
         on_demand_file_index = ON_DEMAND_FILE_REGEX.search(str(response)).group(1)
         regex = re.compile(ON_DEMAND_HASH_PATTERN.format(on_demand_file_index))
-        `#on_demand_file` = ON_DEMAND_FILE_REGEX.search(str(response))
-        `#if` on_demand_file:
-           `#on_demand_file_url` = f"https://abs.twimg.com/responsive-web/client-web/ondemand.s.{on_demand_file.group(1)}a.js"  
         filename = regex.search(str(response)).group(1)
🤖 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 47 - 49, Remove the
three commented-out lines that reference ON_DEMAND_FILE_REGEX and
on_demand_file_url in transaction.py (the block containing "#on_demand_file =
ON_DEMAND_FILE_REGEX.search(str(response))", "#if on_demand_file:", and the
"#on_demand_file_url = ..." line); simply delete that commented legacy code so
the file contains no leftover commented implementation—rely on git history if
the old logic needs to be restored.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@twikit/x_client_transaction/transaction.py`:
- Around line 45-50: The code calls ON_DEMAND_FILE_REGEX.search(...) and
regex.search(...) then immediately .group(1) which can raise AttributeError if
no match is found; update the get_indices logic to null-check the search results
before calling .group(1): check the result of
ON_DEMAND_FILE_REGEX.search(str(response)) and if None gracefully handle (e.g.,
log and return/raise a descriptive error) and only compile/execute
ON_DEMAND_HASH_PATTERN when you have a valid on_demand_file_index, then
similarly check regex.search(str(response)) for None before extracting filename;
reference the existing symbols ON_DEMAND_FILE_REGEX, ON_DEMAND_HASH_PATTERN and
the function that contains this code (get_indices) to locate where to add the
guards and error handling.

---

Nitpick comments:
In `@twikit/x_client_transaction/transaction.py`:
- Around line 47-49: Remove the three commented-out lines that reference
ON_DEMAND_FILE_REGEX and on_demand_file_url in transaction.py (the block
containing "#on_demand_file = ON_DEMAND_FILE_REGEX.search(str(response))", "#if
on_demand_file:", and the "#on_demand_file_url = ..." line); simply delete that
commented legacy code so the file contains no leftover commented
implementation—rely on git history if the old logic needs to be restored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 39d328fe-fb82-4ffe-bd06-97e6953c9125

📥 Commits

Reviewing files that changed from the base of the PR and between c3b7220 and 23ca86c.

📒 Files selected for processing (3)
  • .gitignore
  • twikit/client/gql.py
  • twikit/x_client_transaction/transaction.py

Comment thread twikit/x_client_transaction/transaction.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
twikit/x_client_transaction/transaction.py (1)

45-51: ⚠️ Potential issue | 🔴 Critical

Null checks are placed after .group(1) calls, making them ineffective.

The checks on lines 46-47 and 50-51 cannot prevent AttributeError because .group(1) is called directly on the .search() result. If the regex doesn't match, .search() returns None, and calling .group(1) on None raises AttributeError before the null check is ever reached.

🐛 Proposed fix to add proper null-checks
-        on_demand_file_index = ON_DEMAND_FILE_REGEX.search(str(response)).group(1)
-        if not on_demand_file_index:
-            raise Exception("Failed to match ON_DEMAND_FILE_REGEX in the home page response") 
-        regex = re.compile(ON_DEMAND_HASH_PATTERN.format(on_demand_file_index)) 
-        filename = regex.search(str(response)).group(1)
-        if not filename:
-            raise Exception(f"Failed to match the hash pattern for on_demand_file index {on_demand_file_index}")
+        on_demand_match = ON_DEMAND_FILE_REGEX.search(str(response))
+        if not on_demand_match:
+            raise Exception("Failed to match ON_DEMAND_FILE_REGEX in the home page response")
+        on_demand_file_index = on_demand_match.group(1)
+        regex = re.compile(ON_DEMAND_HASH_PATTERN.format(on_demand_file_index))
+        hash_match = regex.search(str(response))
+        if not hash_match:
+            raise Exception(f"Failed to match the hash pattern for on_demand_file index {on_demand_file_index}")
+        filename = hash_match.group(1)
🤖 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 45 - 51, The code
calls .group(1) directly on regex.search(...) which can return None; change this
to capture the Match objects first and null-check them before calling .group(1):
assign m1 = ON_DEMAND_FILE_REGEX.search(str(response)), if m1 is None raise the
existing exception, then set on_demand_file_index = m1.group(1); likewise build
regex = re.compile(ON_DEMAND_HASH_PATTERN.format(on_demand_file_index)), then
assign m2 = regex.search(str(response)) and if m2 is None raise the
filename-matching exception, otherwise set filename = m2.group(1); reference
ON_DEMAND_FILE_REGEX, ON_DEMAND_HASH_PATTERN, response, on_demand_file_index,
filename.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@twikit/x_client_transaction/transaction.py`:
- Around line 45-51: The code calls .group(1) directly on regex.search(...)
which can return None; change this to capture the Match objects first and
null-check them before calling .group(1): assign m1 =
ON_DEMAND_FILE_REGEX.search(str(response)), if m1 is None raise the existing
exception, then set on_demand_file_index = m1.group(1); likewise build regex =
re.compile(ON_DEMAND_HASH_PATTERN.format(on_demand_file_index)), then assign m2
= regex.search(str(response)) and if m2 is None raise the filename-matching
exception, otherwise set filename = m2.group(1); reference ON_DEMAND_FILE_REGEX,
ON_DEMAND_HASH_PATTERN, response, on_demand_file_index, filename.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40cb3df4-67ae-48db-8d50-03c67d21e67f

📥 Commits

Reviewing files that changed from the base of the PR and between 23ca86c and 0648a03.

📒 Files selected for processing (1)
  • twikit/x_client_transaction/transaction.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
twikit/x_client_transaction/transaction.py (1)

15-16: Quote handling inconsistency between regex patterns.

ON_DEMAND_FILE_REGEX matches both single and double quotes (["']), but ON_DEMAND_HASH_PATTERN only matches double quotes (\"). If Twitter's bundler output uses single quotes for hash values, the extraction will fail.

Consider aligning the quote handling for consistency:

♻️ Suggested fix for consistent quote handling
-ON_DEMAND_HASH_PATTERN: str = r',{}:\"([0-9a-f]+)\"'
+ON_DEMAND_HASH_PATTERN: str = r",{}:[\"']([0-9a-f]+)[\"']"
🤖 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 15 - 16,
ON_DEMAND_FILE_REGEX accepts both single and double quotes but
ON_DEMAND_HASH_PATTERN only matches double quotes, so hash extraction can fail
for single-quoted output; update ON_DEMAND_HASH_PATTERN to allow both quote
types (mirror the ["'] usage from ON_DEMAND_FILE_REGEX) so the pattern
consistently matches single or double quoted hash values when used by the code
that extracts on-demand hashes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@twikit/x_client_transaction/transaction.py`:
- Around line 15-16: ON_DEMAND_FILE_REGEX accepts both single and double quotes
but ON_DEMAND_HASH_PATTERN only matches double quotes, so hash extraction can
fail for single-quoted output; update ON_DEMAND_HASH_PATTERN to allow both quote
types (mirror the ["'] usage from ON_DEMAND_FILE_REGEX) so the pattern
consistently matches single or double quoted hash values when used by the code
that extracts on-demand hashes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 612f6261-add4-4216-8f0c-cd3c2a513940

📥 Commits

Reviewing files that changed from the base of the PR and between 0648a03 and 213c875.

📒 Files selected for processing (1)
  • twikit/x_client_transaction/transaction.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant