huami-token/huami_token.py

264 wiersze
9.5 KiB
Python
Czysty Zwykły widok Historia

2020-05-25 11:27:57 +00:00
#!/usr/bin/env python3
import json
2020-06-07 21:26:18 +00:00
import uuid
import random
2020-05-28 16:53:33 +00:00
import shutil
2020-06-07 21:26:18 +00:00
import urllib
import argparse
import requests
from rich.console import Console
from rich.table import Column, Table
2020-08-04 15:14:27 +00:00
from rich import box
2020-06-07 21:26:18 +00:00
import urls
2020-05-25 11:27:57 +00:00
class HuamiAmazfit:
def __init__(self, method="amazfit", email=None, password=None):
if method == 'amazfit' and (not email or not password):
raise ValueError("For Amazfit method E-Mail and Password can not be null.")
self.method = method
self.email = email
self.password = password
self.access_token = None
self.country_code = None
self.app_token = None
self.login_token = None
self.user_id = None
self.r = str(uuid.uuid4())
# IMEI or something unique
self.device_id = "02:00:00:%02x:%02x:%02x" % (random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255))
def get_access_token(self):
2020-06-01 16:49:48 +00:00
print(f"Getting access token with {self.method} login method...")
2020-05-25 11:27:57 +00:00
if self.method == 'xiaomi':
2020-06-07 21:26:18 +00:00
login_url = urls.URLS["login_xiaomi"]
2020-05-25 11:27:57 +00:00
2020-06-01 16:49:48 +00:00
print(f"Copy this URL to web-browser \n\n{login_url}\n\nand login to your Mi account.")
2020-05-25 11:27:57 +00:00
token_url = input("\nPaste URL after redirection here.\n")
parsed_token_url = urllib.parse.urlparse(token_url)
token_url_parameters = urllib.parse.parse_qs(parsed_token_url.query)
if 'code' not in token_url_parameters:
raise ValueError("No 'code' parameter in login url.")
self.access_token = token_url_parameters['code']
self.country_code = 'US'
elif self.method == 'amazfit':
2020-06-07 21:26:18 +00:00
auth_url = urls.URLS['tokens_amazfit'].format(user_email=urllib.parse.quote(self.email))
data = urls.PAYLOADS['tokens_amazfit']
data['password'] = self.password
2020-05-25 11:27:57 +00:00
response = requests.post(auth_url, data=data, allow_redirects=False)
response.raise_for_status()
# 'Location' parameter contains url with login status
redirect_url = urllib.parse.urlparse(response.headers.get('Location'))
redirect_url_parameters = urllib.parse.parse_qs(redirect_url.query)
if 'error' in redirect_url_parameters:
2020-06-01 16:49:48 +00:00
raise ValueError(f"Wrong E-mail or Password. Error: {redirect_url_parameters['error']}")
2020-05-25 11:27:57 +00:00
if 'access' not in redirect_url_parameters:
raise ValueError("No 'access' parameter in login url.")
if 'country_code' not in redirect_url_parameters:
raise ValueError("No 'country_code' parameter in login url.")
self.access_token = redirect_url_parameters['access']
self.country_code = redirect_url_parameters['country_code']
print("Token: {}".format(self.access_token))
return self.access_token
def login(self, external_token=None):
print("Logging in...")
if external_token:
self.access_token = external_token
2020-06-07 21:26:18 +00:00
login_url = urls.URLS['login_amazfit']
data = urls.PAYLOADS['login_amazfit']
data['country_code'] = self.country_code
data['device_id'] = self.device_id
data['third_name'] = 'huami' if self.method == 'amazfit' else 'mi-watch'
data['code'] = self.access_token
data['grant_type'] = 'access_token' if self.method == 'amazfit' else 'request_token'
2020-05-25 11:27:57 +00:00
response = requests.post(login_url, data=data, allow_redirects=False)
response.raise_for_status()
login_result = response.json()
if 'error_code' in login_result:
2020-06-01 16:49:48 +00:00
raise ValueError(f"Login error. Error: {login_result['error_code']}")
2020-05-25 11:27:57 +00:00
if 'token_info' not in login_result:
raise ValueError("No 'token_info' parameter in login data.")
else:
token_info = login_result['token_info']
if 'app_token' not in token_info:
raise ValueError("No 'app_token' parameter in login data.")
self.app_token = token_info['app_token']
if 'login_token' not in token_info:
raise ValueError("No 'login_token' parameter in login data.")
self.login_token = token_info['login_token']
if 'user_id' not in token_info:
raise ValueError("No 'user_id' parameter in login data.")
self.user_id = token_info['user_id']
print("Logged in! User id: {}".format(self.user_id))
def get_wearable_auth_keys(self):
print("Getting linked wearables...")
2020-05-25 11:27:57 +00:00
2020-06-07 21:26:18 +00:00
devices_url = urls.URLS['devices'].format(user_id=urllib.parse.quote(self.user_id))
headers = urls.PAYLOADS['devices']
headers['apptoken'] = self.app_token
2020-05-25 11:27:57 +00:00
response = requests.get(devices_url, headers=headers)
response.raise_for_status()
device_request = response.json()
if 'items' not in device_request:
raise ValueError("No 'items' parameter in devices data.")
devices = device_request['items']
devices_dict = {}
2020-05-25 11:27:57 +00:00
for idx, wearable in enumerate(devices):
if 'macAddress' not in wearable:
raise ValueError("No 'macAddress' parameter in device data.")
mac_address = wearable['macAddress']
if 'additionalInfo' not in wearable:
raise ValueError("No 'additionalInfo' parameter in device data.")
device_info = json.loads(wearable['additionalInfo'])
if 'auth_key' not in device_info:
raise ValueError("No 'auth_key' parameter in device data.")
key_str = device_info['auth_key']
auth_key = '0x' + (key_str if key_str != '' else '00')
devices_dict[f'{mac_address}'] = auth_key
2020-05-25 11:27:57 +00:00
return devices_dict
2020-05-25 11:27:57 +00:00
2020-05-28 16:53:33 +00:00
def get_gps_data(self):
agps_packs = ["AGPS_ALM", "AGPSZIP"]
agps_file_names = ["cep_alm_pak.zip", "cep_7days.zip"]
2020-06-07 21:26:18 +00:00
agps_link = urls.URLS['agps']
2020-05-28 16:53:33 +00:00
2020-06-07 21:26:18 +00:00
headers = urls.PAYLOADS['agps']
headers['apptoken'] = self.app_token
2020-05-28 16:53:33 +00:00
for idx, agps_pack_name in enumerate(agps_packs):
print("Downloading {}...".format(agps_pack_name))
2020-06-07 21:26:18 +00:00
response = requests.get(agps_link.format(pack_name=agps_pack_name), headers=headers)
2020-05-28 16:53:33 +00:00
response.raise_for_status()
agps_result = response.json()[0]
if 'fileUrl' not in agps_result:
raise ValueError("No 'fileUrl' parameter in files request.")
with requests.get(agps_result['fileUrl'], stream=True) as r:
with open(agps_file_names[idx], 'wb') as f:
shutil.copyfileobj(r.raw, f)
2020-05-25 11:27:57 +00:00
def logout(self):
2020-06-07 21:26:18 +00:00
logout_url = urls.URLS['logout']
data = urls.PAYLOADS['logout']
data['login_token'] = self.login_token
2020-05-25 11:27:57 +00:00
response = requests.post(logout_url, data=data)
logout_result = response.json()
if logout_result['result'] == 'ok':
print("\nLogged out.")
else:
print("\nError logging out.")
if __name__ == "__main__":
2020-05-28 16:53:33 +00:00
parser = argparse.ArgumentParser(description="Obtain Bluetooth Auth key from Amazfit "
"servers and download AGPS data.")
2020-05-25 11:27:57 +00:00
parser.add_argument("-m",
"--method",
choices=["amazfit", "xiaomi"],
default="amazfit",
required=True,
help="Login method ")
parser.add_argument("-e",
"--email",
required=False,
help="Account e-mail address")
2020-05-25 11:27:57 +00:00
parser.add_argument("-p",
"--password",
required=False,
help="Account Password")
parser.add_argument("-b",
"--bt_keys",
required=False,
action='store_true',
help="Get bluetooth tokens of paired devices")
parser.add_argument("-g",
"--gps",
required=False,
action='store_true',
help="Download A-GPS files")
parser.add_argument("-a",
"--all",
required=False,
action='store_true',
help="Do everything: get bluetooth tokens, download A-GPS files")
parser.add_argument("-n",
"--no_logout",
required=False,
action='store_true',
help="Do not logout, keep active session and display app token and access token")
2020-05-25 11:27:57 +00:00
args = parser.parse_args()
console = Console()
2020-08-04 15:14:27 +00:00
table = Table(show_header=True, header_style="bold", box=box.ASCII)
table.add_column("MAC", style="dim", width=17, justify='center')
2020-08-09 16:20:08 +00:00
table.add_column("auth_key", width=50, justify='center')
2020-05-25 11:27:57 +00:00
device = HuamiAmazfit(method=args.method,
email=args.email,
password=args.password)
device.get_access_token()
device.login()
if args.bt_keys or args.all:
device_keys = device.get_wearable_auth_keys()
for device_key in device_keys:
table.add_row(device_key, device_keys[device_key])
console.print(table)
if args.gps or args.all:
device.get_gps_data()
if args.no_logout:
print("\nNo logout!")
print(f"app_token={device.app_token}\nlogin_token={device.login_token}")
else:
device.logout()