├── .gitignore ├── TwitterFrontendFlow ├── __init__.py └── TwitterFrontendFlow.py ├── setup.py ├── LICENSE ├── sample2.py ├── sample.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /TwitterFrontendFlow/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="TwitterFrontendFlow", 5 | version="2.1", 6 | description="Unofficial Client for Twitter Internal API", 7 | url="https://github.com/fa0311/TwitterFrontendFlow", 8 | packages=find_packages(), 9 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 yuki 4 | 5 | https://yuki0311.com/ 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /sample2.py: -------------------------------------------------------------------------------- 1 | from TwitterFrontendFlow.TwitterFrontendFlow import TwitterFrontendFlow 2 | 3 | flow = TwitterFrontendFlow() 4 | 5 | flow.login_flow() 6 | flow.LoginJsInstrumentationSubtask() 7 | 8 | while "LoginSuccessSubtask" not in flow.get_subtask_ids(): 9 | try: 10 | if "LoginEnterUserIdentifierSSO" in flow.get_subtask_ids(): 11 | print("Telephone number / Email address / User name") 12 | flow.LoginEnterUserIdentifierSSO(input()) 13 | elif "LoginEnterAlternateIdentifierSubtask" in flow.get_subtask_ids(): 14 | print(flow.content["subtasks"][0]["enter_text"]["primary_text"]["text"]) 15 | flow.LoginEnterAlternateIdentifierSubtask(input()) 16 | elif "LoginEnterPassword" in flow.get_subtask_ids(): 17 | print(flow.content["subtasks"][0]["enter_password"]["primary_text"]["text"]) 18 | flow.LoginEnterPassword(input()) 19 | elif "AccountDuplicationCheck" in flow.get_subtask_ids(): 20 | print("AccountDuplicationCheck") 21 | flow.AccountDuplicationCheck() 22 | elif "LoginTwoFactorAuthChallenge" in flow.get_subtask_ids(): 23 | header = flow.content["subtasks"][0]["enter_text"]["header"] 24 | print(header["primary_text"]["text"]) 25 | flow.LoginTwoFactorAuthChallenge(input()) 26 | elif "LoginAcid" in flow.get_subtask_ids(): 27 | header = flow.content["subtasks"][0]["enter_text"]["header"] 28 | print(header["secondary_text"]["text"]) 29 | flow.LoginAcid(input()) 30 | elif "SuccessExit" in flow.get_subtask_ids(): 31 | break 32 | else: 33 | print("Non-supported login methods: " + flow.get_subtask_ids()) 34 | exit(1) 35 | 36 | except: 37 | print("Error") 38 | 39 | print("Success") 40 | flow.SaveCookies("cookie.json") 41 | -------------------------------------------------------------------------------- /sample.py: -------------------------------------------------------------------------------- 1 | from TwitterFrontendFlow.TwitterFrontendFlow import TwitterFrontendFlow 2 | 3 | flow = TwitterFrontendFlow() 4 | 5 | print( 6 | """login: ログイン 7 | password_reset: パスワードリセット 8 | load: cookieのロード""") 9 | 10 | action = input() 11 | 12 | if action == "login": 13 | flow.login_flow() 14 | flow.LoginJsInstrumentationSubtask() 15 | print(flow.get_subtask_ids()) 16 | if "LoginEnterUserIdentifierSSO" in flow.get_subtask_ids(): 17 | print("Telephone number / Email address / User name") 18 | flow.LoginEnterUserIdentifierSSO(input()) 19 | print(flow.get_subtask_ids()) 20 | if "LoginEnterAlternateIdentifierSubtask" in flow.get_subtask_ids(): 21 | print(flow.content["subtasks"][0]["enter_text"]["primary_text"]["text"]) 22 | flow.LoginEnterAlternateIdentifierSubtask(input()) 23 | print(flow.get_subtask_ids()) 24 | if "LoginEnterPassword" in flow.get_subtask_ids(): 25 | print(flow.content["subtasks"][0]["enter_password"]["primary_text"]["text"]) 26 | flow.LoginEnterPassword(input()) 27 | print(flow.get_subtask_ids()) 28 | if "AccountDuplicationCheck" in flow.get_subtask_ids(): 29 | flow.AccountDuplicationCheck() 30 | print(flow.get_subtask_ids()) 31 | if "LoginTwoFactorAuthChallenge" in flow.get_subtask_ids(): 32 | print(flow.content["subtasks"][0]["enter_text"]["header"]["primary_text"]["text"]) 33 | flow.LoginTwoFactorAuthChallenge(input()) 34 | print(flow.get_subtask_ids()) 35 | if "LoginAcid" in flow.get_subtask_ids(): 36 | print(flow.content["subtasks"][0]["enter_text"]["header"]["secondary_text"]["text"]) 37 | flow.LoginAcid(input()) 38 | print(flow.get_subtask_ids()) 39 | if "LoginSuccessSubtask" in flow.get_subtask_ids(): 40 | print("===========Success===========") 41 | print(flow.get_subtask_ids()) 42 | if "SuccessExit" not in flow.get_subtask_ids(): 43 | print("Error") 44 | exit() 45 | 46 | elif action == "password_reset": 47 | flow.password_reset_flow() 48 | flow.PwrJsInstrumentationSubtask() 49 | print(flow.get_subtask_ids()) 50 | if "PasswordResetBegin"in flow.get_subtask_ids(): 51 | print("電話番号/メールアドレス/ユーザー名") 52 | flow.PasswordResetBegin(input()) 53 | print(flow.get_subtask_ids()) 54 | if "PwrKnowledgeChallenge"in flow.get_subtask_ids(): 55 | print(flow.content["subtasks"][0]["enter_text"]["secondary_text"]["text"]) 56 | flow.PwrKnowledgeChallenge(input()) 57 | print(flow.get_subtask_ids()) 58 | if "PwrKnowledgeChallenge"in flow.get_subtask_ids(): 59 | print(flow.content["subtasks"][0]["enter_text"]["secondary_text"]["text"]) 60 | flow.PwrKnowledgeChallenge(input()) 61 | print(flow.get_subtask_ids()) 62 | if "PasswordResetChooseChallenge"in flow.get_subtask_ids(): 63 | print("\n".join([choices["id"] + ": " + choices["text"]["text"] for choices in flow.content["subtasks"][0]["choice_selection"]["choices"]])) 64 | flow.PasswordResetChooseChallenge(input()) 65 | print(flow.get_subtask_ids()) 66 | if "PasswordResetConfirmChallenge"in flow.get_subtask_ids(): 67 | print("コードを入力") 68 | flow.PasswordResetConfirmChallenge(input()) 69 | print(flow.get_subtask_ids()) 70 | if "PasswordResetNewPassword"in flow.get_subtask_ids(): 71 | print("新しいパスワードを入力") 72 | flow.PasswordResetNewPassword(input()) 73 | print(flow.get_subtask_ids()) 74 | if "PasswordResetSurvey"in flow.get_subtask_ids(): 75 | print("パスワードを変更した理由を教えてください") 76 | print("\n".join([choices["id"] + ": " + choices["text"]["text"] for choices in flow.content["subtasks"][0]["choice_selection"]["choices"]])) 77 | flow.PasswordResetSurvey(input()) 78 | print(flow.get_subtask_ids()) 79 | exit() 80 | 81 | elif action == "load": 82 | print("ファイル名") 83 | flow.LoadCookies(input()) 84 | 85 | while True: 86 | print( 87 | """tweet: ツイート 88 | fav: いいね 89 | unfav: いいね取り消し 90 | rt: リツイート 91 | unrt: リツイート取り消し 92 | follow: フォロー 93 | unfollow: フォロー取り消し 94 | save: cookieの出力 95 | end: 終了""") 96 | 97 | action = input() 98 | 99 | if action == "tweet": 100 | print("ツイート内容") 101 | flow.CreateTweet(input()) 102 | elif action == "fav": 103 | print("ツイートid") 104 | flow.FavoriteTweet(input()) 105 | elif action == "unfav": 106 | print("ツイートid") 107 | flow.UnfavoriteTweet(input()) 108 | elif action == "rt": 109 | print("ツイートid") 110 | flow.CreateRetweet(input()) 111 | elif action == "unrt": 112 | print("ツイートid") 113 | flow.DeleteRetweet(input()) 114 | elif action == "follow": 115 | print("ユーザー内部id") 116 | flow.friendships_create(input()) 117 | elif action == "unfollow": 118 | print("ユーザー内部id") 119 | flow.friendships_destroy(input()) 120 | elif action == "save": 121 | print("ファイル名") 122 | flow.SaveCookies(input()) 123 | elif action == "end": 124 | break -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TwitterFrontendFlow 2 | 3 | ---- 4 | ---- 5 | This project is not maintained. Use [twitter_openapi_python](https://github.com/fa0311/twitter_openapi_python) and [tweepy_authlib](https://github.com/tsukumijima/tweepy-authlib) 6 | 7 | ---- 8 | ---- 9 | 10 | Twitter の内部 API を叩く 11 | 12 | ログイン: [TwitterFrontendFlow](https://github.com/fa0311/TwitterFrontendFlow) / 13 | 取得: [TweetURLtoData](https://github.com/fa0311/TweetURLtoData) / 14 | スペース: [TwitterSpacesWiretap](https://github.com/fa0311/TwitterSpacesWiretap) 15 | 16 | ## P.S. restriction bypass (Fixed) 17 | 18 | [hackerone.com](https://hackerone.com/reports/1439026) 19 | 20 | ## proxy 21 | 22 | [requests-docs](https://requests-docs-ja.readthedocs.io/en/latest/user/advanced/#proxies) 23 | 24 | ```python 25 | TwitterFrontendFlow(proxies={ 26 | "http":"", 27 | "https":"" 28 | }) 29 | ``` 30 | 31 | ## login flow 32 | 33 | ### 通常ログイン 34 | 35 | ```python 36 | print(TwitterFrontendFlow() 37 | .login_flow() 38 | .LoginJsInstrumentationSubtask() 39 | .LoginEnterUserIdentifierSSO("電話番号/メールアドレス/ユーザー名") 40 | .LoginEnterPassword("パスワード").content) 41 | ``` 42 | 43 | ### 2 段階認証 44 | 45 | ```python 46 | print(TwitterFrontendFlow() 47 | .login_flow() 48 | .LoginJsInstrumentationSubtask() 49 | .LoginEnterUserIdentifierSSO("電話番号/メールアドレス/ユーザー名") 50 | .LoginEnterPassword("パスワード") 51 | .AccountDuplicationCheck() 52 | .LoginTwoFactorAuthChallenge("2段階認証のコード").content) 53 | ``` 54 | 55 | ### 通常とは異なるログイン操作が行われました 56 | 57 | ```python 58 | print(TwitterFrontendFlow() 59 | .login_flow() 60 | .LoginJsInstrumentationSubtask() 61 | .LoginEnterUserIdentifierSSO("電話番号/メールアドレス/ユーザー名") 62 | .LoginEnterAlternateIdentifierSubtask("電話番号/ユーザー名") 63 | .LoginEnterPassword("パスワード").content) 64 | ``` 65 | 66 | ### アカウントの安全のために 67 | 68 | ```python 69 | print(TwitterFrontendFlow() 70 | .login_flow() 71 | .LoginJsInstrumentationSubtask() 72 | .LoginEnterUserIdentifierSSO("電話番号/メールアドレス/ユーザー名") 73 | .LoginEnterPassword("パスワード") 74 | .AccountDuplicationCheck() 75 | .LoginAcid("メールアドレス / メールアドレスのコード").content) 76 | ``` 77 | 78 | ## password reset flow 79 | 80 | ### 通常リセット 81 | 82 | ```python 83 | print(TwitterFrontendFlow() 84 | .password_reset_flow() 85 | .PwrJsInstrumentationSubtask() 86 | .PasswordResetBegin("電話番号/メールアドレス/ユーザー名") 87 | .PasswordResetChooseChallenge() 88 | .PasswordResetConfirmChallenge("認証コード") 89 | .PasswordResetNewPassword("新しいパスワード") 90 | .PasswordResetSurvey("0").content) 91 | ``` 92 | 93 | ### 個人情報を確認してください 94 | 95 | ```python 96 | print(TwitterFrontendFlow() 97 | .password_reset_flow() 98 | .PwrJsInstrumentationSubtask() 99 | .PasswordResetBegin("ユーザー名") 100 | .PwrKnowledgeChallenge("メールアドレス") 101 | .PwrKnowledgeChallenge("電話番号") 102 | .PasswordResetChooseChallenge() 103 | .PasswordResetConfirmChallenge("認証コード") 104 | .PasswordResetNewPassword("新しいパスワード") 105 | .PasswordResetSurvey("0").content) 106 | ``` 107 | 108 | ## Save / Load 109 | 110 | ```python 111 | (TwitterFrontendFlow() 112 | .login_flow() 113 | .LoginJsInstrumentationSubtask() 114 | .LoginEnterUserIdentifierSSO("電話番号/メールアドレス/ユーザー名") 115 | .LoginEnterPassword("パスワード") 116 | .SaveCookies("user.json")) 117 | ``` 118 | 119 | ```python 120 | (TwitterFrontendFlow() 121 | .LoadCookies("user.json")) 122 | ``` 123 | 124 | ## after login 125 | 126 | おまけ程度 127 | 128 | ### ツイート 129 | 130 | ```python 131 | print(TwitterFrontendFlow() 132 | .LoadCookies("user.json") 133 | .CreateTweet("ツイートしたい文字列").content) 134 | ``` 135 | 136 | ### いいね 137 | 138 | ```python 139 | print(TwitterFrontendFlow() 140 | .LoadCookies("user.json") 141 | .FavoriteTweet("ツイートid").content) 142 | ``` 143 | 144 | ### いいね取り消し 145 | 146 | ```python 147 | print(TwitterFrontendFlow() 148 | .LoadCookies("user.json") 149 | .UnfavoriteTweet("ツイートid").content) 150 | ``` 151 | 152 | ### リツイート 153 | 154 | ```python 155 | print(TwitterFrontendFlow() 156 | .LoadCookies("user.json") 157 | .CreateRetweet("ツイートid").content) 158 | ``` 159 | 160 | ### リツイート取り消し 161 | 162 | ```python 163 | print(TwitterFrontendFlow() 164 | .LoadCookies("user.json") 165 | .DeleteRetweet("ツイートid").content) 166 | ``` 167 | 168 | ### フォロー 169 | 170 | 未だに新 API への移行が終わってないらしく旧 API での実装 171 | 172 | ```python 173 | print(TwitterFrontendFlow() 174 | .LoadCookies("user.json") 175 | .friendships_create("ユーザーの内部id").content) 176 | ``` 177 | 178 | ### フォロー取り消し 179 | 180 | 未だに新 API への移行が終わってないらしく旧 API での実装 181 | 182 | ```python 183 | print(TwitterFrontendFlow() 184 | .LoadCookies("user.json") 185 | .friendships_destroy("ユーザーの内部id").content) 186 | ``` 187 | 188 | ## sample 189 | 190 | 中身見たほうが早いかも 191 | これが動かないアカウントがあったら詳細を詳しく issue に 192 | [sample.py](https://github.com/fa0311/TwitterFrontendFlow/blob/master/sample.py) 193 | 194 | ## help 195 | 196 | ### inappropriate method 197 | 198 | LoginFlow のリクエストを送る順番が不適切と検知した場合に表示されます 199 | あくまで検知なのでこれをバイパスする方法があります 200 | 201 | ```python 202 | flow = TwitterFrontendFlow() 203 | flow.method_check_bypass = True 204 | ``` 205 | -------------------------------------------------------------------------------- /TwitterFrontendFlow/TwitterFrontendFlow.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | 5 | class TwitterFrontendFlow: 6 | def __init__(self, proxies={}, language="en"): 7 | self.USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36" 8 | self.AUTHORIZATION = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA" 9 | self.proxies = proxies 10 | self.session = requests.session() 11 | self.__twitter() 12 | self.x_guest_token = self.__get_guest_token() 13 | self.method_check_bypass = False 14 | self.flow_token = None 15 | self.language = language 16 | 17 | def __twitter(self): 18 | headers = { 19 | "User-Agent": self.USER_AGENT, 20 | } 21 | response = self.session.get( 22 | "https://twitter.com/", headers=headers, proxies=self.proxies 23 | ) 24 | return self 25 | 26 | def __get_guest_token(self): 27 | headers = { 28 | "authorization": self.AUTHORIZATION, 29 | "User-Agent": self.USER_AGENT, 30 | } 31 | response = self.session.post( 32 | "https://api.twitter.com/1.1/guest/activate.json", 33 | headers=headers, 34 | proxies=self.proxies, 35 | ).json() 36 | return response["guest_token"] 37 | 38 | def __get_headers(self): 39 | return { 40 | "authorization": self.AUTHORIZATION, 41 | "User-Agent": self.USER_AGENT, 42 | "Content-type": "application/json", 43 | "x-guest-token": self.x_guest_token, 44 | "x-csrf-token": self.session.cookies.get("ct0"), 45 | "x-twitter-active-user": "yes", 46 | "x-twitter-client-language": self.language, 47 | } 48 | 49 | def __get_headers_legacy(self): 50 | return { 51 | "authorization": self.AUTHORIZATION, 52 | "User-Agent": self.USER_AGENT, 53 | "Content-type": "application/x-www-form-urlencoded", 54 | "x-csrf-token": self.session.cookies.get("ct0"), 55 | "x-twitter-active-user": "yes", 56 | "x-twitter-auth-type": "OAuth2Session", 57 | } 58 | 59 | def get_subtask_ids(self): 60 | return [subtasks["subtask_id"] for subtasks in self.content["subtasks"]] 61 | 62 | def __flow_token_check(self): 63 | if self.flow_token == None: 64 | raise Exception("not found token") 65 | 66 | def __error_check(self, content): 67 | if content.get("errors"): 68 | raise Exception(content["errors"][0]["message"]) 69 | 70 | def __method_check(self, method_name): 71 | if self.method_check_bypass: 72 | return 73 | if method_name not in self.get_subtask_ids(): 74 | raise Exception( 75 | "{0} is inappropriate method. choose from {1}. information: https://github.com/fa0311/TwitterFrontendFlow#inappropriate-method".format( 76 | method_name, ", ".join(self.get_subtask_ids()) 77 | ) 78 | ) 79 | 80 | def LoadCookies(self, file_path): 81 | with open(file_path, "r") as f: 82 | for cookie in json.load(f): 83 | self.session.cookies.set_cookie( 84 | requests.cookies.create_cookie(**cookie) 85 | ) 86 | return self 87 | 88 | def SaveCookies(self, file_path): 89 | cookies = [] 90 | for cookie in self.session.cookies: 91 | cookie_dict = dict( 92 | version=cookie.version, 93 | name=cookie.name, 94 | value=cookie.value, 95 | port=cookie.port, 96 | domain=cookie.domain, 97 | path=cookie.path, 98 | secure=cookie.secure, 99 | expires=cookie.expires, 100 | discard=cookie.discard, 101 | comment=cookie.comment, 102 | comment_url=cookie.comment_url, 103 | rfc2109=cookie.rfc2109, 104 | rest=cookie._rest, 105 | ) 106 | cookies.append(cookie_dict) 107 | 108 | with open(file_path, "w") as f: 109 | json.dump(cookies, f, indent=4) 110 | return self 111 | 112 | # ログイン 113 | 114 | def login_flow(self): 115 | data = { 116 | "input_flow_data": { 117 | "flow_context": { 118 | "debug_overrides": {}, 119 | "start_location": {"location": "splash_screen"}, 120 | } 121 | }, 122 | "subtask_versions": { 123 | "contacts_live_sync_permission_prompt": 0, 124 | "email_verification": 1, 125 | "topics_selector": 1, 126 | "wait_spinner": 1, 127 | "cta": 4, 128 | }, 129 | } 130 | params = {"flow_name": "login"} 131 | response = self.session.post( 132 | "https://twitter.com/i/api/1.1/onboarding/task.json", 133 | headers=self.__get_headers(), 134 | json=data, 135 | params=params, 136 | proxies=self.proxies, 137 | ).json() 138 | self.__error_check(response) 139 | self.flow_token = response.get("flow_token") 140 | self.content = response 141 | return self 142 | 143 | def LoginJsInstrumentationSubtask(self): 144 | self.__flow_token_check() 145 | self.__method_check("LoginJsInstrumentationSubtask") 146 | data = { 147 | "flow_token": self.flow_token, 148 | "subtask_inputs": [ 149 | { 150 | "subtask_id": "LoginJsInstrumentationSubtask", 151 | "js_instrumentation": { 152 | "response": json.dumps( 153 | { 154 | "rf": { 155 | "af07339bbc6d24ced887d705eb0c9fd29b4a7d7ddc21136c9f94d53a4bc774d2": 88, 156 | "a6ce87d6481c6ec4a823548be3343437888441d2a453061c54f8e2eb325856f7": 250, 157 | "a0062ad06384a8afd38a41cd83f31b0dbfdea0eff4b24c69f0dd9095b2fb56d6": 16, 158 | "a929e5913a5715d93491eaffaa139ba4977cbc826a5e2dbcdc81cae0f093db25": 186, 159 | }, 160 | "s": "Q-H-53m1uXImK0F0ogrxRQtCWTH1KIlPbIy0MloowlMa4WNK5ZCcDoXyRs1q_cPbynK73w_wfHG_UVRKKBWRoh6UJtlPS5kMa1p8fEvTYi76hwdzBEzovieR8t86UpeSkSBFYcL8foYKSp6Nop5mQR_QHGyEeleclCPUvzS0HblBJqZZdtUo-6by4BgCyu3eQ4fY5nOF8fXC85mu6k34wo982LMK650NsoPL96DBuloqSZvSHU47wq2uA4xy24UnI2WOc6U9KTvxumtchSYNnXq1HV662B8U2-jWrzvIU4yUHV3JYUO6sbN6j8Ho9JaUNJpJSK7REwqCBQ3yG7iwMAAAAX2Vqcbs", 161 | } 162 | ), 163 | "link": "next_link", 164 | }, 165 | } 166 | ], 167 | } 168 | response = self.session.post( 169 | "https://twitter.com/i/api/1.1/onboarding/task.json", 170 | headers=self.__get_headers(), 171 | json=data, 172 | proxies=self.proxies, 173 | ).json() 174 | self.__error_check(response) 175 | self.flow_token = response.get("flow_token") 176 | self.content = response 177 | return self 178 | 179 | def LoginEnterUserIdentifierSSO(self, user_id): 180 | self.__flow_token_check() 181 | self.__method_check("LoginEnterUserIdentifierSSO") 182 | data = { 183 | "flow_token": self.flow_token, 184 | "subtask_inputs": [ 185 | { 186 | "subtask_id": "LoginEnterUserIdentifierSSO", 187 | "settings_list": { 188 | "setting_responses": [ 189 | { 190 | "key": "user_identifier", 191 | "response_data": {"text_data": {"result": user_id}}, 192 | } 193 | ], 194 | "link": "next_link", 195 | }, 196 | } 197 | ], 198 | } 199 | response = self.session.post( 200 | "https://twitter.com/i/api/1.1/onboarding/task.json", 201 | headers=self.__get_headers(), 202 | json=data, 203 | proxies=self.proxies, 204 | ).json() 205 | self.__error_check(response) 206 | self.flow_token = response.get("flow_token") 207 | self.content = response 208 | return self 209 | 210 | def AccountDuplicationCheck(self): 211 | self.__flow_token_check() 212 | self.__method_check("AccountDuplicationCheck") 213 | data = { 214 | "flow_token": self.flow_token, 215 | "subtask_inputs": [ 216 | { 217 | "subtask_id": "AccountDuplicationCheck", 218 | "check_logged_in_account": { 219 | "link": "AccountDuplicationCheck_false" 220 | }, 221 | } 222 | ], 223 | } 224 | response = self.session.post( 225 | "https://twitter.com/i/api/1.1/onboarding/task.json", 226 | headers=self.__get_headers(), 227 | json=data, 228 | proxies=self.proxies, 229 | ).json() 230 | self.__error_check(response) 231 | self.flow_token = response.get("flow_token") 232 | self.content = response 233 | return self 234 | 235 | def LoginEnterAlternateIdentifierSubtask(self, text): 236 | self.__flow_token_check() 237 | self.__method_check("LoginEnterAlternateIdentifierSubtask") 238 | data = { 239 | "flow_token": self.flow_token, 240 | "subtask_inputs": [ 241 | { 242 | "subtask_id": "LoginEnterAlternateIdentifierSubtask", 243 | "enter_text": {"text": text, "link": "next_link"}, 244 | } 245 | ], 246 | } 247 | response = self.session.post( 248 | "https://twitter.com/i/api/1.1/onboarding/task.json", 249 | headers=self.__get_headers(), 250 | json=data, 251 | proxies=self.proxies, 252 | ).json() 253 | self.__error_check(response) 254 | self.flow_token = response.get("flow_token") 255 | self.content = response 256 | return self 257 | 258 | def LoginEnterPassword(self, password): 259 | self.__flow_token_check() 260 | self.__method_check("LoginEnterPassword") 261 | data = { 262 | "flow_token": self.flow_token, 263 | "subtask_inputs": [ 264 | { 265 | "subtask_id": "LoginEnterPassword", 266 | "enter_password": {"password": password, "link": "next_link"}, 267 | } 268 | ], 269 | } 270 | response = self.session.post( 271 | "https://twitter.com/i/api/1.1/onboarding/task.json", 272 | headers=self.__get_headers(), 273 | json=data, 274 | proxies=self.proxies, 275 | ).json() 276 | self.__error_check(response) 277 | self.flow_token = response.get("flow_token") 278 | self.content = response 279 | return self 280 | 281 | def LoginTwoFactorAuthChallenge(self, TwoFactorCode): 282 | self.__flow_token_check() 283 | self.__method_check("LoginTwoFactorAuthChallenge") 284 | data = { 285 | "flow_token": self.flow_token, 286 | "subtask_inputs": [ 287 | { 288 | "subtask_id": "LoginTwoFactorAuthChallenge", 289 | "enter_text": {"text": TwoFactorCode, "link": "next_link"}, 290 | } 291 | ], 292 | } 293 | response = self.session.post( 294 | "https://twitter.com/i/api/1.1/onboarding/task.json", 295 | headers=self.__get_headers(), 296 | json=data, 297 | proxies=self.proxies, 298 | ).json() 299 | self.__error_check(response) 300 | self.flow_token = response.get("flow_token") 301 | self.content = response 302 | return self 303 | 304 | def LoginAcid(self, acid): 305 | self.__flow_token_check() 306 | self.__method_check("LoginAcid") 307 | data = { 308 | "flow_token": self.flow_token, 309 | "subtask_inputs": [ 310 | { 311 | "subtask_id": "LoginAcid", 312 | "enter_text": {"text": acid, "link": "next_link"}, 313 | } 314 | ], 315 | } 316 | response = self.session.post( 317 | "https://twitter.com/i/api/1.1/onboarding/task.json", 318 | headers=self.__get_headers(), 319 | json=data, 320 | proxies=self.proxies, 321 | ).json() 322 | self.__error_check(response) 323 | self.flow_token = response.get("flow_token") 324 | self.content = response 325 | return self 326 | 327 | def LoginAcid(self, acid): 328 | self.__flow_token_check() 329 | self.__method_check("LoginAcid") 330 | data = { 331 | "flow_token":self.flow_token, 332 | "subtask_inputs":[ 333 | { 334 | "subtask_id":"LoginAcid", 335 | "enter_text": 336 | { 337 | "text": acid, 338 | "link":"next_link" 339 | } 340 | } 341 | ] 342 | } 343 | response = self.session.post( 344 | "https://twitter.com/i/api/1.1/onboarding/task.json", 345 | headers=self.__get_headers(), 346 | json=data, 347 | proxies=self.proxies, 348 | ).json() 349 | self.flow_token = response.get("flow_token") 350 | self.content = response 351 | self.__error_check() 352 | return self 353 | 354 | 355 | # attの取得 無くても動くっぽい 356 | 357 | def get_att(self): 358 | data = {"flow_token": self.flow_token, "subtask_inputs": []} 359 | response = self.session.post( 360 | "https://twitter.com/i/api/1.1/onboarding/task.json", 361 | headers=self.__get_headers(), 362 | json=data, 363 | proxies=self.proxies, 364 | ).json() 365 | self.__error_check(response) 366 | self.content = response 367 | return self 368 | 369 | # ct0の更新 無くても動くっぽい 370 | 371 | def Viewer(self): 372 | params = { 373 | "variables": json.dumps( 374 | { 375 | "withCommunitiesMemberships": True, 376 | "withCommunitiesCreation": True, 377 | "withSuperFollowsUserFields": True, 378 | } 379 | ) 380 | } 381 | response = self.session.get( 382 | "https://twitter.com/i/api/graphql/O_C5Q6xAVNOmeolcXjKqYw/Viewer", 383 | headers=self.__get_headers(), 384 | params=params, 385 | ) 386 | self.__error_check(response) 387 | self.content = response 388 | return self 389 | 390 | def RedirectToPasswordReset(self): 391 | raise Exception( 392 | "RedirectToPasswordResetは現在サポートされていません。代わりにpassword_reset_flowを使用して下さい。" 393 | ) 394 | 395 | # パスワードリセット 396 | 397 | def password_reset_flow(self): 398 | data = { 399 | "input_flow_data": { 400 | "flow_context": { 401 | "debug_overrides": {}, 402 | "start_location": {"location": "manual_link"}, 403 | } 404 | }, 405 | "subtask_versions": { 406 | "contacts_live_sync_permission_prompt": 0, 407 | "email_verification": 1, 408 | "topics_selector": 1, 409 | "wait_spinner": 1, 410 | "cta": 4, 411 | }, 412 | } 413 | params = {"flow_name": "password_reset"} 414 | response = self.session.post( 415 | "https://twitter.com/i/api/1.1/onboarding/task.json", 416 | headers=self.__get_headers(), 417 | json=data, 418 | params=params, 419 | proxies=self.proxies, 420 | ).json() 421 | self.__error_check(response) 422 | self.flow_token = response.get("flow_token") 423 | self.content = response 424 | return self 425 | 426 | def PwrJsInstrumentationSubtask(self): 427 | self.__flow_token_check() 428 | self.__method_check("PwrJsInstrumentationSubtask") 429 | data = { 430 | "flow_token": self.flow_token, 431 | "subtask_inputs": [ 432 | { 433 | "subtask_id": "PwrJsInstrumentationSubtask", 434 | "js_instrumentation": { 435 | "response": json.dumps( 436 | { 437 | "rf": { 438 | "af07339bbc6d24ced887d705eb0c9fd29b4a7d7ddc21136c9f94d53a4bc774d2": 88, 439 | "a6ce87d6481c6ec4a823548be3343437888441d2a453061c54f8e2eb325856f7": 250, 440 | "a0062ad06384a8afd38a41cd83f31b0dbfdea0eff4b24c69f0dd9095b2fb56d6": 16, 441 | "a929e5913a5715d93491eaffaa139ba4977cbc826a5e2dbcdc81cae0f093db25": 186, 442 | }, 443 | "s": "Q-H-53m1uXImK0F0ogrxRQtCWTH1KIlPbIy0MloowlMa4WNK5ZCcDoXyRs1q_cPbynK73w_wfHG_UVRKKBWRoh6UJtlPS5kMa1p8fEvTYi76hwdzBEzovieR8t86UpeSkSBFYcL8foYKSp6Nop5mQR_QHGyEeleclCPUvzS0HblBJqZZdtUo-6by4BgCyu3eQ4fY5nOF8fXC85mu6k34wo982LMK650NsoPL96DBuloqSZvSHU47wq2uA4xy24UnI2WOc6U9KTvxumtchSYNnXq1HV662B8U2-jWrzvIU4yUHV3JYUO6sbN6j8Ho9JaUNJpJSK7REwqCBQ3yG7iwMAAAAX2Vqcbs", 444 | } 445 | ), 446 | "link": "next_link", 447 | }, 448 | } 449 | ], 450 | } 451 | response = self.session.post( 452 | "https://twitter.com/i/api/1.1/onboarding/task.json", 453 | headers=self.__get_headers(), 454 | json=data, 455 | proxies=self.proxies, 456 | ).json() 457 | self.__error_check(response) 458 | self.flow_token = response.get("flow_token") 459 | self.content = response 460 | return self 461 | 462 | def PasswordResetBegin(self, user_id): 463 | self.__flow_token_check() 464 | self.__method_check("PasswordResetBegin") 465 | data = { 466 | "flow_token": self.flow_token, 467 | "subtask_inputs": [ 468 | { 469 | "subtask_id": "PasswordResetBegin", 470 | "enter_text": {"text": user_id, "link": "next_link"}, 471 | } 472 | ], 473 | } 474 | response = self.session.post( 475 | "https://twitter.com/i/api/1.1/onboarding/task.json", 476 | headers=self.__get_headers(), 477 | json=data, 478 | proxies=self.proxies, 479 | ).json() 480 | self.__error_check(response) 481 | self.flow_token = response.get("flow_token") 482 | self.content = response 483 | return self 484 | 485 | def PasswordResetChooseChallenge(self, choices="0"): 486 | self.__flow_token_check() 487 | self.__method_check("PasswordResetChooseChallenge") 488 | data = { 489 | "flow_token": self.flow_token, 490 | "subtask_inputs": [ 491 | { 492 | "subtask_id": "PasswordResetChooseChallenge", 493 | "choice_selection": { 494 | "link": "next_link", 495 | "selected_choices": [choices], 496 | }, 497 | } 498 | ], 499 | } 500 | response = self.session.post( 501 | "https://twitter.com/i/api/1.1/onboarding/task.json", 502 | headers=self.__get_headers(), 503 | json=data, 504 | proxies=self.proxies, 505 | ).json() 506 | self.__error_check(response) 507 | self.flow_token = response.get("flow_token") 508 | self.content = response 509 | return self 510 | 511 | def PwrKnowledgeChallenge(self, text): 512 | self.__flow_token_check() 513 | self.__method_check("PwrKnowledgeChallenge") 514 | data = { 515 | "flow_token": self.flow_token, 516 | "subtask_inputs": [ 517 | { 518 | "subtask_id": "PwrKnowledgeChallenge", 519 | "enter_text": {"text": text, "link": "next_link"}, 520 | } 521 | ], 522 | } 523 | response = self.session.post( 524 | "https://twitter.com/i/api/1.1/onboarding/task.json", 525 | headers=self.__get_headers(), 526 | json=data, 527 | proxies=self.proxies, 528 | ).json() 529 | self.__error_check(response) 530 | self.flow_token = response.get("flow_token") 531 | self.content = response 532 | return self 533 | 534 | def PasswordResetConfirmChallenge(self, code): 535 | self.__flow_token_check() 536 | self.__method_check("PasswordResetConfirmChallenge") 537 | data = { 538 | "flow_token": self.flow_token, 539 | "subtask_inputs": [ 540 | { 541 | "subtask_id": "PasswordResetConfirmChallenge", 542 | "enter_text": {"text": code, "link": "next_link"}, 543 | } 544 | ], 545 | } 546 | response = self.session.post( 547 | "https://twitter.com/i/api/1.1/onboarding/task.json", 548 | headers=self.__get_headers(), 549 | json=data, 550 | proxies=self.proxies, 551 | ).json() 552 | self.__error_check(response) 553 | self.flow_token = response.get("flow_token") 554 | self.content = response 555 | return self 556 | 557 | def PasswordResetNewPassword(self, password): 558 | self.__flow_token_check() 559 | self.__method_check("PasswordResetNewPassword") 560 | data = { 561 | "flow_token": self.flow_token, 562 | "subtask_inputs": [ 563 | { 564 | "subtask_id": "PasswordResetNewPassword", 565 | "enter_password": {"password": password, "link": "next_link"}, 566 | } 567 | ], 568 | } 569 | response = self.session.post( 570 | "https://twitter.com/i/api/1.1/onboarding/task.json", 571 | headers=self.__get_headers(), 572 | json=data, 573 | proxies=self.proxies, 574 | ).json() 575 | self.__error_check(response) 576 | self.flow_token = response.get("flow_token") 577 | self.content = response 578 | return self 579 | 580 | def PasswordResetSurvey(self, choices="0"): 581 | self.__flow_token_check() 582 | self.__method_check("PasswordResetSurvey") 583 | data = { 584 | "flow_token": self.flow_token, 585 | "subtask_inputs": [ 586 | { 587 | "subtask_id": "PasswordResetSurvey", 588 | "choice_selection": { 589 | "link": "next_link", 590 | "selected_choices": [choices], 591 | }, 592 | } 593 | ], 594 | } 595 | response = self.session.post( 596 | "https://twitter.com/i/api/1.1/onboarding/task.json", 597 | headers=self.__get_headers(), 598 | json=data, 599 | proxies=self.proxies, 600 | ).json() 601 | self.__error_check(response) 602 | self.flow_token = response.get("flow_token") 603 | self.content = response 604 | return self 605 | 606 | # ログイン後 607 | 608 | def CreateTweet(self, tweet_text): 609 | data = { 610 | "queryId": "XyvN0Wv13eeu_gVIHDi10g", 611 | "variables": json.dumps( 612 | { 613 | "tweet_text": tweet_text, 614 | "media": {"media_entities": [], "possibly_sensitive": False}, 615 | "withDownvotePerspective": False, 616 | "withReactionsMetadata": False, 617 | "withReactionsPerspective": False, 618 | "withSuperFollowsTweetFields": True, 619 | "withSuperFollowsUserFields": False, 620 | "semantic_annotation_ids": [], 621 | "dark_request": False, 622 | "withBirdwatchPivots": False, 623 | } 624 | ), 625 | } 626 | response = self.session.post( 627 | "https://twitter.com/i/api/graphql/XyvN0Wv13eeu_gVIHDi10g/CreateTweet", 628 | headers=self.__get_headers(), 629 | json=data, 630 | ).json() 631 | self.__error_check(response) 632 | self.content = response 633 | return self 634 | 635 | def FavoriteTweet(self, tweet_id): 636 | data = { 637 | "queryId": "lI07N6Otwv1PhnEgXILM7A", 638 | "variables": json.dumps( 639 | { 640 | "tweet_id": tweet_id, 641 | } 642 | ), 643 | } 644 | response = self.session.post( 645 | "https://twitter.com/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet", 646 | headers=self.__get_headers(), 647 | json=data, 648 | ).json() 649 | self.__error_check(response) 650 | self.content = response 651 | return self 652 | 653 | def UnfavoriteTweet(self, tweet_id): 654 | data = { 655 | "queryId": "ZYKSe-w7KEslx3JhSIk5LA", 656 | "variables": json.dumps( 657 | { 658 | "tweet_id": tweet_id, 659 | } 660 | ), 661 | } 662 | response = self.session.post( 663 | "https://twitter.com/i/api/graphql/ZYKSe-w7KEslx3JhSIk5LA/UnfavoriteTweet", 664 | headers=self.__get_headers(), 665 | json=data, 666 | ).json() 667 | self.__error_check(response) 668 | self.content = response 669 | return self 670 | 671 | def CreateRetweet(self, tweet_id): 672 | data = { 673 | "queryId": "ojPdsZsimiJrUGLR1sjUtA", 674 | "variables": json.dumps( 675 | { 676 | "tweet_id": tweet_id, 677 | "dark_request": False, 678 | } 679 | ), 680 | } 681 | response = self.session.post( 682 | "https://twitter.com/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet", 683 | headers=self.__get_headers(), 684 | json=data, 685 | ).json() 686 | self.__error_check(response) 687 | self.content = response 688 | return self 689 | 690 | def DeleteRetweet(self, tweet_id): 691 | data = { 692 | "queryId": "iQtK4dl5hBmXewYZuEOKVw", 693 | "variables": json.dumps( 694 | { 695 | "source_tweet_id": tweet_id, 696 | "dark_request": False, 697 | } 698 | ), 699 | } 700 | response = self.session.post( 701 | "https://twitter.com/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet", 702 | headers=self.__get_headers(), 703 | json=data, 704 | ).json() 705 | self.__error_check(response) 706 | self.content = response 707 | return self 708 | 709 | # Legacy API v1.1 710 | 711 | def friendships_create(self, tweet_id): 712 | data = { 713 | "include_profile_interstitial_type": 1, 714 | "include_blocking": 1, 715 | "include_blocked_by": 1, 716 | "include_followed_by": 1, 717 | "include_want_retweets": 1, 718 | "include_mute_edge": 1, 719 | "include_can_dm": 1, 720 | "include_can_media_tag": 1, 721 | "include_ext_has_nft_avatar": 1, 722 | "skip_status": 1, 723 | "id": tweet_id, 724 | } 725 | response = self.session.post( 726 | "https://twitter.com/i/api/1.1/friendships/create.json", 727 | headers=self.__get_headers_legacy(), 728 | data=data, 729 | ).json() 730 | self.content = response 731 | return self 732 | 733 | def friendships_destroy(self, tweet_id): 734 | data = { 735 | "include_profile_interstitial_type": 1, 736 | "include_blocking": 1, 737 | "include_blocked_by": 1, 738 | "include_followed_by": 1, 739 | "include_want_retweets": 1, 740 | "include_mute_edge": 1, 741 | "include_can_dm": 1, 742 | "include_can_media_tag": 1, 743 | "include_ext_has_nft_avatar": 1, 744 | "skip_status": 1, 745 | "id": tweet_id, 746 | } 747 | response = self.session.post( 748 | "https://twitter.com/i/api/1.1/friendships/destroy.json", 749 | headers=self.__get_headers_legacy(), 750 | data=data, 751 | ).json() 752 | self.content = response 753 | return self 754 | --------------------------------------------------------------------------------