summaryrefslogtreecommitdiff
path: root/desk/lib/twatter.hoon
diff options
context:
space:
mode:
Diffstat (limited to 'desk/lib/twatter.hoon')
-rw-r--r--desk/lib/twatter.hoon511
1 files changed, 511 insertions, 0 deletions
diff --git a/desk/lib/twatter.hoon b/desk/lib/twatter.hoon
new file mode 100644
index 0000000..3410c0d
--- /dev/null
+++ b/desk/lib/twatter.hoon
@@ -0,0 +1,511 @@
+/- spider
+/+ strandio,sr=sortug, server
+=, strand=strand:spider
+=, strand-fail=strand-fail:libstrand:spider
+|%
++$ kv [k=@t v=@t]
++$ param [k=@t v=json]
+:: check nitter/src/consts.nim
+++ auth-key :: nitters
+'Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF'
+++ api-key
+'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA'
+++ api-key2
+'Bearer AAAAAAAAAAAAAAAAAAAAAIWCCAAAAAAA2C25AxqI%2BYCS7pdfJKRH8Xh19zA%3D8vpDZzPHaEJhd20MKVWp3UR38YoPpuTX7UD2cVYo3YNikubuxd'
+:: iphone
+++ api-key-3
+'Bearer AAAAAAAAAAAAAAAAAAAAAAj4AQAAAAAAPraK64zCZ9CSzdLesbE7LB%2Bw4uE%3DVJQREvQNCZJNiz3rHO7lOXlkVOQkzzdsgu6wWgcazdMUaGoUGm'
+:: ipad -- TimelineSearch returns data in a different format, making nitter return empty results. on the other hand, it has high rate limits. build separate token pools per endpoint?
+++ api-key-4
+'Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR'
+++ base-url
+"https://api.twitter.com/"
+++ graphql
+(weld base-url "graphql/")
+++ burl
+|= [query-id=tape path=tape] ^- tape
+"{graphql}{query-id}/{path}"
+++ urls
+ |%
+ ++ tweet-lurk
+ [query-id="mbnjGF4gOwo5gyp9pe5s4A" path="TweetResultByRestId"]
+ :: logged gets
+ ++ user-by-id
+ [query-id="oPppcargziU1uDQHAUmH-A" path="UserResultByIdQuery"]
+ ++ user-tweets
+ [query-id="VgitpdpNZ-RUIp5D1Z_D-A" path="UserTweets"]
+ ++ user-by-name
+ [query-id="G3KGOASz96M-Qu0nwmGXNg" path="UserByScreenName"]
+ ++ user-replies
+ [query-id="8IS8MaO-2EN6GZZZb8jF0g" path="UserWithProfileTweetsAndRepliesQueryV2"]
+ ++ user-media
+ [query-id="PDfFf8hGeJvUCiTyWtw4wQ" path="MediaTimelineV2"]
+ ++ conversation
+ [query-id="83h5UyHZ9wEKBVzALX8R_g" path="ConversationTimelineV2"]
+ ++ tweet-result
+ [query-id="sITyJdhRPpvpEjg4waUmTA" path="TweetResultByIdQuery"]
+ ++ list-by-id
+ [query-id="iTpgCtbdxrsJfyx0cFjHqg" path="ListByRestId"]
+ ++ list-by-slug
+ [query-id="-kmqNvm5Y-cVrfvBy6docg" path="ListBySlug"]
+ ++ list-members
+ [query-id="P4NpVZDqUD_7MEM84L-8nw" path="ListMembers"]
+ ++ list-timeline
+ [query-id="BbGLL1ZfMibdFNWlk7a0Pw" path="ListTimeline"]
+ ++ tweet-detail
+ [query-id="BbmLpxKh8rX8LNe2LhVujA" path="TweetDetail"]
+ ++ search-timeline
+ [query-id="lZ0GCEojmtQfiUQa5oJSEw" path="SearchTimeline"]
+ :: POST URLs
+ ++ create-tweet
+ [query-id="5V_dkq1jfalfiFOEZ4g47A" path="CreateTweet"]
+ ++ create-retweet
+ [query-id="ojPdsZsimiJrUGLR1sjUtA" path="CreateRetweet"]
+ ++ favorite-tweet
+ [query-id="lI07N6Otwv1PhnEgXILM7A" path="FavoriteTweet"]
+ ++ delete-tweet
+ [query-id="VaenaVgh5q5ih7kvyVjgtg" path="DeleteTweet"]
+ --
+:: shit this is legacy
+++ notifications-url "https://twitter.com/i/api/2/notifications/all.json"
+++ base-variables
+^- (list param)
+:~
+['includePromotedContent' b+%|]
+['withHighlightedLabel' b+%|]
+['withCommunity' b+%|]
+['with_rux_injections' b+%|]
+['referrer' s+'tweet']
+['withTweetQuoteCount' b+%&]
+['withBirdwatchNotes' b+%|]
+['withBirdwatchPivots' b+%|]
+['withTweetResult' b+%&]
+['withReactions' b+%&]
+['withReactionsMetadata' b+%|]
+['withReactionsPerspective' b+%|]
+['withSuperFollowsTweetFields' b+%|]
+['withSuperFollowsUserFields' b+%|]
+['withUserResults' b+%|]
+['withVoice' b+%&]
+['withV2Timeline' b+%&]
+::
+['withQuickPromoteEligibilityTweetFields' b+%|]
+==
+++ build-variables
+|= l=(list param) ^- tape
+%- uri-encode:sr
+%- trip
+%- en:json:html %- pairs:enjs:format
+(weld base-variables l)
+++ features-2
+^- tape
+%- uri-encode:sr
+%- trip
+%- en:json:html %- pairs:enjs:format
+:~
+['android_graphql_skip_api_media_color_palette' b+.n]
+['blue_business_profile_image_shape_enabled' b+.n]
+['creator_subscriptions_subscription_count_enabled' b+.n]
+['creator_subscriptions_tweet_preview_api_enabled' b+.y]
+['freedom_of_speech_not_reach_fetch_enabled' b+.n]
+['graphql_is_translatable_rweb_tweet_is_translatable_enabled' b+.n]
+['hidden_profile_likes_enabled' b+.n]
+['highlights_tweets_tab_ui_enabled' b+.n]
+['interactive_text_enabled' b+.n]
+['longform_notetweets_consumption_enabled' b+.y]
+['longform_notetweets_inline_media_enabled' b+.n]
+['longform_notetweets_richtext_consumption_enabled' b+.y]
+['longform_notetweets_rich_text_read_enabled' b+.n]
+['responsive_web_edit_tweet_api_enabled' b+.n]
+['responsive_web_home_pinned_timelines_enabled' b+.n]
+['responsive_web_enhance_cards_enabled' b+.n]
+['responsive_web_graphql_exclude_directive_enabled' b+.y]
+['responsive_web_graphql_skip_user_profile_image_extensions_enabled' b+.n]
+['responsive_web_graphql_timeline_navigation_enabled' b+.n]
+['responsive_web_media_download_video_enabled' b+.n]
+['responsive_web_text_conversations_enabled' b+.n]
+['responsive_web_twitter_article_tweet_consumption_enabled' b+.n]
+['responsive_web_twitter_blue_verified_badge_is_enabled' b+.y]
+['rweb_lists_timeline_redesign_enabled' b+.y]
+['spaces_2022_h2_clipping' b+.y]
+['spaces_2022_h2_spaces_communities' b+.y]
+['standardized_nudges_misinfo' b+.n]
+['subscriptions_verification_info_enabled' b+.y]
+['subscriptions_verification_info_reason_enabled' b+.y]
+['subscriptions_verification_info_verified_since_enabled' b+.y]
+['super_follow_badge_privacy_enabled' b+.n]
+['super_follow_exclusive_tweet_notifications_enabled' b+.n]
+['super_follow_tweet_api_enabled' b+.n]
+['super_follow_user_api_enabled' b+.n]
+['tweet_awards_web_tipping_enabled' b+.n]
+['tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled' b+.n]
+['tweetypie_unmention_optimization_enabled' b+.n]
+['unified_cards_ad_metadata_container_dynamic_card_content_query_enabled' b+.n]
+['verified_phone_label_enabled' b+.n]
+['vibe_api_enabled' b+.n]
+['view_counts_everywhere_api_enabled' b+.n]
+==
+++ post-features ^- (list [@t json])
+:~ ['c9s_tweet_anatomy_moderator_badge_enabled' b+.y]
+ ['freedom_of_speech_not_reach_fetch_enabled' b+.y]
+ ['graphql_is_translatable_rweb_tweet_is_translatable_enabled' b+.y]
+ ['longform_notetweets_consumption_enabled' b+.y]
+ ['longform_notetweets_inline_media_enabled' b+.y]
+ ['longform_notetweets_rich_text_read_enabled' b+.y]
+ ['responsive_web_edit_tweet_api_enabled' b+.y]
+ ['responsive_web_enhance_cards_enabled' b+.n]
+ ['responsive_web_graphql_exclude_directive_enabled' b+.y]
+ ['responsive_web_graphql_skip_user_profile_image_extensions_enabled' b+.n]
+ ['responsive_web_graphql_timeline_navigation_enabled' b+.y]
+ ['responsive_web_home_pinned_timelines_enabled' b+.y]
+ ['responsive_web_media_download_video_enabled' b+.n]
+ ['responsive_web_twitter_article_tweet_consumption_enabled' b+.n]
+ ['standardized_nudges_misinfo' b+.y]
+ ['tweet_awards_web_tipping_enabled' b+.n]
+ ['tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled' b+.y]
+ ['tweetypie_unmention_optimization_enabled' b+.y]
+ ['verified_phone_label_enabled' b+.n]
+ ['view_counts_everywhere_api_enabled' b+.y]
+==
+++ features
+^- tape
+%- uri-encode:sr
+%- trip
+%- en:json:html %- pairs:enjs:format
+:~
+['creator_subscriptions_tweet_preview_api_enabled' b+%&]
+['freedom_of_speech_not_reach_fetch_enabled' b+%|]
+['graphql_is_translatable_rweb_tweet_is_translatable_enabled' b+%&]
+['interactive_text_enabled' b+%&]
+['longform_notetweets_consumption_enabled' b+%&]
+['longform_notetweets_inline_media_enabled' b+%&]
+['longform_notetweets_rich_text_read_enabled' b+%|]
+['longform_notetweets_richtext_consumption_enabled' b+%|]
+['responsive_web_edit_tweet_api_enabled' b+%&]
+['responsive_web_enhance_cards_enabled' b+%|]
+['responsive_web_home_pinned_timelines_enabled' b+.n]
+['responsive_web_graphql_exclude_directive_enabled' b+%&]
+['responsive_web_graphql_timeline_navigation_enabled' b+%&]
+['responsive_web_graphql_skip_user_profile_image_extensions_enabled' b+%|]
+['responsive_web_media_download_video_enabled' b+%&]
+['responsive_web_text_conversations_enabled' b+%|]
+['responsive_web_twitter_blue_verified_badge_is_enabled' b+%&]
+['responsive_web_twitter_article_tweet_consumption_enabled' b+%&]
+['rweb_lists_timeline_redesign_enabled' b+%&]
+['standardized_nudges_misinfo' b+%&]
+['tweet_awards_web_tipping_enabled' b+%|]
+['tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled' b+%|]
+['tweetypie_unmention_optimization_enabled' b+%&]
+['verified_phone_label_enabled' b+%|]
+['vibe_api_enabled' b+%&]
+['view_counts_everywhere_api_enabled' b+%&]
+::
+['android_graphql_skip_api_media_color_palette' b+.y]
+['super_follow_badge_privacy_enabled' b+.y]
+['super_follow_tweet_api_enabled' b+.y]
+['subscriptions_verification_info_enabled' b+.y]
+['blue_business_profile_image_shape_enabled' b+.n]
+['super_follow_user_api_enabled' b+.y]
+['creator_subscriptions_subscription_count_enabled' b+.y]
+['super_follow_exclusive_tweet_notifications_enabled' b+.y]
+['unified_cards_ad_metadata_container_dynamic_card_content_query_enabled' b+.y]
+:: logged
+['c9s_tweet_anatomy_moderator_badge_enabled' b+.n]
+:: logged profile
+['hidden_profile_likes_enabled' b+.n]
+['subscriptions_verification_info_verified_since_enabled' b+.n]
+['highlights_tweets_tab_ui_enabled' b+.n]
+['subscriptions_verification_info_is_identity_verified_enabled' b+.n]
+['hidden_profile_subscriptions_enabled' b+.n]
+==
+++ userid-params
+|= id=@t ^- tape
+=/ vrs (build-variables ~[['userId' %s id] ['count' %n '20']])
+"?variables={vrs}&features={features}"
+++ post-params
+|= id=@t ^- tape
+=/ vrs (build-variables ~[['tweetId' %s id]])
+"?variables={vrs}&features={features}"
+++ user-params
+|= id=@t ^- tape
+=/ vrs (build-variables ~[['screen_name' %s id]])
+"?variables={vrs}&features={features-2}"
+++ user-tweets-params
+|= id=@t ^- tape
+=/ vrs (build-variables ~[['rest_id' %s id] ['count' %n '100']])
+"?variables={vrs}&features={features-2}"
+++ legacy-params ^- (list kv)
+:~ ['include_profile_interstitial_type' '1']
+ ['include_blocking' '1']
+ ['include_blocked_by' '1']
+ ['include_followed_by' '1']
+ ['include_want_retweets' '1']
+ ['include_mute_edge' '1']
+ ['include_can_dm' '1']
+ ['include_can_media_tag' '1']
+ ['include_ext_has_nft_avatar' '1']
+ ['include_ext_is_blue_verified' '1']
+ ['include_ext_verified_type' '1']
+ ['skip_status' '1']
+ ['cards_platform' 'Web-12']
+ ['include_cards' '1']
+ ['include_ext_alt_text' 'true']
+ ['include_ext_limited_action_results' 'false']
+ ['include_quote_count' 'true']
+ ['include_reply_count' '1']
+ ['tweet_mode' 'extended']
+ ['include_ext_collab_control' 'true']
+ ['include_ext_views' 'true']
+ ['include_entities' 'true']
+ ['include_ext_media_color' 'true']
+ ['include_ext_media_availability' 'true']
+ ['include_ext_sensitive_media_warning' 'true']
+ ['include_ext_trusted_friends_metadata' 'true']
+ ['send_error_codes' 'true']
+ ['simple_quoted_tweet' 'true']
+ ['include_ext_edit_control' 'true']
+ ['ext' 'mediaStats,highlightedLabel,hasNftAvatar,voiceInfo,enrichments,superFollowMetadata,unmentionInfo,editControl,collab_control,vibe']
+==
+++ params-to-tape
+|= l=(list kv) ^- tape
+%+ roll l
+|= [i=[k=@t v=@t] a=tape]
+=/ key (trip k.i)
+=/ val (trip v.i)
+?~ a "{a}key={val}"
+"{a}&{key}={val}"
+++ notes-params
+%- params-to-tape
+%+ weld legacy-params
+:~ ['count' '20']
+ ['requestContext' 'launch']
+==
+++ search-params
+|= [query=@t cursor=@t count=@ type=?(%user %post)] ^- tape
+=/ base=(list kv) %+ weld legacy-params
+:~ ['q' query]
+ ['count' (crip "{<count>}")]
+ ['query_source' 'typeahead_click'] :: 'typed_query
+ ['cursor' cursor]
+ ['pc' '1']
+ ['spelling_corrections' '1']
+ ['include_ext_edit_control' 'true']
+ ['ext' 'mediaStats,highlightedLabel,hasNftAvatar,voiceInfo,enrichments,superFollowMetadata,unmentionInfo,editControl,collab_control,vibe']
+==
+=/ full=(list [@t @t]) ?- type
+ %user (snoc base ['result_filter' 'user'])
+ %post (snoc base ['tweet_search_mode' 'live'])
+==
+(params-to-tape full)
+:: requests
+:: guest-token
+++ gt-request
+^- request:http
+=/ method %'POST'
+=/ url 'https://api.twitter.com/1.1/guest/activate.json'
+=/ headers
+:~
+['connection' 'keep-alive']
+['authorization' auth-key]
+['content-type' 'application/x-www-form-urlffencoded']
+['x-twitter-active-user' 'yes']
+['x-twitter-client-language' 'en-us']
+['Accept-language' 'en-US,en;q=0.9']
+['Accept' '*/*']
+['origin' 'https://twitter.com']
+['referer' 'https://twitter.com/']
+['DNT' '1']
+['User-agent' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36']
+==
+[method url headers ~]
+++ parse-gt
+|= res=client-response:iris
+=/ m (strand ,cord)
+^- form:m
+?. ?=(%finished -.res) (strand-fail:strand %no-body ~)
+?~ full-file.res (strand-fail:strand %no-body ~)
+=/ body=@t q.data.u.full-file.res
+=/ jon=(unit json) (de:json:html body)
+?~ jon (strand-fail:strand %no-body ~)
+(pure:m (parse-gt-json u.jon))
+++ parse-gt-json
+ |= =json
+ ^- @t
+ =/ mapi ((om so):dejs:format json)
+ (~(got by mapi) 'guest_token')
+::: csrf
+++ csrf-token-request
+^- request:http
+=/ method %'GET'
+=/ url 'https://developer.twitter.com/en'
+=/ headers
+:~
+['connection' 'keep-alive']
+['content-type' 'application/x-www-form-urlffencoded']
+['x-twitter-active-user' 'yes']
+['x-twitter-client-language' 'en-us']
+['Accept-language' 'en-US,en;q=0.9']
+['Accept' '*/*']
+['origin' 'https://twitter.com']
+['referer' 'https://twitter.com/']
+['DNT' '1']
+['User-agent' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36']
+==
+[method url headers ~]
+++ parse-csrf
+|= res=client-response:iris
+=/ m (strand ,cord)
+^- form:m
+?. ?=(%finished -.res) (strand-fail:strand %no-body ~)
+=+ %+ skim headers.response-header.res
+|= pair=[key=@t value=@t] .=(key.pair 'set-cookie')
+=/ cookies=(list @t) %+ turn -
+|= pair=[key=@t value=@t] value.pair :: parse this a bit
+=/ csrf (extract-csrf cookies)
+(pure:m csrf)
+++ extract-csrf
+|= cookies=(list @t) ^- @t
+%+ roll cookies
+|= [item=@t acc=@t]
+?. (contains:string:sr (trip item) "ct0=") acc
+=+ (split:string:sr (trip item) ";")
+=+ (split:string:sr (snag 0 -) "=")
+(crip (snag 1 -))
+:: ++ embed-request
+:: |= id=@t
+:: =/ endpoint %- crip "{embed-url:endpoints}{(trip id)}"
+:: =/ method %'GET'
+:: =/ headers
+:: :~
+:: ['connection' 'keep-alive']
+:: ['Accept-language' 'en-US,en;q=0.9']
+:: ['Accept' '*/*']
+:: ['origin' 'https://platform.twitter.com']
+:: ['referer' 'https://platform.twitter.com/']
+:: ['DNT' '1']
+:: ['User-agent' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36']
+:: ==
+:: [method endpoint headers ~]
+++ twatter-request
+|= [endpoint=@t gt=@t csrf=@t]
+^- request:http
+=/ method %'GET'
+=/ headers
+:~
+ ['connection' 'keep-alive']
+ ['authorization' auth-key]
+ ['content-type' 'application/json']
+ ['x-csrf-token' csrf]
+ ['x-guest-token' gt]
+ ['x-twitter-active-user' 'yes']
+ ['x-twitter-client-language' 'en-us']
+ ['Accept-language' 'en-US,en;q=0.9']
+ ['Accept' '*/*']
+ ['origin' 'https://twitter.com']
+ ['referer' 'https://twitter.com/']
+ ['DNT' '1']
+ ['User-agent' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36']
+ :: ['authority' 'api.twitter.com']
+==
+[method endpoint headers ~]
+:: ++ build-request-url
+:: |= [endpoint=tape l=(list kv)] ^- @t
+:: =/ vars (uri-encode:u (build-variables l))
+:: =+ (weld base-url:endpoints endpoint)
+:: =+ (weld - vars)
+:: (crip -)
+:: ++ build-request-v2-url
+:: |= [key=@t query=@t cursor=@t endpoint=tape] ^- @t
+:: =/ vars (uri-encode:u (build-variables key query cursor))
+:: =/ feat (uri-encode:u features)
+:: =+ (weld base-url:endpoiunts endpoint)
+:: =+ (weld - vars)
+:: =+ (weld - "&features={feat}")
+:: (crip -)
+:: ++ build-search-url
+:: |= [query=@t cursor=@t] :: TODO count and replies
+:: :: " include:nativeretweets", " include:replies"
+:: =/ q "{<query>} include:nativeretweets exclude:replies"
+:: :: =/ q "{<query>} include:nativeretweets include:replies"
+:: =/ qq %- en-urlt:html (en-urlt:html q)
+:: =/ c %- en-urlt:html (en-urlt:html (trip cursor))
+:: :: =/ c (en-urlt:html (trip cursor))
+:: :: =/ c (trip cursor)
+:: =+
+:: "include_profile_interstitial_type=1&cursor={c}&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&include_ext_has_nft_avatar=1&skip_status=1&cards_platform=Web-12&include_cards=1&include_ext_alt_text=true&include_quote_count=true&include_reply_count=1&tweet_mode=extended&include_entities=true&include_user_entities=true&include_ext_media_color=true&include_ext_media_availability=true&include_ext_sensitive_media_warning=true&send_error_codes=true&simple_quoted_tweet=true&tweet_search_mode=live&q={qq}&count=20&query_source=typed_query&pc=1&spelling_corrections=1&ext=mediaStats%2ChighlightedLabel%2ChasNftAvatar%2CvoiceInfo%2CsuperFollowMetadata"
+:: =+ (weld search:endpoints (search-params query cursor 20 %post))
+:: =+ (weld base-url:endpoints -)
+:: (crip -) :: =/ url %- crip (weld tweet-lurk (post-params id.u.command))
+ :: =/ req (twatter-request url gt csrf)
+++ lurk-headers
+:~
+ ['Connection' 'keep-alive']
+ ['Content-type' 'application/json']
+ ['X-twitter-active-user' 'yes']
+ ['X-twitter-client-language' 'en-us']
+ ::['x-guest-token' gt]
+ ['Accept-language' 'en-US,en;q=0.9']
+ ['Accept' '*/*']
+ ['origin' 'https://twitter.com']
+ ['referer' 'https://twitter.com/']
+ ['DNT' '1']
+ ['User-agent' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36']
+==
+++ logged-headers
+|= [cookie-string=@t csrf=@t]
+:: =/ cookie-string
+:: 'guest_id=v1%3A169380915299959910; guest_id_ads=v1%3A169380915299959910; guest_id_marketing=v1%3A169380915299959910; kdt=VZeo5b3hVg9VsnQjJYiUsFLNvQIa1lqnxry4dcXK; auth_token=88ab0ccf40bb6be863354720d504895c7308e647; ct0=9469495056e099284acb28c9889422b02285c3883e8c9e681c287c2b1287e2e4a8d0ed3dff9a1db4ef05152d2d3f9a891031804a5fc80ef0d8b0ab251067558eec751d8241f617517a47f12a5c0b1ba8; twid=u%3D1633158398555353096; lang=en; external_referer=8e8t2xd8A2w%3D|0|F8C7rVpldvGNltGxuH%2ByoRY%2FzjrflHIZH061f%2B5OiIwP17ZTz34ZGg%3D%3D; personalization_id="v1_kxI2MM4RPdOgambYqR1tUA=="'
+:: =/ csrf '9469495056e099284acb28c9889422b02285c3883e8c9e681c287c2b1287e2e4a8d0ed3dff9a1db4ef05152d2d3f9a891031804a5fc80ef0d8b0ab251067558eec751d8241f617517a47f12a5c0b1ba8'
+:~ ['Cookie' cookie-string]
+ ['Connection' 'keep-alive']
+ ['Authorization' api-key]
+ ['Content-type' 'application/json']
+ ['X-csrf-token' csrf]
+ ['X-twitter-active-user' 'yes']
+ ['X-twitter-auth-type' 'OAuth2Session']
+ ['X-twitter-client-language' 'en-us']
+ ['Accept-language' 'en-US,en;q=0.9']
+ ['Accept' '*/*']
+ ['origin' 'https://twitter.com']
+ ['referer' 'https://twitter.com/']
+ ['DNT' '1']
+ ['User-agent' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36']
+ :: ['authority' 'api.twitter.com']
+==
+:: ++ build-post-body
+:: |= p=post-req:tw ^- octs
+:: =, enjs:format
+:: %- json-to-octs:server
+:: %- pairs %- limo
+:: :~ [%features (pairs post-features)]
+:: ['queryId' s+(crip query-id:create-tweet:urls)]
+:: ['variables' (pairs (build-post-vars p))]
+:: ==
+:: ++ build-post-vars
+:: |= p=post-req:tw
+:: =, enjs:format
+:: =/ text=@t ?. mask.p text.p
+:: :: TODO automated masking
+:: 'This tweet is too spicy for Elon Musk\'s friends. To read it click on the link at my bio'
+:: =/ base-vars
+:: :~ ['dark_request' b+.n]
+:: ['semantic_annotation_ids' a+~]
+:: ['tweet_text' s+text]
+:: :- 'media' %- pairs
+:: :~ ['possibly_sensitive' b+.n]
+:: ['media_entities' a+~]
+:: ==
+:: ==
+:: ?^ quoting.p :_ base-vars :- 'attachment_url' s+u.quoting.p
+:: ?^ replying.p :_ base-vars :- 'reply' %- pairs
+:: :~ ['exclude_reply_user_ids' a+~]
+:: ['in_reply_to_twitter_id' s+u.replying.p]
+:: ==
+:: base-vars
+:: ++ logged-req ^- request:http
+:: =/ url %- crip (weld user-tweets (userid-params '1633158398555353096'))
+:: [%'GET' url logged-headers ~]
+:: :: {"userId":"1633158398555353096","count":20,"includePromotedContent":true,"withQuickPromoteEligibilityTweetFields":true,"withVoice":true,"withV2Timeline":true}
+:: {"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"responsive_web_home_pinned_timelines_enabled":true,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"c9s_tweet_anatomy_moderator_badge_enabled":true,"tweetypie_unmention_optimization_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":false,"tweet_awards_web_tipping_enabled":false,"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":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false}
+
+--