Last active
August 22, 2023 17:58
-
-
Save BitTheByte/88437dbb3e751aa1dc9b66401d45ccba to your computer and use it in GitHub Desktop.
Script to tweet like the Official Twitter Application with 2fa support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from base64 import b64decode,b64encode | |
from urllib.parse import quote,parse_qs,urlsplit,urlparse | |
from random import randint | |
from bs4 import BeautifulSoup | |
import calendar | |
import requests | |
import hashlib | |
import base64 | |
import time | |
import hmac | |
import types | |
class Twitter(object): | |
def __init__(self): | |
self.twitter_android_secret = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys" | |
self.twitter_android_key = "3nVuSoBZnx6U4vzUxf5w" | |
self.session = requests.Session() | |
self.access_token = None | |
self.guest_token = None | |
self.challenged = False | |
self.challenge_type = None | |
self.challenge = None | |
self.on_challenge = None | |
self.xauth = None | |
def oauth_signature(self,request,oauth_secret): | |
key = bytes(self.twitter_android_secret +"&"+ oauth_secret, 'UTF-8') | |
message = bytes(request, 'UTF-8') | |
digester = hmac.new(key, message, hashlib.sha1) | |
signature1 = digester.digest() | |
signature2 = base64.urlsafe_b64encode(signature1) | |
signature2 = str(signature2, 'UTF-8').replace("-","+").replace("_","/") | |
return quote(signature2,safe='') | |
def signature_message(self,url,params,oauth_secret,method="POST"): | |
header = method + "&" + quote(url,safe='') + "&" | |
body = bytes() | |
for key,value in params.items(): | |
body += bytes(key,"UTF-8") + b"=" + bytes(value,"UTF-8") + b"&" | |
body = body[:-1] | |
return self.oauth_signature(header + quote(body,safe=''),oauth_secret) | |
def xauth_login(self,username,password): | |
self.access_token = self.session.post("https://api.twitter.com/oauth2/token",params = {'grant_type':'client_credentials'}, | |
headers = { | |
"Accept": "application/json", | |
"Authorization": "Basic " + b64encode((self.twitter_android_key + ":" + self.twitter_android_secret).encode("utf8")).decode("utf8") | |
} | |
).json()["access_token"] | |
self.guest_token = self.session.post("https://api.twitter.com/1.1/guest/activate.json",headers={"Authorization": "Bearer " + self.access_token}).json()["guest_token"] | |
auth = self.session.post("https://api.twitter.com/auth/1/xauth_password.json",headers={ | |
"Authorization": "Bearer " + self.access_token, | |
"X-Guest-Token": self.guest_token | |
}, | |
params = { | |
"x_auth_identifier":username, | |
"x_auth_password":password, | |
"send_error_codes":"true", | |
"x_auth_login_challenge":"1", | |
"x_auth_login_verification":"1", | |
"x_auth_country_code":"US", | |
"ui_metrics":"" | |
}).json() | |
self.xauth = auth | |
if "login_verification_request_url" in auth.keys(): | |
self.challenged = True | |
self.challenge = auth | |
self.challenge_type = parse_qs(urlparse(auth["login_verification_request_url"]).query)['challenge_type'][0] | |
return auth | |
def challenge_access_token(self,user_id,request_id): | |
oauth_nonce = ''.join([str(randint(0,9)) for n in range(31)]) | |
oauth_timestamp = str(calendar.timegm(time.gmtime())) | |
return self.session.post("https://api.twitter.com/oauth/access_token",data={ | |
"x_auth_mode":"client_auth", | |
"x_auth_login_verification":"1", | |
"x_auth_login_challenge": "1", | |
"send_error_codes":"true", | |
"login_verification_user_id": user_id, | |
"login_verification_request_id": request_id | |
}, | |
headers = { | |
"Authorization":'OAuth realm="http://api.twitter.com/", oauth_version="1.0", oauth_nonce="{oauth_nonce}", oauth_timestamp="{oauth_timestamp}", oauth_signature="{oauth_signature}", oauth_consumer_key="{oauth_consumer_key}", oauth_signature_method="HMAC-SHA1"'.format( | |
oauth_nonce = oauth_nonce, | |
oauth_timestamp = oauth_timestamp, | |
oauth_consumer_key = self.twitter_android_key, | |
oauth_signature = self.signature_message(url="https://api.twitter.com/oauth/access_token",params={ | |
"login_verification_request_id": request_id, | |
"login_verification_user_id": user_id, | |
"oauth_consumer_key":self.twitter_android_key, | |
"oauth_nonce": oauth_nonce, | |
"oauth_signature_method":"HMAC-SHA1", | |
"oauth_timestamp": oauth_timestamp, | |
"oauth_version":"1.0", | |
"send_error_codes":"true", | |
"x_auth_login_challenge":"1", | |
"x_auth_login_verification":"1", | |
"x_auth_mode":"client_auth", | |
},oauth_secret="") | |
), | |
"Content-Type": "application/x-www-form-urlencoded", | |
"Accept": "application/json", | |
}).json() | |
def solve_challenge(self,challenge,answer): | |
self.has_challenge = True | |
challenge_url = challenge["login_verification_request_url"] | |
request_id = challenge["login_verification_request_id"] | |
response = self.session.get(challenge_url) | |
soup = BeautifulSoup(response.content, "lxml") | |
authenticity_token = soup.select_one("input[name=authenticity_token]")["value"] | |
challenge_id = soup.select_one("input[name=challenge_id]")["value"] | |
user_id = soup.select_one("input[name=user_id]")["value"] | |
self.challenge_type = soup.select_one("input[name=challenge_type]")["value"] | |
data = { | |
'authenticity_token':authenticity_token, | |
'challenge_id':challenge_id, | |
'user_id':user_id, | |
'challenge_type':self.challenge_type, | |
'platform':'mobile', | |
'redirect_after_login':'', | |
'remember_me':'true', | |
'challenge_response':answer, | |
} | |
self.session.post("https://twitter.com/account/login_challenge",data=data) | |
twitter_session = self.challenge_access_token(user_id, request_id) | |
self.xauth = twitter_session | |
return twitter_session | |
def auth_headers(self,oauth_nonce,oauth_timestamp,oauth_signature,skip_token=False): | |
return { | |
"Authorization": 'OAuth realm="http://api.twitter.com/", oauth_version="1.0", oauth_token="{oauth_token}", oauth_nonce="{oauth_nonce}", oauth_timestamp="{oauth_timestamp}", oauth_signature="{oauth_signature}", oauth_consumer_key="{oauth_consumer_key}", oauth_signature_method="{oauth_signature_method}"'.format( | |
oauth_token = self.xauth["oauth_token"] if not skip_token else "", | |
oauth_nonce = oauth_nonce, | |
oauth_timestamp = oauth_timestamp, | |
oauth_signature = oauth_signature, | |
oauth_consumer_key = self.twitter_android_key, | |
oauth_signature_method = "HMAC-SHA1" | |
), | |
"User-Agent": "TwitterAndroid/8.40.0-release.02 (18400002-r-2) Nexus 7/7.0 (asus;Nexus 7;google;nakasi;0;;0;2012)", | |
"X-Twitter-Active-User": "yes", | |
"X-Twitter-API-Version": "5", | |
"X-Twitter-Client": "TwitterAndroid", | |
"X-Twitter-Client-Language": "en-US", | |
"Accept": "application/json", | |
} | |
def tweet(self, text): | |
api_statuses = "https://api.twitter.com/1.1/statuses/update.json" | |
oauth_nonce = ''.join([str(randint(0,9)) for n in range(31)]) | |
oauth_timestamp = str(calendar.timegm(time.gmtime())) | |
params = { | |
"batch_mode":"off", | |
"cards_platform":"Android-12", | |
"earned_read":"true", | |
"enable_dm_commands":"false", | |
"ext":"mediaRestrictions%2CaltText%2CmediaStats%2CmediaColor%2Cinfo360%2CcameraMoment%2ChighlightedLabel%2Cmaster_playlist_only", | |
"include_blocked_by":"true", | |
"include_blocking":"true", | |
"include_cards":"true", | |
"include_entities":"true", | |
"include_media_features":"true", | |
"include_reply_count":"true", | |
"oauth_consumer_key":self.twitter_android_key, | |
"oauth_nonce": oauth_nonce, | |
"oauth_signature_method":"HMAC-SHA1", | |
"oauth_timestamp":oauth_timestamp, | |
"oauth_token": self.xauth["oauth_token"], | |
"oauth_version":"1.0", | |
"send_error_codes":"true", | |
"status": quote(text,safe=''), | |
"tweet_mode":"extended" | |
} | |
headers = self.auth_headers( | |
oauth_nonce= oauth_nonce, | |
oauth_timestamp= oauth_timestamp, | |
oauth_signature= self.signature_message( | |
url= api_statuses, | |
params= params, | |
oauth_secret= self.xauth['oauth_token_secret'] | |
) | |
) | |
headers["Content-Type"] = "application/x-www-form-urlencoded" | |
post_params = '&'.join([k + u"=" + v for k,v in params.items()]) | |
return self.session.post(api_statuses,headers=headers,data=post_params.encode('utf-8'),verify=False).json() | |
api = Twitter() | |
result = api.xauth_login("username","password") | |
if api.challenged: | |
answer = input("code:").strip() | |
api.solve_challenge(result,answer) | |
api.tweet("XXXXXXXXXXXXXXXXXXXXX") |
xauth_password doesn't work... any solution?
Hi Everyone, this implementation was based on the mobile version of Twitter, if anyone would like to continue supporting it please reverse engineer the app again.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
xauth_password dead