diff --git a/.gitignore b/.gitignore
index 606a10b..0aa3360 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.buildozer
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/README.md b/README.md
index 85b8957..12ee9e4 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,25 @@
# huafetcher
+Kivy GUI for huami-token. Works on desktop and as Android apk. Downloads key and aGPS data, unzips it into `/storage/emulated/0`
+
+[huami-token](https://github.com/argrento/huami-token): all credits to the original author
+
+## Install
+
+Install [Buildozer](https://github.com/kivy/buildozer/) and [Kivy](https://github.com/kivy/kivy)
+
+pip install buildozer
+pip install kivy
+
+or
+
+pip install -r requirements.txt
+
+## Run
+
+python main.py
+
+## make Android apk
+
+buildozer -v android debug deploy run
+
diff --git a/data/icon.png b/data/icon.png
new file mode 100644
index 0000000..0c74ddc
Binary files /dev/null and b/data/icon.png differ
diff --git a/data/icon.svg b/data/icon.svg
new file mode 100644
index 0000000..b3c0442
--- /dev/null
+++ b/data/icon.svg
@@ -0,0 +1,80 @@
+
+
diff --git a/huami_token.py b/huami_token.py
new file mode 100644
index 0000000..7ccaf8d
--- /dev/null
+++ b/huami_token.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+
+import json
+import uuid
+import random
+import shutil
+import urllib
+import requests
+
+import urls
+
+
+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.result = 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 __str__(self):
+ return f"{self.email} {self.password} {self.method} {self.access_token}"
+
+ def parse_token(self, token_url):
+
+ 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:
+ return
+
+ self.access_token = token_url_parameters['code']
+ self.country_code = 'US'
+
+ def get_access_token(self):
+ print(f"Getting access token with {self.method} login method...")
+
+ if self.method == 'xiaomi':
+ login_url = urls.URLS["login_xiaomi"]
+
+
+
+ elif self.method == 'amazfit':
+
+ auth_url = urls.URLS['tokens_amazfit'].format(user_email=urllib.parse.quote(self.email))
+
+ data = urls.PAYLOADS['tokens_amazfit']
+ data['password'] = self.password
+
+ 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:
+ raise ValueError(f"Wrong E-mail or Password. Error: {redirect_url_parameters['error']}")
+
+ 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']
+
+
+ def login(self, external_token=None):
+ print("Logging in...")
+ if external_token:
+ self.access_token = external_token
+
+ 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'
+
+ response = requests.post(login_url, data=data, allow_redirects=False)
+ response.raise_for_status()
+ login_result = response.json()
+
+ if 'error_code' in login_result:
+ raise ValueError(f"Login error. Error: {login_result['error_code']}")
+
+ 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))
+ return True
+
+ def get_wearable_auth_keys(self):
+ print("Getting linked wearables...")
+ print(self.user_id)
+
+ devices_url = urls.URLS['devices'].format(user_id=urllib.parse.quote(self.user_id))
+
+ headers = urls.PAYLOADS['devices']
+ headers['apptoken'] = self.app_token
+
+ 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 = {}
+
+ 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
+
+ return devices_dict
+
+ def get_gps_data(self):
+ agps_packs = ["AGPS_ALM", "AGPSZIP"]
+ agps_file_names = ["cep_alm_pak.zip", "cep_7days.zip"]
+ agps_link = urls.URLS['agps']
+
+ headers = urls.PAYLOADS['agps']
+ headers['apptoken'] = self.app_token
+
+ for idx, agps_pack_name in enumerate(agps_packs):
+ print("Downloading {}...".format(agps_pack_name))
+ response = requests.get(agps_link.format(pack_name=agps_pack_name), headers=headers)
+ 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)
+
+ def logout(self):
+ logout_url = urls.URLS['logout']
+
+ data = urls.PAYLOADS['logout']
+ data['login_token'] = self.login_token
+
+ response = requests.post(logout_url, data=data)
+ logout_result = response.json()
+
+ if logout_result['result'] == 'ok':
+ print("\nLogged out.")
+ else:
+ print("\nError logging out.")
+
+
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..fc011ef
--- /dev/null
+++ b/main.py
@@ -0,0 +1,394 @@
+from kivy.app import App
+from kivy.uix.button import Button
+from kivy.uix.label import Label
+from kivy.lang import Builder
+from kivy.utils import platform
+from kivy.uix.textinput import TextInput
+from kivy.uix.boxlayout import BoxLayout
+from functools import partial
+from kivy.clock import Clock
+from kivy.logger import Logger
+from kivy.uix.dropdown import DropDown
+from huami_token import HuamiAmazfit
+import urls as urls
+from kivy.core.clipboard import Clipboard
+from kivy.storage.jsonstore import JsonStore
+
+DEBUG=False
+DEBUG=True
+
+def debug_print(*xargs):
+ if DEBUG:
+ print(*xargs)
+
+SPACING=2
+Builder.load_string('''
+:
+ font_size: 30
+ halign: 'left'
+ color: 1,1,1,1
+ bcolor: .1, 0, .6, 0
+ canvas.before:
+ Color:
+ rgba: root.bcolor
+ Rectangle:
+ size: (self.width -2, self.height -2)
+ pos: (self.x+1,self.y +1)
+
+
+:
+ font_size: 30
+ halign: 'center'
+ bcolor: .7, .7, .7, 1
+ canvas.before:
+ Color:
+ rgba: root.bcolor
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+:
+ font_size: 30
+ halign: 'left'
+ bcolor: .7, .7, .7, 1
+ canvas.before:
+ Color:
+ rgba: root.bcolor
+ Rectangle:
+ size: self.size
+ pos: self.pos
+:
+ font_size: 30
+ bcolor: .7, .7, .7, 1
+ background_color: .1, 0, .5, 0
+ canvas.before:
+ Color:
+ rgba: root.bcolor
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+ ''')
+class MyLabel(Label):
+ pass
+
+class MyLeftLabel(Label):
+ pass
+
+class MyButton(Button):
+ pass
+
+class MyInput(TextInput):
+ pass
+
+
+class Main(App):
+ def build(self):
+ self.huamidevice=HuamiAmazfit()
+ self.store = JsonStore('credentials.json')
+
+ screen_layout = BoxLayout(orientation="vertical", spacing=SPACING)
+ buttons_layout = BoxLayout(orientation="horizontal", spacing=SPACING)
+ rows_layout = BoxLayout(orientation="vertical", spacing=SPACING)
+
+ self.instructions_label=MyLeftLabel(text='Huafetcher')
+ rows_layout.add_widget(self.instructions_label)
+
+ dropdown = DropDown()
+
+ xiaomi_button = MyButton(text='Xiaomi', size_hint_y=None, height=150)
+ xiaomi_button.bind(on_press=lambda a:self.set_login_method('xiaomi'))
+ xiaomi_button.bind(on_release=lambda btn: dropdown.select(btn.text))
+ amazfit_button = MyButton(text='Amazfit', size_hint_y=None, height=150)
+ amazfit_button.bind(on_press=lambda a:self.set_login_method('amazfit'))
+ amazfit_button.bind(on_release=lambda btn: dropdown.select(btn.text))
+
+ dropdown.add_widget(xiaomi_button)
+ dropdown.add_widget(amazfit_button)
+
+ dropdown_button = MyButton(text='Login method')
+ dropdown_button.bind(on_release=dropdown.open)
+ #dropdown_button.bind(on_press=lambda x: setattr(dropdown_button,'text','Select'))
+ dropdown.bind(on_select=lambda instance, x: setattr(dropdown_button, 'text', x))
+
+ get_token_button = MyButton(text='Get token')
+ get_token_button.bind(on_press=self.on_press_button_gettoken)
+
+ fetch_key_button = MyButton(text='Fetch key')
+ fetch_key_button.bind(on_press=self.on_press_button_fetch_key)
+
+ fetch_agps_button = MyButton(text='Fetch aGPS')
+ fetch_agps_button.bind(on_press=self.on_press_button_agps)
+
+ share_agps_button = MyButton(text='Share aGPS')
+ share_agps_button.bind(on_press=self.on_press_button_share_agps)
+
+ buttons_layout.add_widget(get_token_button)
+ #buttons_layout.add_widget(login_button)
+ buttons_layout.add_widget(fetch_key_button)
+ buttons_layout.add_widget(fetch_agps_button)
+ #sharing doesn't work
+ #buttons_layout.add_widget(share_agps_button)
+
+ paste_token_input_layout = BoxLayout(orientation="horizontal", spacing=SPACING)
+ paste_token_input_label=MyLabel(text='URL result')
+
+ self.paste_token_input = MyInput(text='',
+ multiline=False,
+ )
+ self.paste_token_input.bind(text=self.set_token)
+
+
+ paste_button1=MyButton(text='Paste', size_hint=(.3, 1))
+ paste_button1.bind(on_press=lambda instance: self.on_press_paste(self.paste_token_input) )
+
+ paste_token_input_layout.add_widget(paste_token_input_label)
+ paste_token_input_layout.add_widget(paste_button1)
+ paste_token_input_layout.add_widget(self.paste_token_input)
+ credentials_email_label=MyButton(text='Email' , size_hint=(.7, 1))
+
+ self.credentials_email_input = MyInput(text='',
+ multiline=False,
+ )
+ self.credentials_email_input.bind(text=lambda instance,x: setattr(self.huamidevice,'email',x))
+ credentials_email_label.bind(on_press=self.on_press_paste_email)
+
+ paste_button2=MyButton(text='Paste', size_hint=(.3, 1))
+ paste_button2.bind(on_press=lambda instance: self.on_press_paste(self.credentials_email_input) )
+
+ save_button1=MyButton(text='Save', size_hint=(.3, 1))
+ #load_button1.bind(on_press=lambda instance: )
+ save_button1.bind(on_press=lambda instance: self.on_press_save(self.credentials_email_input, 'email') )
+
+
+
+ self.credentials_email_layout = BoxLayout(orientation="horizontal", spacing=SPACING)
+ self.credentials_email_layout.add_widget(credentials_email_label)
+ #credentials_email_layout.add_widget(load_button1)
+ self.credentials_email_layout.add_widget(save_button1)
+ self.credentials_email_layout.add_widget(paste_button2)
+ self.credentials_email_layout.add_widget(self.credentials_email_input)
+
+
+ credentials_password_label=MyLabel(text='Password', size_hint=(.7, 1))
+
+ self.credentials_password_input = MyInput(text='',
+ multiline=False,
+ )
+ self.credentials_password_input.bind(text=lambda instance,x: setattr(self.huamidevice,'password',x))
+ credentials_password_label.bind(on_press=self.on_press_paste_password)
+
+ paste_button3=MyButton(text='Paste', size_hint=(.3, 1))
+ paste_button3.bind(on_press=lambda instance: self.on_press_paste(self.credentials_password_input) )
+
+ save_button2=MyButton(text='Save', size_hint=(.3, 1))
+ save_button2.bind(on_press=lambda instance: self.on_press_save(self.credentials_password_input, 'password') )
+
+ self.credentials_password_layout = BoxLayout(orientation="horizontal", spacing=SPACING)
+ self.credentials_password_layout.add_widget(credentials_password_label)
+ self.credentials_password_layout.add_widget(save_button2)
+ self.credentials_password_layout.add_widget(paste_button3)
+ self.credentials_password_layout.add_widget(self.credentials_password_input)
+
+ rows_layout.add_widget(dropdown_button)
+ rows_layout.add_widget(self.credentials_email_layout)
+ rows_layout.add_widget(self.credentials_password_layout)
+ rows_layout.add_widget(paste_token_input_layout)
+
+ result_value_label=MyButton(text='Found key')
+ self.result_value_value=TextInput()
+
+
+ copy_button4=MyButton(text='Copy', size_hint=(.3, 1))
+ copy_button4.bind(on_press=lambda instance: self.on_press_copy(self.result_value_value) )
+
+
+ result_value_layout = BoxLayout(orientation="horizontal", spacing=SPACING)
+ result_value_layout.add_widget(result_value_label)
+ result_value_layout.add_widget(copy_button4)
+ result_value_layout.add_widget(self.result_value_value)
+ result_value_label.bind(on_press=self.on_press_copy_result)
+
+
+ rows_layout.add_widget(result_value_layout)
+
+
+ rows_layout.add_widget(buttons_layout)
+
+
+ screen_layout.add_widget(rows_layout)
+
+ self.on_press_load(self.credentials_email_input, 'email')
+ self.on_press_load(self.credentials_password_input, 'password')
+ self.set_login_method('xiaomi')
+ dropdown_button.text='Xiaomi'
+
+ return screen_layout
+
+ def hide_widget(self, wid, dohide=True):
+ debug_print(dohide)
+ if hasattr(wid, 'saved_attrs'):
+ debug_print(wid.saved_attrs)
+ if not dohide:
+ wid.height, wid.size_hint_y, wid.opacity, wid.disabled = wid.saved_attrs
+ del wid.saved_attrs
+ elif dohide:
+ wid.saved_attrs = wid.height, wid.size_hint_y, wid.opacity, wid.disabled
+ wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
+
+
+ def set_login_method(self,method):
+ debug_print(method)
+ self.huamidevice.method=method
+ if method == 'xiaomi':
+ self.hide_widget(self.credentials_email_layout, dohide=True)
+ self.hide_widget(self.credentials_password_layout, dohide=True)
+ else:
+ self.hide_widget(self.credentials_email_layout, dohide=False)
+ self.hide_widget(self.credentials_password_layout, dohide=False)
+
+ def set_token(self, instance, text):
+ debug_print("got", text)
+ debug_print(self.huamidevice)
+ self.huamidevice.parse_token(text)
+ debug_print(self.huamidevice)
+
+ def on_press_button_gettoken(self, instance):
+ debug_print('You pressed the button login!')
+ debug_print(self.huamidevice)
+ self.instructions_label.text="log in and paste url here"
+ if self.huamidevice.method == 'xiaomi':
+ login_url = urls.URLS["login_xiaomi"]
+ if ( platform != 'android' ):
+ import webbrowser
+ webbrowser.open(login_url, new = 2)
+ else:
+ from jnius import autoclass
+ from jnius import cast
+
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+ Intent = autoclass('android.content.Intent')
+ Uri = autoclass('android.net.Uri')
+ intent = Intent()
+ intent.setAction(Intent.ACTION_VIEW)
+ intent.setData(Uri.parse(login_url))
+ currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
+ currentActivity.startActivity(intent)
+
+ self.huamidevice.get_access_token()
+
+
+ def on_press_button_fetch_key(self, instance):
+ debug_print('You pressed the button fetch!')
+ debug_print(self.huamidevice)
+ self.instructions_label.text="signing in"
+ if (self.huamidevice.login()):
+ self.instructions_label.text="Signed in as: {}, getting data".format(self.huamidevice.user_id)
+
+
+ device_keys = self.huamidevice.get_wearable_auth_keys()
+ self.instructions_label.text="Done"
+ self.result_value_value.text=""
+ for device_key in device_keys:
+ debug_print(f"{device_key} {device_keys[device_key]}")
+
+ self.result_value_value.text=f"{device_keys[device_key]}"
+
+ #Clock.schedule_once(partial(self.doit), 1)
+
+ def on_press_paste_token(self, instance):
+ self.paste_token_input.text=Clipboard.paste()
+
+ def on_press_paste_email(self, instance):
+ self.credentials_email_input.text=Clipboard.paste()
+
+ def on_press_paste_password(self, instance):
+ self.credentials_password_input.text=Clipboard.paste()
+
+ def on_press_copy_result(self, instance):
+ Clipboard.copy(self.result_value_value.text)
+
+ def on_press_paste(self, instance):
+ instance.text=Clipboard.paste()
+
+ def on_press_copy(self, instance):
+ Clipboard.copy(instance.text)
+
+ def on_press_load(self, instance, key):
+ if self.store.exists(key):
+ instance.text=self.store.get(key)["value"]
+
+ def on_press_save(self, instance, key):
+ self.store.put(key, value=instance.text)
+
+ def on_press_button_agps(self, instance):
+ import zipfile
+ debug_print('You pressed the button agps!')
+ debug_print(self.huamidevice)
+ self.instructions_label.text="signing in"
+ if (self.huamidevice.login()):
+ self.instructions_label.text="Signed in as: {}, getting data".format(self.huamidevice.user_id)
+
+ self.huamidevice.get_gps_data()
+ agps_file_names = ["cep_alm_pak.zip"]
+ if ( platform == 'android' ):
+ import shutil
+ import os
+ from jnius import autoclass
+ from jnius import cast
+ Environment = autoclass('android.os.Environment')
+ File = autoclass('java.io.File')
+ data_dir = Environment.getExternalStorageDirectory().getPath()
+ debug_print(data_dir)
+ for filename in agps_file_names:
+ sdpathfile = os.path.join(data_dir, filename)
+ shutil.copyfile(filename, sdpathfile)
+ with zipfile.ZipFile(filename, "r") as zip_f:
+ #zip_f.extractall()
+ zip_f.extractall(data_dir)
+
+ self.instructions_label.text="Done"
+ #Clock.schedule_once(partial(self.doit), 1)
+
+ def on_press_button_share_agps(self, instance):
+ #not working because android broke it
+ if ( platform == 'android' ):
+ import os
+ from jnius import autoclass
+ from jnius import cast
+ Environment = autoclass('android.os.Environment')
+ File = autoclass('java.io.File')
+ data_dir = Environment.getExternalStorageDirectory().getPath()
+
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+ Intent = autoclass('android.content.Intent')
+ Uri = autoclass('android.net.Uri')
+ intent = Intent()
+ intent.setAction(Intent.ACTION_VIEW)
+ data_dir = getattr(self, 'user_data_dir')
+ file_target=File(os.path.join(data_dir, "cep_pak.bin"))
+ #target=Uri.parse(os.path.join("file:///", data_dir, "cep_pak.bin"))
+ target=Uri.fromFile(file_target)
+ #intent.setData(Uri.parse(os.path.join("file:///", data_dir, "cep_pak.bin")))
+ #intent.setType("application/octet-stream")
+ intent.setDataAndType(target, "application/octet-stream")
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
+ currentActivity.startActivity(intent)
+
+ #intent = Intent()
+ #intent.setAction(Intent.ACTION_VIEW)
+ #intent.setData(Uri.parse("gps_alm.bin"))
+ #currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
+ #currentActivity.startActivity(intent)
+
+
+ #def doit(self, *kargs):
+
+ def openweb(url):
+ debug_print("open ", url)
+
+if __name__ == '__main__':
+ app = Main()
+ app.run()
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..fc35816
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,221 @@
+aiofile==1.4.3
+aiofiles==0.4.0
+aiohttp==3.5.4
+aiohttp-cors==0.7.0
+aiosqlite==0.8.0
+altgraph==0.16.1
+appdirs==1.4.4
+argon2-cffi==20.1.0
+arrow==0.17.0
+asciimatics==1.11.0
+asn1crypto==0.24.0
+astral==1.9.2
+async-timeout==3.0.1
+atomicwrites==1.3.0
+attrs==18.2.0
+backcall==0.2.0
+bcrypt==3.1.5
+beautifulsoup4==4.6.3
+binaryornot==0.4.4
+bleach==3.1.5
+boltons==18.0.1
+boto3==1.9.106
+botocore==1.12.106
+briefcase==0.3.3
+brython==3.6.2
+bs4==0.0.1
+buildozer==1.2.0
+cachetools==4.1.0
+certifi==2018.11.29
+cffi==1.12.2
+chardet==3.0.4
+cli-helpers==2.1.0
+click==7.1.2
+colorama==0.4.3
+colosseum==0.2.0
+commonmark==0.9.1
+configobj==5.0.6
+cookiecutter==1.7.2
+cryptography==2.9
+cycler==0.10.0
+Cython==0.29.21
+dateparser==0.7.0
+decorator==4.4.2
+defusedxml==0.6.0
+distlib==0.3.1
+distro==1.4.0
+docutils==0.14
+ecdsa==0.16.0
+entrypoints==0.3
+enum34==1.1.10
+envs==1.3
+et-xmlfile==1.0.1
+face==0.1.0
+feedparser==6.0.2
+filelock==3.0.12
+Flask==0.12.2
+Flask-SQLAlchemy==2.3.2
+future==0.18.2
+gbulb==0.6.1
+gitdb==4.0.5
+GitPython==3.1.11
+glom==18.4.0
+gTTS-token==1.1.3
+h11==0.9.0
+h2==3.2.0
+home-assistant-frontend==20190220.0
+homeassistant==0.88.2
+hpack==3.0.0
+humanize==0.5.1
+hyperframe==5.2.0
+idna==2.7
+ifaddr==0.1.6
+importlib-metadata==1.6.0
+ipykernel==5.3.4
+ipython==7.18.1
+ipython-genutils==0.2.0
+ipywidgets==7.5.1
+iso8601==0.1.12
+itsdangerous==0.24
+jdcal==1.4.1
+jedi==0.17.2
+Jinja2==2.10
+jinja2-time==0.2.0
+jmespath==0.9.4
+joblib==0.16.0
+jsonschema==3.2.0
+jupyter==1.0.0
+jupyter-client==6.1.7
+jupyter-console==6.2.0
+jupyter-core==4.6.3
+Kivy==1.11.1
+Kivy-Garden==0.1.4
+kiwisolver==1.0.1
+litecli==1.4.1
+Logbook==1.5.3
+lxml==4.2.5
+macholib==1.11
+MarkupSafe==1.0
+matplotlib==3.0.0
+matrix-nio==0.10.0
+mistune==0.8.4
+MouseInfo==0.1.2
+multidict==4.4.2
+mutagen==1.42.0
+nbconvert==5.6.1
+nbformat==5.0.7
+netdisco==2.3.0
+notebook==6.1.3
+Nuitka==0.6.3.1
+numpy==1.15.2
+opencv-python==4.1.2.30
+openpyxl==3.0.3
+packaging==20.4
+paho-mqtt==1.4.0
+pandas==0.24.0
+pandocfilters==1.4.2
+parso==0.7.1
+patsy==0.5.1
+peewee==3.13.2
+pefile==2018.8.8
+pep517==0.6.0
+pexpect==4.8.0
+pickleshare==0.7.5
+Pillow==6.2.1
+ply==3.11
+poyo==0.5.0
+prometheus-client==0.8.0
+prompt-toolkit==3.0.7
+ptyprocess==0.6.0
+PyAutoGUI==0.9.48
+pycairo==1.20.0
+pycparser==2.19
+pycryptodome==3.3.1
+pyfiglet==0.8.post1
+pygal==2.4.0
+PyGetWindow==0.0.8
+Pygments==2.6.1
+PyGObject==3.38.0
+pyjsparser==2.7.1
+PyJWT==1.6.4
+PyMsgBox==1.0.6
+pyodbc==4.0.24
+pyOpenSSL==19.1.0
+pyotp==2.2.6
+pyparsing==2.2.2
+pyperclip==1.7.0
+PyQRCode==1.2.1
+PyRect==0.1.4
+pyrsistent==0.16.0
+PyScreeze==0.1.25
+pyserial==3.4
+python-dateutil==2.7.3
+python-jose-cryptodome==1.3.2
+python-magic==0.4.15
+python-olm==3.1.3
+python-slugify==4.0.1
+python-xlib==0.23
+python3-xlib==0.15
+pytoml==0.1.21
+PyTweening==1.0.3
+pytz==2018.9
+PyYAML==3.13
+pyzmq==19.0.2
+qtconsole==4.7.6
+QtPy==1.9.0
+regex==2019.2.7
+requests==2.25.0
+rich==9.0.1
+ruamel.yaml==0.15.88
+s3transfer==0.2.0
+scikit-learn==0.23.2
+scipy==1.2.0
+selenium==3.141.0
+Send2Trash==1.5.0
+serial==0.0.97
+sgmllib3k==1.0.0
+sh==1.14.1
+simplegeneric==0.8.1
+six==1.15.0
+sklearn==0.0
+slimit==0.8.1
+smmap==3.0.4
+SQLAlchemy==1.2.2
+sqlparse==0.3.1
+statsmodels==0.12.0
+tabulate==0.8.7
+terminado==0.8.3
+terminaltables==3.1.0
+testpath==0.4.4
+text-unidecode==1.3
+threadpoolctl==2.1.0
+toga-core==0.3.0.dev25
+toga-gtk==0.3.0.dev25
+toml==0.10.2
+tornado==6.0.4
+traitlets==4.3.3
+travertino==0.1.3
+typing-extensions==3.7.4.3
+tzlocal==1.5.1
+ua-parser==0.8.0
+Unidecode==1.0.23
+unpaddedbase64==1.1.0
+urllib3==1.23
+urwid==2.1.0
+user-agents==1.1.0
+virtualenv==20.1.0
+voluptuous==0.11.5
+voluptuous-serialize==2.0.0
+warrant==0.6.1
+wcwidth==0.2.5
+webcolors==1.11.1
+webencodings==0.5.1
+Werkzeug==0.16.0
+widgetsnbextension==3.5.1
+xlib==0.21
+xlrd==1.2.0
+xmltodict==0.11.0
+yarl==1.2.6
+yattag==1.10.0
+zeroconf==0.21.3
+zipp==3.1.0
diff --git a/urls.py b/urls.py
new file mode 100644
index 0000000..8836b6d
--- /dev/null
+++ b/urls.py
@@ -0,0 +1,53 @@
+URLS = {
+ 'login_xiaomi': 'https://account.xiaomi.com/oauth2/authorize?skip_confirm=false&'
+ 'client_id=2882303761517383915&pt=0&scope=1+6000+16001+20000&'
+ 'redirect_uri=https%3A%2F%2Fhm.xiaomi.com%2Fwatch.do&_locale=en_US&response_type=code',
+ 'tokens_amazfit': 'https://api-user.huami.com/registrations/{user_email}/tokens',
+ 'login_amazfit': 'https://account.huami.com/v2/client/login',
+ 'devices': 'https://api-mifit-us2.huami.com/users/{user_id}/devices',
+ 'agps': 'https://api-mifit-us2.huami.com/apps/com.huami.midong/fileTypes/{pack_name}/files',
+ 'data_short': 'https://api-mifit-us2.huami.com/users/{user_id}/deviceTypes/4/data',
+ 'logout': 'https://account-us2.huami.com/v1/client/logout'
+}
+
+PAYLOADS = {
+ 'login_xiaomi': None,
+ 'tokens_amazfit': {
+ 'state': 'REDIRECTION',
+ 'client_id': 'HuaMi',
+ 'password': None,
+ 'redirect_uri': 'https://s3-us-west-2.amazonws.com/hm-registration/successsignin.html',
+ 'region': 'us-west-2',
+ 'token': 'access',
+ 'country_code': 'US'
+ },
+ 'login_amazfit': {
+ 'dn': 'account.huami.com,api-user.huami.com,app-analytics.huami.com,api-watch.huami.com,'
+ 'api-analytics.huami.com,api-mifit.huami.com',
+ 'app_version': '4.3.0-play',
+ 'source': 'com.huami.watch.hmwatchmanager',
+ 'country_code': None,
+ 'device_id': None,
+ 'third_name': None,
+ 'lang': 'en',
+ 'device_model': 'android_phone',
+ 'allow_registration': 'false',
+ 'app_name': 'com.huami.midong',
+ 'code': None,
+ 'grant_type': None
+ },
+ 'devices': {
+ 'apptoken': None
+ },
+ 'agps': {
+ 'apptoken': None
+ },
+ 'data_short': {
+ 'apptoken': None,
+ 'startDay': None,
+ 'endDay': None
+ },
+ 'logout': {
+ 'login_token': None
+ },
+}
\ No newline at end of file