| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- ;; -*- lexical-binding: t; -*-
- ;; Copyright (C) 2024 Free Software Foundation, Inc.
- ;; Author: Yuchen Pei <id@ypei.org>
- ;; Package-Requires: ((emacs "29.4") (request "0.3.3"))
- ;; This file is part of exitter.
- ;; exitter is free software: you can redistribute it and/or modify it under
- ;; the terms of the GNU Affero General Public License as published by
- ;; the Free Software Foundation, either version 3 of the License, or
- ;; (at your option) any later version.
- ;; exitter is distributed in the hope that it will be useful, but WITHOUT
- ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
- ;; Public License for more details.
- ;; You should have received a copy of the GNU Affero General Public
- ;; License along with exitter. If not, see <https://www.gnu.org/licenses/>.
- (require 'request)
- (setq exitter-url-endpoint "https://api.twitter.com/1.1"
- exitter-url-activate
- (format "%s/guest/activate.json" exitter-url-endpoint)
- exitter-url-task
- (format "%s/onboarding/task.json" exitter-url-endpoint)
- exitter-url-token
- (format "https://api.twitter.com/oauth2/token"))
- (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-oauth-consumer-key nil)
- (defvar exitter-oauth-consumer-secret nil)
- (defvar exitter-access-token nil)
- (defvar exitter-username nil)
- (defvar exitter-password nil)
- (defvar exitter-email nil)
- ;;; for debugging
- ;; (setq request-message-level 'blather)
- ;;; disable
- ;; (setq request-message-level -1)
- (defun exitter-get-access-token ()
- (let ((oauth-consumer-key-secret
- (base64-encode-string
- (format "%s:%s" exitter-oauth-consumer-key
- exitter-oauth-consumer-secret)
- t)))
- (request exitter-url-token
- :headers `(("Authorization" .
- ,(format "Basic %s" oauth-consumer-key-secret))
- ("Content-Type" . "application/x-www-form-urlencoded"))
- :data "grant_type=client_credentials"
- :parser 'json-read
- :type "POST"
- :success (cl-function
- (lambda (&key data &allow-other-keys)
- ;; will generate exactly the same token as
- ;; `exitter-access-token'
- (print (alist-get 'access_token data))))
- :error
- (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
- (message "Got error: %S" error-thrown)))
- )))
- (defun exitter-get-guest-token ()
- (message "entering exitter-get-guest-token")
- (request exitter-url-activate
- :headers `(("Authorization" . ,(format "Bearer %s" exitter-access-token)))
- :parser 'json-read
- :type "POST"
- :success (cl-function
- (lambda (&key data &allow-other-keys)
- (exitter-login-flow-token (alist-get 'guest_token data))))
- :error
- (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
- (message "Got error: %S" error-thrown)))
- ))
- (defun exitter-login-flow-token (guest-token)
- (message "entering exitter-login-flow-token")
- (let ((headers `(,@exitter-init-headers
- ("Authorization" . ,(format "Bearer %s" exitter-access-token))
- ("X-Guest-Token" . ,guest-token)
- )))
- (request exitter-url-task
- :params '(("flow_name" . "login")
- ("lang" . "en"))
- :headers headers
- :data (json-encode
- '(("flow_token" . nil)
- ("input_flow_data" .
- (("country_code" . nil)
- ("flow_context" .
- (("referral_context" .
- (("referral_details" .
- "utm_source=google-play&utm_medium=organic")
- ("referrer_url" . "")))
- ("start_location" . (("location" . "deeplink")))
- ("request_variant" . nil)
- ("target_user_id" . 0)))))))
- :parser 'json-read
- :type "POST"
- :complete (cl-function
- (lambda (&key response &allow-other-keys)
- (let* ((att
- (request-response-header response "att"))
- (data
- (request-response-data response))
- (flow-token (alist-get 'flow_token data)))
- (unless (exitter-find-subtask data "LoginEnterUserIdentifier")
- (error "Subtask LoginEnterUserIdentifier not found"))
- ;; (pp data)
- ;; (message "flow-token: %s\natt: %s"
- ;; (alist-get 'flow_token data)
- ;; att)
- (when att
- (setq headers
- `(,@headers
- ("att" . ,att)
- ("cookie" . ,(format "att=%s" att)))))
- (exitter-enter-username flow-token headers))))
- :error
- (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
- (message "Got error: %S" error-thrown)))
- )))
- (defun exitter-find-subtask (data subtask-id)
- (message "entering exitter-find-subtask")
- (message "subtask-id: %s" subtask-id)
- (seq-find
- (lambda (subtask)
- (equal (alist-get 'subtask_id subtask) subtask-id))
- (alist-get 'subtasks data)))
- (defun exitter-report-error (&rest args &key error-thrown &allow-other-keys)
- (message "Got error: %S" error-thrown))
- (defun exitter-enter-username (flow-token headers)
- (message "entering exitter-enter-username")
- (request exitter-url-task
- :params '(("lang" . "en"))
- :headers headers
- :data (json-encode
- `(("flow_token" . ,flow-token)
- ("subtask_inputs" .
- [(("enter_text" .
- (("suggestion_id" . nil)
- ("text" . ,exitter-username)
- ("link" . "next_link")))
- ("subtask_id" . "LoginEnterUserIdentifier"))])))
- :type "POST"
- :parser 'json-read
- :success (cl-function
- (lambda (&key data &allow-other-keys)
- (let ((new-flow-token
- (alist-get 'flow_token data)))
- (cond
- ((exitter-find-subtask data "LoginEnterPassword")
- (message "LoginEnterPassword")
- (exitter-enter-password new-flow-token headers))
- ((exitter-find-subtask data "LoginEnterAlternateIdentifierSubtask")
- (message "LoginEnterAlternateIdentifierSubtask")
- (exitter-enter-email new-flow-token headers))
- (t (message "Cannot find any matching subtasks"))))
- ))
- :error
- (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
- (message "Got error: %S" error-thrown)))))
- (defun exitter-enter-password (flow-token headers)
- (message "entering exitter-enter-password")
- (request exitter-url-task
- :params '(("lang" . "en"))
- :headers headers
- :data (json-encode
- `(("flow_token" . ,flow-token)
- ("subtask_inputs" .
- [(("enter_password" .
- (("password" . ,exitter-password)
- ("link" . "next_link")))
- ("subtask_id" . "LoginEnterPassword"))])))
- :type "POST"
- :parser 'json-read
- :success (cl-function
- (lambda (&key data &allow-other-keys)
- (print data)
- ))
- :error
- (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
- (message "Got error: %S" error-thrown)))
- ))
- (defun exitter-enter-email (flow-token headers)
- (message "entering exitter-enter-email")
- (request exitter-url-task
- :params '(("lang" . "en"))
- :headers headers
- :data (json-encode
- `(("flow_token" . ,flow-token)
- ("subtask_inputs" .
- [(("enter_text" .
- (("text" . ,exitter-email)
- ("link" . "next_link")))
- ("subtask_id" . "LoginEnterAlternateIdentifierSubtask"))])))
- :type "POST"
- :parser 'json-read
- :success (cl-function
- (lambda (&key data &allow-other-keys)
- (print data)
- ))
- :error
- (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
- (message "Got error: %S" error-thrown)))
- ))
- (provide 'exitter)
|