exitter.el 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. ;; -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2024 Free Software Foundation, Inc.
  3. ;; Author: Yuchen Pei <id@ypei.org>
  4. ;; Package-Requires: ((emacs "29.4") (request "0.3.3"))
  5. ;; This file is part of exitter.
  6. ;; exitter is free software: you can redistribute it and/or modify it under
  7. ;; the terms of the GNU Affero General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; exitter is distributed in the hope that it will be useful, but WITHOUT
  11. ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  12. ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
  13. ;; Public License for more details.
  14. ;; You should have received a copy of the GNU Affero General Public
  15. ;; License along with exitter. If not, see <https://www.gnu.org/licenses/>.
  16. (require 'request)
  17. (setq exitter-url-endpoint "https://api.twitter.com/1.1"
  18. exitter-url-activate
  19. (format "%s/guest/activate.json" exitter-url-endpoint)
  20. exitter-url-task
  21. (format "%s/onboarding/task.json" exitter-url-endpoint)
  22. exitter-url-token
  23. (format "https://api.twitter.com/oauth2/token"))
  24. (setq exitter-tor-param "-x socks5://127.0.0.1:9050/")
  25. (setq exitter-init-headers
  26. `(
  27. ("Content-Type" . "application/json")
  28. ("User-Agent" . "TwitterAndroid/10.10.0 (29950000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)")
  29. ;; ("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")
  30. ("X-Twitter-API-Version" . "5")
  31. ("X-Twitter-Client" . "TwitterAndroid")
  32. ("X-Twitter-Client-Version" . "10.10.0")
  33. ("OS-Version" . "28")
  34. ("System-User-Agent" . "Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)")
  35. ("X-Twitter-Active-User" . "yes")
  36. ))
  37. (defvar exitter-oauth-consumer-key nil)
  38. (defvar exitter-oauth-consumer-secret nil)
  39. (defvar exitter-access-token nil)
  40. (defvar exitter-username nil)
  41. (defvar exitter-password nil)
  42. (defvar exitter-email nil)
  43. (defvar exitter-oauth-token nil)
  44. (defvar exitter-oauth-token-secret nil)
  45. (defvar exitter-oauth-token-ctime nil)
  46. (defvar exitter-debug nil)
  47. ;;; for debugging
  48. (if exitter-debug
  49. (setq request-message-level 'blather)
  50. (setq request-message-level -1))
  51. (defun exitter-get-access-token ()
  52. (let ((oauth-consumer-key-secret
  53. (base64-encode-string
  54. (format "%s:%s" exitter-oauth-consumer-key
  55. exitter-oauth-consumer-secret)
  56. t)))
  57. (request exitter-url-token
  58. :headers `(("Authorization" .
  59. ,(format "Basic %s" oauth-consumer-key-secret))
  60. ("Content-Type" . "application/x-www-form-urlencoded"))
  61. :data "grant_type=client_credentials"
  62. :parser 'json-read
  63. :type "POST"
  64. :success (cl-function
  65. (lambda (&key data &allow-other-keys)
  66. ;; will generate exactly the same token as
  67. ;; `exitter-access-token'
  68. (print (alist-get 'access_token data))))
  69. :error
  70. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  71. (message "Got error: %S" error-thrown)))
  72. )))
  73. (defun exitter-get-guest-token ()
  74. (when exitter-debug (message "entering exitter-get-guest-token"))
  75. (request exitter-url-activate
  76. :headers `(("Authorization" . ,(format "Bearer %s" exitter-access-token)))
  77. :parser 'json-read
  78. :type "POST"
  79. :success (cl-function
  80. (lambda (&key data &allow-other-keys)
  81. (exitter-login-flow-token (alist-get 'guest_token data))))
  82. :error
  83. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  84. (message "Got error: %S" error-thrown)))
  85. ))
  86. (defun exitter-login-flow-token (guest-token)
  87. (when exitter-debug (message "entering exitter-login-flow-token"))
  88. (let ((headers `(,@exitter-init-headers
  89. ("Authorization" . ,(format "Bearer %s" exitter-access-token))
  90. ("X-Guest-Token" . ,guest-token)
  91. )))
  92. (request exitter-url-task
  93. :params '(("flow_name" . "login")
  94. ("lang" . "en"))
  95. :headers headers
  96. :data (json-encode
  97. '(("flow_token" . nil)
  98. ("input_flow_data" .
  99. (("country_code" . nil)
  100. ("flow_context" .
  101. (("referral_context" .
  102. (("referral_details" .
  103. "utm_source=google-play&utm_medium=organic")
  104. ("referrer_url" . "")))
  105. ("start_location" . (("location" . "deeplink")))
  106. ("request_variant" . nil)
  107. ("target_user_id" . 0)))))))
  108. :parser 'json-read
  109. :type "POST"
  110. :complete (cl-function
  111. (lambda (&key response &allow-other-keys)
  112. (let* ((att
  113. (request-response-header response "att"))
  114. (data
  115. (request-response-data response))
  116. (flow-token (alist-get 'flow_token data)))
  117. (unless (exitter-find-subtask data "LoginEnterUserIdentifier")
  118. (error "Subtask LoginEnterUserIdentifier not found"))
  119. ;; (pp data)
  120. ;; (message "flow-token: %s\natt: %s"
  121. ;; (alist-get 'flow_token data)
  122. ;; att)
  123. (when att
  124. (setq headers
  125. `(,@headers
  126. ("att" . ,att)
  127. ("cookie" . ,(format "att=%s" att)))))
  128. (exitter-enter-username flow-token headers))))
  129. :error
  130. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  131. (message "Got error: %S" error-thrown)))
  132. )))
  133. (defun exitter-find-subtask (data subtask-id)
  134. (when exitter-debug
  135. (message "entering exitter-find-subtask")
  136. (message "subtask-id: %s" subtask-id))
  137. (seq-find
  138. (lambda (subtask)
  139. (equal (alist-get 'subtask_id subtask) subtask-id))
  140. (alist-get 'subtasks data)))
  141. (defun exitter-report-error (&rest args &key error-thrown &allow-other-keys)
  142. (message "Got error: %S" error-thrown))
  143. (defun exitter-enter-username (flow-token headers)
  144. (when exitter-debug (message "entering exitter-enter-username"))
  145. (request exitter-url-task
  146. :params '(("lang" . "en"))
  147. :headers headers
  148. :data (json-encode
  149. `(("flow_token" . ,flow-token)
  150. ("subtask_inputs" .
  151. [(("enter_text" .
  152. (("suggestion_id" . nil)
  153. ("text" . ,exitter-username)
  154. ("link" . "next_link")))
  155. ("subtask_id" . "LoginEnterUserIdentifier"))])))
  156. :type "POST"
  157. :parser 'json-read
  158. :success (cl-function
  159. (lambda (&key data &allow-other-keys)
  160. (let ((new-flow-token
  161. (alist-get 'flow_token data)))
  162. (cond
  163. ((exitter-find-subtask data "LoginEnterPassword")
  164. (message "LoginEnterPassword")
  165. (exitter-enter-password new-flow-token headers))
  166. ((exitter-find-subtask data "LoginEnterAlternateIdentifierSubtask")
  167. (message "LoginEnterAlternateIdentifierSubtask")
  168. (exitter-enter-email new-flow-token headers))
  169. (t (message "Cannot find any matching subtasks"))))
  170. ))
  171. :error
  172. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  173. (message "Got error: %S" error-thrown)))))
  174. (defun exitter-enter-password (flow-token headers)
  175. (when exitter-debug (message "entering exitter-enter-password"))
  176. (request exitter-url-task
  177. :params '(("lang" . "en"))
  178. :headers headers
  179. :data (json-encode
  180. `(("flow_token" . ,flow-token)
  181. ("subtask_inputs" .
  182. [(("enter_password" .
  183. (("password" . ,exitter-password)
  184. ("link" . "next_link")))
  185. ("subtask_id" . "LoginEnterPassword"))])))
  186. :type "POST"
  187. :parser 'json-read
  188. :success (cl-function
  189. (lambda (&key data &allow-other-keys)
  190. (cond
  191. ((exitter-find-subtask data "LoginSuccessSubtask")
  192. (message "LoginSuccessSubtask")
  193. (let* ((subtask
  194. (print (exitter-find-subtask data "LoginSuccessSubtask")))
  195. (open-account (alist-get 'open_account subtask)))
  196. (setq exitter-oauth-token
  197. (alist-get 'oauth_token open-account)
  198. exitter-oauth-token-secret
  199. (alist-get 'oauth_token_secret open-account)
  200. exitter-oauth-token-ctime
  201. (format-time-string "%Y-%m-%d %a %H:%M:%S"
  202. (current-time)))))
  203. (t (message "Cannot find any matching subtasks"))) ))
  204. :error
  205. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  206. (message "Got error: %S" error-thrown)))
  207. ))
  208. (defun exitter-enter-email (flow-token headers)
  209. (when exitter-debug (message "entering exitter-enter-email"))
  210. (request exitter-url-task
  211. :params '(("lang" . "en"))
  212. :headers headers
  213. :data (json-encode
  214. `(("flow_token" . ,flow-token)
  215. ("subtask_inputs" .
  216. [(("enter_text" .
  217. (("text" . ,exitter-email)
  218. ("link" . "next_link")))
  219. ("subtask_id" . "LoginEnterAlternateIdentifierSubtask"))])))
  220. :type "POST"
  221. :parser 'json-read
  222. :success (cl-function
  223. (lambda (&key data &allow-other-keys)
  224. (let ((new-flow-token
  225. (alist-get 'flow_token data)))
  226. (cond
  227. ((exitter-find-subtask data "LoginEnterPassword")
  228. (message "LoginEnterPassword")
  229. (exitter-enter-password new-flow-token headers))
  230. (t (message "Cannot find any matching subtasks"))))
  231. ))
  232. :error
  233. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  234. (message "Got error: %S" error-thrown)))
  235. ))
  236. (provide 'exitter)