Forráskód Böngészése

[wip] Initial implementation to go through the flow login process

Yuchen Pei 1 éve
commit
e6e2fa5172
1 módosított fájl, 243 hozzáadás és 0 törlés
  1. 243 0
      exitter.el

+ 243 - 0
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)