Skip to content

feat: support long-form tweets via CreateNoteTweet#60

Open
rakei076 wants to merge 1 commit into
public-clis:mainfrom
rakei076:feat-create-note-tweet
Open

feat: support long-form tweets via CreateNoteTweet#60
rakei076 wants to merge 1 commit into
public-clis:mainfrom
rakei076:feat-create-note-tweet

Conversation

@rakei076
Copy link
Copy Markdown

Closes #54

Summary

Adds automatic routing to the CreateNoteTweet GraphQL endpoint when the weighted character count of a post exceeds 280, enabling X Premium users to post long-form tweets from the CLI. Standard tweets (≤ 280 weighted) continue to use CreateTweet with no behavior change.

Changes

File Change
twitter_cli/graphql.py Register CreateNoteTweet queryId; add 5 feature flags required by the long-form endpoint
twitter_cli/client.py Add _weighted_length() helper (mirrors X's ASCII=1 / other=2 rule); route create_tweet() to CreateNoteTweet when weighted > 280

Total diff: +47 / −3 across 2 files.

The silent-failure bug

CreateNoteTweet requires variables.disallowed_reply_options to be present (with null as the canonical value). Without this field X responds HTTP 200 with an empty tweet_results object — the call looks successful but no tweet is created. This is likely why the endpoint hasn't been wired up before: the failure mode is invisible without comparing responses against a successful one.

Setting variables["disallowed_reply_options"] = None resolves it; the response then contains the full tweet_results.result.rest_id as expected.

Testing

Verified on an X Premium account with Japanese long-form posts (~400 weighted characters):

INFO twitter_cli.client: Tweet weighted=379 > 280, using CreateNoteTweet (long-form)
ok: true
url: https://x.com/i/status/...

Standard ASCII tweets ≤ 280 weighted still route through CreateTweet — no regression.

Notes

  • CreateNoteTweet queryId (yeInFtqpUoABoBE_YWPYgA) was extracted from x.com's current JS bundle and added to FALLBACK_QUERY_IDS; the existing _scan_bundles flow will keep it fresh if X rotates the hash.
  • The 5 added feature flags are the minimum set required for the endpoint to accept the request; missing any of them produces a 400 with a "missing feature" message.

…s#54)

Adds automatic routing to the CreateNoteTweet GraphQL endpoint when
the weighted character count of a post exceeds 280 (X Premium
long-form post limit). Standard tweets continue to use CreateTweet.

Changes
-------
* graphql.py: register CreateNoteTweet queryId and add the four
  feature flags required by the long-form endpoint
  (longform_notetweets_creation_enabled,
   longform_notetweets_richtext_consumption_enabled,
   responsive_web_twitter_article_data_v2_enabled,
   articles_preview_enabled,
   tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled)
* client.py: add static helper _weighted_length() that mirrors X's
  weighted-character rule (ASCII = 1, others = 2), and route
  create_tweet() to CreateNoteTweet when weighted > 280.

Notes on the silent-failure bug
-------------------------------
CreateNoteTweet requires variables.disallowed_reply_options to be
present (null is the canonical value). Without this field X responds
HTTP 200 with an empty tweet_results object, which makes the failure
look like a successful call. Setting disallowed_reply_options=None
fixes the silent failure.

Tested on an X Premium account with Japanese long-form posts (~400
weighted characters): tweets are created and returned with a valid
rest_id. Standard <=280 posts continue to use the existing CreateTweet
path with no behavior change.
Copilot AI review requested due to automatic review settings May 25, 2026 14:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for X long-form “Note Tweets” by introducing the CreateNoteTweet GraphQL operation and routing oversized tweets to that endpoint.

Changes:

  • Added CreateNoteTweet GraphQL operation id and enabled additional long-form/article feature flags.
  • Implemented weighted-length calculation and auto-routing in create_tweet to use CreateNoteTweet when over the limit.
  • Updated result parsing and error message to account for multiple mutations.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
twitter_cli/graphql.py Registers CreateNoteTweet operation and enables feature flags needed for long-form/article-related GraphQL flows.
twitter_cli/client.py Adds weighted-length logic and routes tweet creation to either CreateTweet or CreateNoteTweet; adjusts response parsing and errors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread twitter_cli/client.py
Comment on lines +563 to +575
def _weighted_length(text):
# type: (str) -> int
"""Return X's weighted character count for a tweet.

X counts basic ASCII characters as 1 and most other characters (CJK,
emoji, etc.) as 2. The standard tweet limit is 280 weighted units;
anything above must go through the CreateNoteTweet endpoint (X
Premium long-form post).
"""
n = 0
for ch in text:
n += 1 if ord(ch) < 0x80 else 2
return n
Comment thread twitter_cli/client.py
Comment on lines +604 to +616
# Route to long-form endpoint if the tweet exceeds the standard limit.
weighted = self._weighted_length(text)
if weighted > 280:
op_name = "CreateNoteTweet"
# CreateNoteTweet requires this field; without it X returns
# HTTP 200 with an empty tweet_results object (silent failure).
variables["disallowed_reply_options"] = None
logger.info(
"Tweet weighted=%d > 280, using CreateNoteTweet (long-form)",
weighted,
)
else:
op_name = "CreateTweet"
Comment thread twitter_cli/client.py
Comment on lines +581 to +582
Automatically routes to the CreateNoteTweet GraphQL endpoint when the
weighted character count exceeds 280 (X Premium long-form post).
Comment thread twitter_cli/client.py
Comment on lines +604 to +607
# Route to long-form endpoint if the tweet exceeds the standard limit.
weighted = self._weighted_length(text)
if weighted > 280:
op_name = "CreateNoteTweet"
Comment thread twitter_cli/client.py
Comment on lines +620 to +625
if op_name == "CreateNoteTweet":
result = _deep_get(
data, "data", "notetweet_create", "tweet_results", "result"
)
else:
result = _deep_get(data, "data", "create_tweet", "tweet_results", "result")
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.

support long text which is more than 300 characters

2 participants