fix: 431 Request Too Large — reduce FEATURES to 15 essential keys, dynamic update only updates existing keys
This commit is contained in:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "twitter-cli"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
description = "A CLI for Twitter/X — feed, bookmarks, and user timeline in terminal"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -213,30 +213,28 @@ class TestBestChromeTarget:
|
||||
# ── _update_features_from_html ───────────────────────────────────────────
|
||||
|
||||
class TestUpdateFeaturesFromHtml:
|
||||
def test_extracts_feature_flags(self):
|
||||
# Save original state
|
||||
def test_updates_existing_feature_flags(self):
|
||||
"""Should update existing FEATURES keys, not add new ones."""
|
||||
original = dict(FEATURES)
|
||||
try:
|
||||
html = '''
|
||||
"responsive_web_test_feature":{"value":true},
|
||||
"responsive_web_another_feature":{"value":false},
|
||||
"rweb_some_flag":{"value":true}
|
||||
'''
|
||||
# Use a key that exists in FEATURES
|
||||
existing_key = list(FEATURES.keys())[0]
|
||||
original_value = FEATURES[existing_key]
|
||||
opposite = "false" if original_value else "true"
|
||||
html = '"%s":{"value":%s}' % (existing_key, opposite)
|
||||
_update_features_from_html(html)
|
||||
assert FEATURES["responsive_web_test_feature"] is True
|
||||
assert FEATURES["responsive_web_another_feature"] is False
|
||||
assert FEATURES["rweb_some_flag"] is True
|
||||
assert FEATURES[existing_key] != original_value
|
||||
finally:
|
||||
# Restore original state
|
||||
FEATURES.clear()
|
||||
FEATURES.update(original)
|
||||
|
||||
def test_ignores_non_feature_keys(self):
|
||||
def test_does_not_add_new_keys(self):
|
||||
"""Should never add keys not already in FEATURES (prevents URL bloat)."""
|
||||
original = dict(FEATURES)
|
||||
try:
|
||||
html = '"some_random_key":{"value":true}'
|
||||
html = '"responsive_web_brand_new_feature":{"value":true}'
|
||||
_update_features_from_html(html)
|
||||
assert "some_random_key" not in FEATURES
|
||||
assert "responsive_web_brand_new_feature" not in FEATURES
|
||||
finally:
|
||||
FEATURES.clear()
|
||||
FEATURES.update(original)
|
||||
|
||||
@@ -53,43 +53,24 @@ TWITTER_OPENAPI_URL = (
|
||||
"main/src/config/placeholder.json"
|
||||
)
|
||||
|
||||
# Essential features only — keep this list SMALL to avoid 414/431 URI Too Long.
|
||||
# Twitter's API defaults missing features to False, so we only need True-valued ones
|
||||
# that affect tweet data we actually consume. Each additional key adds ~60 chars to URL.
|
||||
_DEFAULT_FEATURES = {
|
||||
"rweb_video_screen_enabled": False,
|
||||
"profile_label_improvements_pcf_label_in_post_enabled": True,
|
||||
"responsive_web_profile_redirect_enabled": False,
|
||||
"rweb_tipjar_consumption_enabled": False,
|
||||
"verified_phone_label_enabled": False,
|
||||
"creator_subscriptions_tweet_preview_api_enabled": True,
|
||||
"responsive_web_graphql_timeline_navigation_enabled": True,
|
||||
"responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
|
||||
"premium_content_api_read_enabled": False,
|
||||
"communities_web_enable_tweet_community_results_fetch": True,
|
||||
"c9s_tweet_anatomy_moderator_badge_enabled": True,
|
||||
"responsive_web_grok_analyze_button_fetch_trends_enabled": False,
|
||||
"responsive_web_grok_analyze_post_followups_enabled": True,
|
||||
"responsive_web_jetfuel_frame": True,
|
||||
"responsive_web_grok_share_attachment_enabled": True,
|
||||
"responsive_web_grok_annotations_enabled": True,
|
||||
"articles_preview_enabled": True,
|
||||
"responsive_web_edit_tweet_api_enabled": True,
|
||||
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
|
||||
"view_counts_everywhere_api_enabled": True,
|
||||
"longform_notetweets_consumption_enabled": True,
|
||||
"responsive_web_twitter_article_tweet_consumption_enabled": True,
|
||||
"tweet_awards_web_tipping_enabled": False,
|
||||
"content_disclosure_indicator_enabled": True,
|
||||
"content_disclosure_ai_generated_indicator_enabled": True,
|
||||
"responsive_web_grok_show_grok_translated_post": True,
|
||||
"responsive_web_grok_analysis_button_from_backend": True,
|
||||
"post_ctas_fetch_enabled": True,
|
||||
"freedom_of_speech_not_reach_fetch_enabled": True,
|
||||
"standardized_nudges_misinfo": True,
|
||||
"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": True,
|
||||
"longform_notetweets_rich_text_read_enabled": True,
|
||||
"longform_notetweets_inline_media_enabled": False,
|
||||
"responsive_web_grok_image_annotation_enabled": True,
|
||||
"responsive_web_grok_imagine_annotation_enabled": True,
|
||||
"responsive_web_grok_community_note_auto_translation_is_enabled": False,
|
||||
"freedom_of_speech_not_reach_fetch_enabled": True,
|
||||
"standardized_nudges_misinfo": True,
|
||||
"responsive_web_graphql_timeline_navigation_enabled": True,
|
||||
"responsive_web_enhance_cards_enabled": False,
|
||||
}
|
||||
|
||||
@@ -226,11 +207,9 @@ def _update_features_from_html(html):
|
||||
|
||||
Twitter embeds feature switch config in inline scripts on the homepage.
|
||||
We parse these to keep FEATURES in sync with the current frontend.
|
||||
Only UPDATES existing keys — never adds new ones to avoid URL bloat.
|
||||
"""
|
||||
try:
|
||||
# Look for feature flags in inline script content
|
||||
# Pattern: "featureSwitch":{"...":{"value":true/false},...}
|
||||
# Also try: features:{key:!0, key2:!1, ...} in JS bundles
|
||||
feature_pattern = re.compile(
|
||||
r'"([a-z][a-z0-9_]+)":\s*\{\s*"value"\s*:\s*(true|false)',
|
||||
re.IGNORECASE,
|
||||
@@ -239,8 +218,10 @@ def _update_features_from_html(html):
|
||||
for match in feature_pattern.finditer(html):
|
||||
key = match.group(1)
|
||||
value = match.group(2).lower() == "true"
|
||||
# Only update keys that look like feature flags
|
||||
if any(prefix in key for prefix in ("responsive_web_", "rweb_", "longform_", "creator_", "communities_", "c9s_")):
|
||||
# Only update keys already in FEATURES — never add new ones
|
||||
# Adding new keys inflates URL length, causing 414/431 errors
|
||||
if key in FEATURES and FEATURES[key] != value:
|
||||
logger.debug("Feature flag updated: %s = %s -> %s", key, FEATURES[key], value)
|
||||
FEATURES[key] = value
|
||||
found += 1
|
||||
if found:
|
||||
|
||||
Reference in New Issue
Block a user