exitter.el 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. ;;; for debugging
  44. ;; (setq request-message-level 'blather)
  45. ;;; disable
  46. ;; (setq request-message-level -1)
  47. (defun exitter-get-access-token ()
  48. (let ((oauth-consumer-key-secret
  49. (base64-encode-string
  50. (format "%s:%s" exitter-oauth-consumer-key
  51. exitter-oauth-consumer-secret)
  52. t)))
  53. (request exitter-url-token
  54. :headers `(("Authorization" .
  55. ,(format "Basic %s" oauth-consumer-key-secret))
  56. ("Content-Type" . "application/x-www-form-urlencoded"))
  57. :data "grant_type=client_credentials"
  58. :parser 'json-read
  59. :type "POST"
  60. :success (cl-function
  61. (lambda (&key data &allow-other-keys)
  62. ;; will generate exactly the same token as
  63. ;; `exitter-access-token'
  64. (print (alist-get 'access_token data))))
  65. :error
  66. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  67. (message "Got error: %S" error-thrown)))
  68. )))
  69. (defun exitter-get-guest-token ()
  70. (message "entering exitter-get-guest-token")
  71. (request exitter-url-activate
  72. :headers `(("Authorization" . ,(format "Bearer %s" exitter-access-token)))
  73. :parser 'json-read
  74. :type "POST"
  75. :success (cl-function
  76. (lambda (&key data &allow-other-keys)
  77. (exitter-login-flow-token (alist-get 'guest_token data))))
  78. :error
  79. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  80. (message "Got error: %S" error-thrown)))
  81. ))
  82. (defun exitter-login-flow-token (guest-token)
  83. (message "entering exitter-login-flow-token")
  84. (let ((headers `(,@exitter-init-headers
  85. ("Authorization" . ,(format "Bearer %s" exitter-access-token))
  86. ("X-Guest-Token" . ,guest-token)
  87. )))
  88. (request exitter-url-task
  89. :params '(("flow_name" . "login")
  90. ("lang" . "en"))
  91. :headers headers
  92. :data (json-encode
  93. '(("flow_token" . nil)
  94. ("input_flow_data" .
  95. (("country_code" . nil)
  96. ("flow_context" .
  97. (("referral_context" .
  98. (("referral_details" .
  99. "utm_source=google-play&utm_medium=organic")
  100. ("referrer_url" . "")))
  101. ("start_location" . (("location" . "deeplink")))
  102. ("request_variant" . nil)
  103. ("target_user_id" . 0)))))))
  104. :parser 'json-read
  105. :type "POST"
  106. :complete (cl-function
  107. (lambda (&key response &allow-other-keys)
  108. (let* ((att
  109. (request-response-header response "att"))
  110. (data
  111. (request-response-data response))
  112. (flow-token (alist-get 'flow_token data)))
  113. (unless (exitter-find-subtask data "LoginEnterUserIdentifier")
  114. (error "Subtask LoginEnterUserIdentifier not found"))
  115. ;; (pp data)
  116. ;; (message "flow-token: %s\natt: %s"
  117. ;; (alist-get 'flow_token data)
  118. ;; att)
  119. (when att
  120. (setq headers
  121. `(,@headers
  122. ("att" . ,att)
  123. ("cookie" . ,(format "att=%s" att)))))
  124. (exitter-enter-username flow-token headers))))
  125. :error
  126. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  127. (message "Got error: %S" error-thrown)))
  128. )))
  129. (defun exitter-find-subtask (data subtask-id)
  130. (message "entering exitter-find-subtask")
  131. (message "subtask-id: %s" subtask-id)
  132. (seq-find
  133. (lambda (subtask)
  134. (equal (alist-get 'subtask_id subtask) subtask-id))
  135. (alist-get 'subtasks data)))
  136. (defun exitter-report-error (&rest args &key error-thrown &allow-other-keys)
  137. (message "Got error: %S" error-thrown))
  138. (defun exitter-enter-username (flow-token headers)
  139. (message "entering exitter-enter-username")
  140. (request exitter-url-task
  141. :params '(("lang" . "en"))
  142. :headers headers
  143. :data (json-encode
  144. `(("flow_token" . ,flow-token)
  145. ("subtask_inputs" .
  146. [(("enter_text" .
  147. (("suggestion_id" . nil)
  148. ("text" . ,exitter-username)
  149. ("link" . "next_link")))
  150. ("subtask_id" . "LoginEnterUserIdentifier"))])))
  151. :type "POST"
  152. :parser 'json-read
  153. :success (cl-function
  154. (lambda (&key data &allow-other-keys)
  155. (let ((new-flow-token
  156. (alist-get 'flow_token data)))
  157. (cond
  158. ((exitter-find-subtask data "LoginEnterPassword")
  159. (message "LoginEnterPassword")
  160. (exitter-enter-password new-flow-token headers))
  161. ((exitter-find-subtask data "LoginEnterAlternateIdentifierSubtask")
  162. (message "LoginEnterAlternateIdentifierSubtask")
  163. (exitter-enter-email new-flow-token headers))
  164. (t (message "Cannot find any matching subtasks"))))
  165. ))
  166. :error
  167. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  168. (message "Got error: %S" error-thrown)))))
  169. (defun exitter-enter-password (flow-token headers)
  170. (message "entering exitter-enter-password")
  171. (request exitter-url-task
  172. :params '(("lang" . "en"))
  173. :headers headers
  174. :data (json-encode
  175. `(("flow_token" . ,flow-token)
  176. ("subtask_inputs" .
  177. [(("enter_password" .
  178. (("password" . ,exitter-password)
  179. ("link" . "next_link")))
  180. ("subtask_id" . "LoginEnterPassword"))])))
  181. :type "POST"
  182. :parser 'json-read
  183. :success (cl-function
  184. (lambda (&key data &allow-other-keys)
  185. (print data)
  186. ))
  187. :error
  188. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  189. (message "Got error: %S" error-thrown)))
  190. ))
  191. (defun exitter-enter-email (flow-token headers)
  192. (message "entering exitter-enter-email")
  193. (request exitter-url-task
  194. :params '(("lang" . "en"))
  195. :headers headers
  196. :data (json-encode
  197. `(("flow_token" . ,flow-token)
  198. ("subtask_inputs" .
  199. [(("enter_text" .
  200. (("text" . ,exitter-email)
  201. ("link" . "next_link")))
  202. ("subtask_id" . "LoginEnterAlternateIdentifierSubtask"))])))
  203. :type "POST"
  204. :parser 'json-read
  205. :success (cl-function
  206. (lambda (&key data &allow-other-keys)
  207. (print data)
  208. ))
  209. :error
  210. (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
  211. (message "Got error: %S" error-thrown)))
  212. ))
  213. (provide 'exitter)