summaryrefslogtreecommitdiff
path: root/exitter.el
diff options
context:
space:
mode:
Diffstat (limited to 'exitter.el')
-rw-r--r--exitter.el257
1 files changed, 243 insertions, 14 deletions
diff --git a/exitter.el b/exitter.el
index 8dde97d..2cb74e2 100644
--- a/exitter.el
+++ b/exitter.el
@@ -27,20 +27,66 @@
exitter-url-task
(format "%s/onboarding/task.json" exitter-url-endpoint)
exitter-url-token
- (format "https://api.twitter.com/oauth2/token"))
+ (format "https://api.twitter.com/oauth2/token")
+ exitter-url-tweet-detail
+ "https://api.twitter.com/graphql/3XDB26fBve-MmjHaWTUZxA/TweetDetail")
(setq exitter-tor-param "-x socks5://127.0.0.1:9050/")
-(setq exitter-init-headers
- `(
- ("Content-Type" . "application/json")
- ("User-Agent" . "TwitterAndroid/10.10.0 (29950000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)")
- ;; ("User-Agent" . "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
- ("X-Twitter-API-Version" . "5")
- ("X-Twitter-Client" . "TwitterAndroid")
- ("X-Twitter-Client-Version" . "10.10.0")
- ("OS-Version" . "28")
- ("System-User-Agent" . "Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)")
- ("X-Twitter-Active-User" . "yes")
- ))
+(defvar exitter-init-headers
+ `(
+ ("Content-Type" . "application/json")
+ ("User-Agent" . "TwitterAndroid/10.10.0 (29950000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)")
+ ;; ("User-Agent" . "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
+ ("X-Twitter-API-Version" . "5")
+ ("X-Twitter-Client" . "TwitterAndroid")
+ ("X-Twitter-Client-Version" . "10.10.0")
+ ("OS-Version" . "28")
+ ("System-User-Agent" . "Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)")
+ ("X-Twitter-Active-User" . "yes")
+ ))
+(defvar exitter-default-features
+ (json-encode
+ '(
+ ("android_graphql_skip_api_media_color_palette" . :json-false)
+ ("blue_business_profile_image_shape_enabled" . :json-false)
+ ("creator_subscriptions_subscription_count_enabled" . :json-false)
+ ("creator_subscriptions_tweet_preview_api_enabled" . :json-true)
+ ("freedom_of_speech_not_reach_fetch_enabled" . :json-false)
+ ("graphql_is_translatable_rweb_tweet_is_translatable_enabled" . :json-false)
+ ("hidden_profile_likes_enabled" . :json-false)
+ ("highlights_tweets_tab_ui_enabled" . :json-false)
+ ("interactive_text_enabled" . :json-false)
+ ("longform_notetweets_consumption_enabled" . :json-true)
+ ("longform_notetweets_inline_media_enabled" . :json-false)
+ ("longform_notetweets_richtext_consumption_enabled" . :json-true)
+ ("longform_notetweets_rich_text_read_enabled" . :json-false)
+ ("responsive_web_edit_tweet_api_enabled" . :json-false)
+ ("responsive_web_enhance_cards_enabled" . :json-false)
+ ("responsive_web_graphql_exclude_directive_enabled" . :json-true)
+ ("responsive_web_graphql_skip_user_profile_image_extensions_enabled" . :json-false)
+ ("responsive_web_graphql_timeline_navigation_enabled" . :json-false)
+ ("responsive_web_media_download_video_enabled" . :json-false)
+ ("responsive_web_text_conversations_enabled" . :json-false)
+ ("responsive_web_twitter_article_tweet_consumption_enabled" . :json-false)
+ ("responsive_web_twitter_blue_verified_badge_is_enabled" . :json-true)
+ ("rweb_lists_timeline_redesign_enabled" . :json-true)
+ ("spaces_2022_h2_clipping" . :json-true)
+ ("spaces_2022_h2_spaces_communities" . :json-true)
+ ("standardized_nudges_misinfo" . :json-false)
+ ("subscriptions_verification_info_enabled" . :json-true)
+ ("subscriptions_verification_info_reason_enabled" . :json-true)
+ ("subscriptions_verification_info_verified_since_enabled" . :json-true)
+ ("super_follow_badge_privacy_enabled" . :json-false)
+ ("super_follow_exclusive_tweet_notifications_enabled" . :json-false)
+ ("super_follow_tweet_api_enabled" . :json-false)
+ ("super_follow_user_api_enabled" . :json-false)
+ ("tweet_awards_web_tipping_enabled" . :json-false)
+ ("tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled" . :json-false)
+ ("tweetypie_unmention_optimization_enabled" . :json-false)
+ ("unified_cards_ad_metadata_container_dynamic_card_content_query_enabled" . :json-false)
+ ("verified_phone_label_enabled" . :json-false)
+ ("vibe_api_enabled" . :json-false)
+ ("view_counts_everywhere_api_enabled" . :json-false)
+ )))
(defvar exitter-oauth-consumer-key nil)
(defvar exitter-oauth-consumer-secret nil)
@@ -209,7 +255,7 @@
((exitter-find-subtask data "LoginSuccessSubtask")
(message "LoginSuccessSubtask")
(let* ((subtask
- (print (exitter-find-subtask data "LoginSuccessSubtask")))
+ (exitter-find-subtask data "LoginSuccessSubtask"))
(open-account (alist-get 'open_account subtask)))
(setq exitter-oauth-token
(alist-get 'oauth_token open-account)
@@ -253,4 +299,187 @@
(message "Got error: %S" error-thrown)))
))
+(defun exitter-get-sign-oauth (uri method)
+ (let* ((params `((oauth_consumer_key . ,exitter-oauth-consumer-key)
+ (oauth_nonce . ,(exitter-nonce))
+ (oauth_signature_method . "HMAC-SHA1")
+ (oauth_timestamp . ,(format-time-string "%s" (current-time)))
+ (oauth_token . ,exitter-oauth-token)
+ (oauth_version . "1.0")))
+ (method-up (upcase method))
+ (link (url-hexify-string uri))
+ (param-to-sign-unencoded
+ (mapconcat
+ (lambda (pair)
+ (format "%s=%s" (car pair) (url-hexify-string (cdr pair))))
+ params
+ "&"))
+ (param-to-sign
+ (replace-regexp-in-string
+ "&" "%26"
+ (replace-regexp-in-string
+ "=" "%3d"
+ (replace-regexp-in-string
+ "%" "%25"
+ (replace-regexp-in-string
+ "\\+" "%20"
+ param-to-sign-unencoded)))))
+ (to-sign (format "%s&%s&%s" method-up link param-to-sign))
+ (signature (url-hexify-string
+ (base64-encode-string
+ (hmac-sha1 (encode-coding-string
+ (format "%s&%s"
+ exitter-oauth-consumer-secret
+ exitter-oauth-token-secret)
+ 'utf-8)
+ (encode-coding-string to-sign 'utf-8)))))
+ )
+ (message "PARAM-TO-SIGN-UNENC: %s" (prin1-to-string param-to-sign-unencoded))
+ (message "PARAM-TO-SIGN: %s" (prin1-to-string param-to-sign))
+ (message "TO-SIGN: %s" (prin1-to-string to-sign))
+ (format "OAuth realm=\"http://api.twitter.com/\", oauth_signature=\"%s\", %s"
+ signature
+ (mapconcat
+ (lambda (pair)
+ (format "%s=\"%s\"" (car pair) (cdr pair)))
+ params
+ ", "))))
+
+(defun exitter-do-fetch (uri headers)
+ (when exitter-debug (message "entering exitter-do-fetch"))
+ (let ((authorization (exitter-get-sign-oauth uri "GET")))
+ (request uri
+ :headers `(,@headers
+ ("Connection" . "Keep-Alive")
+ ("Authorization" . ,authorization)
+ ("Content-Type" . "application/json")
+ ("X-Twitter-Active-User" . "yes")
+ ("Authority" . "api.twitter.com")
+ ("Accept-Encoding" . "gzip")
+ ("Accept-Language" . "en-US,en;q=0.9")
+ ("Accept" . "*/*")
+ ("DNT" . "1")
+ ("User-Agent" .
+ "TwitterAndroid/10.10.0 (29950000-r-0) ONEPLUS+A3010/9 \
+(OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)")
+ ("X-Twitter-API-Version" . "5")
+ ("X-Twitter-Client" . "TwitterAndroid")
+ ("X-Twitter-Client-Version" . "10.10.0")
+ ("OS-Version" . "28")
+ ("System-User-Agent" .
+ "Dalvik/2.1.0 (Linux; U; Android 9; \
+ONEPLUS A3010 Build/PKQ1.181203.001)")
+ )
+ :type "GET"
+ :parser 'json-read
+ :success (cl-function
+ (lambda (&key data &allow-other-keys)
+ (pp data)))
+ :error
+ (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
+ (message "Got error: %S" error-thrown)))
+ )))
+
+(defun exitter-get-tweet (id)
+ (let ((variables (json-encode
+ `(
+ ("focalTweetId" . ,id)
+ ;; ("referrer" . "tweet")
+ ;; ("with_rux_injections" . :json-false)
+ ("includePromotedContent" . :json-false)
+ ;; ("withCommunity" . :json-true)
+ ("withQuickPromoteEligibilityTweetFields" . :json-false)
+ ("includeHasBirdwatchNotes" . :json-false)
+ ("withBirdwatchNotes" . :json-false)
+ ("withVoice" . :json-false)
+ ("withV2Timeline" . :json-true)
+ ))))
+ (exitter-do-fetch
+ exitter-url-tweet-detail
+ `(("variables" . ,variables)
+ ("features" . ,exitter-default-features)))))
+
+(require 'bindat)
+
+(defun exitter-nonce ()
+ (let ((xs))
+ (dotimes (_ 32 xs)
+ (setq xs (cons (random 256) xs)))
+ (replace-regexp-in-string
+ "[=/+]" ""
+ (base64-encode-string
+ (alist-get 'bs (bindat-unpack '((bs str 32)) (vconcat xs)))))))
+
+
+(require 'sha1)
+
+(defun hmac-sha1 (key message)
+ "Return an HMAC-SHA1 authentication code for KEY and MESSAGE.
+
+KEY and MESSAGE must be unibyte strings. The result is a unibyte
+string. Use the function `encode-hex-string' or the function
+`base64-encode-string' to produce human-readable output.
+
+See URL:<http://en.wikipedia.org/wiki/HMAC> for more information
+on the HMAC-SHA1 algorithm.
+
+The Emacs multibyte representation actually uses a series of
+8-bit values under the hood, so we could have allowed multibyte
+strings as arguments. However, internal 8-bit values don't
+correspond to any external representation \(at least for major
+version 22). This makes multibyte strings useless for generating
+hashes.
+
+Instead, callers must explicitly pick and use an encoding for
+their multibyte data. Most callers will want to use UTF-8
+encoding, which we can generate as follows:
+
+ (let ((unibyte-key (encode-coding-string key 'utf-8 t))
+ (unibyte-value (encode-coding-string value 'utf-8 t)))
+ (hmac-sha1 unibyte-key unibyte-value))
+
+For keys and values that are already unibyte, the
+`encode-coding-string' calls just return the same string."
+ (when (multibyte-string-p key)
+ (error "key %s must be unibyte" key))
+ (when (multibyte-string-p message)
+ (error "message %s must be unibyte" message))
+
+ ;; The key block is always exactly the block size of the hash
+ ;; algorithm. If the key is too small, we pad it with zeroes (or
+ ;; instead, we initialize the key block with zeroes and copy the
+ ;; key onto the nulls). If the key is too large, we run it
+ ;; through the hash algorithm and use the hashed value (strange
+ ;; but true).
+
+ (let ((+hmac-sha1-block-size-bytes+ 64)) ; SHA-1 uses 512-bit blocks
+ (when (< +hmac-sha1-block-size-bytes+ (length key))
+ (setq key (sha1 key nil nil t)))
+
+ (let ((key-block (make-vector +hmac-sha1-block-size-bytes+ 0)))
+ (dotimes (i (length key))
+ (aset key-block i (aref key i)))
+
+ (let ((opad (make-vector +hmac-sha1-block-size-bytes+ #x5c))
+ (ipad (make-vector +hmac-sha1-block-size-bytes+ #x36)))
+
+ (dotimes (i +hmac-sha1-block-size-bytes+)
+ (aset ipad i (logxor (aref ipad i) (aref key-block i)))
+ (aset opad i (logxor (aref opad i) (aref key-block i))))
+
+ (when (fboundp 'unibyte-string)
+ ;; `concat' of Emacs23 (and later?) generates a multi-byte
+ ;; string from a vector of characters with eight bit.
+ ;; Since `opad' and `ipad' must be unibyte, we have to
+ ;; convert them by using `unibyte-string'.
+ ;; We cannot use `string-as-unibyte' here because it encodes
+ ;; bytes with the manner of UTF-8.
+ (setq opad (apply 'unibyte-string (mapcar 'identity opad)))
+ (setq ipad (apply 'unibyte-string (mapcar 'identity ipad))))
+
+ (sha1 (concat opad
+ (sha1 (concat ipad message)
+ nil nil t))
+ nil nil t)))))
+
(provide 'exitter)