diff options
Diffstat (limited to 'exitter.el')
-rw-r--r-- | exitter.el | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/exitter.el b/exitter.el new file mode 100644 index 0000000..4e3f293 --- /dev/null +++ b/exitter.el @@ -0,0 +1,243 @@ +;; -*- 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-agent-param "-A \"TwitterAndroid/10.10.0\"") +(setq exitter-tor-param "-x socks5://127.0.0.1:9150/") +(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)") + ("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 ((request-curl-options `(,exitter-agent-param)) + (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") + (let ((request-curl-options `(,exitter-agent-param))) + (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-get-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-get-flow-token (guest-token) + (message "entering exitter-get-flow-token") + (let ((request-curl-options `(,exitter-agent-param))) + (request exitter-url-task + :param '(("flow_name" . "login")) + :headers `(("Authorization" . ,(format "Bearer %s" exitter-access-token)) + ("Content-type" . "application/json") + ("X-Guest-Token" . ,guest-token)) + :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))) + (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) + (exitter-enter-username guest-token + (alist-get 'flow_token data) + att) + ))) + :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 (guest-token flow-token att) + (message "entering exitter-enter-username") + (let ((request-curl-options `(,exitter-agent-param))) + (request exitter-url-task + :params '(("lang" . "en")) + :headers `(("Authorization" . ,(format "Bearer %s" exitter-access-token)) + ("Content-type" . "application/json") + ("X-Guest-Token" . ,guest-token) + ("att" . ,att) + ("cookie" . ,(format "att=%s" att))) + :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) + (cond + ((exitter-find-subtask data "LoginEnterPassword") + (message "LoginEnterPassword") + (exitter-enter-password guest-token flow-token att)) + ((exitter-find-subtask data "LoginEnterAlternateIdentifierSubtask") + (message "LoginEnterAlternateIdentifierSubtask") + (exitter-enter-email guest-token flow-token att)) + (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 (guest-token flow-token att) + (message "entering exitter-enter-password") + (let ((request-curl-options `(,exitter-agent-param))) + (request exitter-url-task + :params '(("lang" . "en")) + :headers `(("Authorization" . ,(format "Bearer %s" exitter-access-token)) + ("Content-type" . "application/json") + ("X-Guest-Token" . ,guest-token) + ("att" . ,att) + ("cookie" . ,(format "att=%s" att))) + :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 (guest-token flow-token att) + (message "entering exitter-enter-email") + (let ((request-curl-options `(,exitter-agent-param))) + (request exitter-url-task + :params '(("lang" . "en")) + :headers `(("Authorization" . ,(format "Bearer %s" exitter-access-token)) + ("Content-type" . "application/json") + ("X-Guest-Token" . ,guest-token) + ("att" . ,att) + ("cookie" . ,(format "att=%s" att))) + :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) |