Skip to content

feat: support Premium long-form posts#64

Open
hhh2210 wants to merge 2 commits into
public-clis:mainfrom
hhh2210:fix/long-form-note-tweets
Open

feat: support Premium long-form posts#64
hhh2210 wants to merge 2 commits into
public-clis:mainfrom
hhh2210:fix/long-form-note-tweets

Conversation

@hhh2210

@hhh2210 hhh2210 commented Jun 11, 2026

Copy link
Copy Markdown

Summary

Fixes #54.

Adds automatic routing to X's long-form post mutation for Premium accounts when a post exceeds the standard 280 weighted-character limit.

What changed

  • Register CreateNoteTweet in GraphQL fallback query IDs.
  • Add long-form related feature flags used by the write mutation.
  • Route create_tweet() and quote_tweet() to CreateNoteTweet when text is over the standard weighted limit.
  • Include disallowed_reply_options: null for long-form writes; without this field X can return an empty tweet_results object.
  • Keep standard posts at or below the limit on the existing CreateTweet path.
  • Document automatic long-form routing in README.

Why this scope

This intentionally keeps the patch narrower than #62: it does not add video upload or Article publishing. Article publishing appears to be a separate X entity flow (ArticleEntityDraftCreate / update title / update content / publish) and should be handled as a separate feature with its own payload tests.

Compared with #60, this also covers quote tweets and includes local tests for the routing and payload shape.

Validation

  • uv run ruff check .
  • uv run mypy twitter_cli
  • uv run pytest -q
  • Manual local smoke on a Premium account: posted a >280 character long-form post successfully, then deleted the test post successfully.

Copilot AI review requested due to automatic review settings June 11, 2026 15:41
@hhh2210

hhh2210 commented Jun 11, 2026

Copy link
Copy Markdown
Author

Article publishing note: this PR intentionally only fixes long-form posts.

X Articles use a separate entity flow (ArticleEntityDraftCreate, ArticleEntityUpdateTitle, ArticleEntityUpdateContent, ArticleEntityPublish) rather than CreateTweet / CreateNoteTweet. The repo already supports reading/exporting Articles through twitter article; publishing Articles should be a separate feature because it needs a reliable rich content-state payload builder and dedicated tests.

Copilot AI left a comment

Copy link
Copy Markdown

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 long-form (“Note Tweet”) support by routing oversized tweet text to X’s CreateNoteTweet GraphQL mutation, while updating feature flags, tests, and user-facing docs.

Changes:

  • Add CreateNoteTweet operation + long-form related feature flags in GraphQL config.
  • Route create_tweet / quote_tweet to CreateNoteTweet when text exceeds the weighted length threshold; parse results from multiple response shapes.
  • Add unit tests and README documentation for long-form routing behavior.

Reviewed changes

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

File Description
twitter_cli/graphql.py Adds CreateNoteTweet operation id and enables long-form/article-related feature flags.
twitter_cli/client.py Implements weighted-length routing to CreateNoteTweet, shared result extraction, and improved error messages.
tests/test_client.py Adds coverage for routing logic and asserts the chosen mutation name/variables.
README.md Documents automatic routing for Premium long-form posts.

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

Comment thread twitter_cli/client.py
Comment on lines +134 to +145
def _tweet_weighted_length(text):
# type: (str) -> int
"""Return X's approximate weighted character count for composer routing."""
return sum(1 if ord(ch) < 0x80 else 2 for ch in text)


def _tweet_create_operation(text):
# type: (str) -> str
"""Return the GraphQL mutation for a standard or long-form post."""
if _tweet_weighted_length(text) > _STANDARD_TWEET_WEIGHT_LIMIT:
return "CreateNoteTweet"
return "CreateTweet"
Comment thread twitter_cli/graphql.py
Comment on lines 65 to 75
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
"view_counts_everywhere_api_enabled": True,
"longform_notetweets_consumption_enabled": True,
"longform_notetweets_creation_enabled": True,
"longform_notetweets_richtext_consumption_enabled": True,
"responsive_web_twitter_article_tweet_consumption_enabled": True,
"articles_preview_enabled": True,
"tweet_awards_web_tipping_enabled": False,
"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": True,
"longform_notetweets_rich_text_read_enabled": True,
"longform_notetweets_inline_media_enabled": True,
Comment thread twitter_cli/client.py
Comment on lines +615 to +620
operation_name = _tweet_create_operation(text)
if operation_name == "CreateNoteTweet":
# Required by the long-form mutation. Without it X can return HTTP
# 200 with an empty tweet_results object instead of creating a post.
variables["disallowed_reply_options"] = None
data = self._graphql_post(operation_name, variables, FEATURES)
Comment thread twitter_cli/client.py

def _tweet_weighted_length(text):
# type: (str) -> int
"""Return X's approximate weighted character count for composer routing."""
@hhh2210

hhh2210 commented Jun 12, 2026

Copy link
Copy Markdown
Author

#60 by @rakei076 reached the same core conclusion 17 days earlier — including the disallowed_reply_options silent-failure discovery. Credit where due; I should have referenced it when opening this.

What this PR adds on top of #60:

  • quote_tweet routingfeat: support long-form tweets via CreateNoteTweet #60 only routes create_tweet, so long-form quote tweets still go through the standard mutation and fail with error 186
  • regression tests for both paths, including assertions that standard-length posts keep using CreateTweet so future changes can't silently flip the operation
  • response parsing tolerant to all three envelopes seen from this mutation family (create_tweet / notetweet_create / create_note_tweet), in the spirit of the fix: adapt to Twitter API schema changes (April 2026) #51 schema-drift fixes
  • README docs (EN + ZH sections)

Happy to either rebase this as a follow-up on top of #60, or have maintainers pick whichever — the quote path and the tests are the parts worth landing either way.

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