;; -*- lexical-binding: t; -*- ;; Copyright (C) 2024 Free Software Foundation, Inc. ;; Author: Yuchen Pei ;; 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 . (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) (defvar exitter-oauth-token nil) (defvar exitter-oauth-token-secret nil) (defvar exitter-oauth-token-ctime nil) (defvar exitter-debug nil) ;;; for debugging (if exitter-debug (setq request-message-level 'blather) (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 () (when exitter-debug (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) (when exitter-debug (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) (when exitter-debug (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) (when exitter-debug (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) (when exitter-debug (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) (cond ((exitter-find-subtask data "LoginSuccessSubtask") (message "LoginSuccessSubtask") (let* ((subtask (print (exitter-find-subtask data "LoginSuccessSubtask"))) (open-account (alist-get 'open_account subtask))) (setq exitter-oauth-token (alist-get 'oauth_token open-account) exitter-oauth-token-secret (alist-get 'oauth_token_secret open-account) exitter-oauth-token-ctime (format-time-string "%Y-%m-%d %a %H:%M:%S" (current-time))))) (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-email (flow-token headers) (when exitter-debug (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) (let ((new-flow-token (alist-get 'flow_token data))) (cond ((exitter-find-subtask data "LoginEnterPassword") (message "LoginEnterPassword") (exitter-enter-password 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))) )) (provide 'exitter)