From 7b449719f415f04b6b4e30a1e5f1aeda758ddcdc Mon Sep 17 00:00:00 2001 From: vanous Date: Tue, 24 Nov 2020 23:12:25 +0100 Subject: [PATCH] initial commit --- .gitignore | 1 + README.md | 23 +++ data/icon.png | Bin 0 -> 28634 bytes data/icon.svg | 80 ++++++++++ huami_token.py | 193 +++++++++++++++++++++++ main.py | 394 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 221 ++++++++++++++++++++++++++ urls.py | 53 +++++++ 8 files changed, 965 insertions(+) create mode 100644 data/icon.png create mode 100644 data/icon.svg create mode 100644 huami_token.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 urls.py 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 0000000000000000000000000000000000000000..0c74ddc1a2053c788c188725862b4878cebe10b8 GIT binary patch literal 28634 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 Q0RTW%Srb|Q!0Pq?0V800#Q*>R literal 0 HcmV?d00001 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 @@ + + + + + + image/svg+xml + + + + + + + + + + hf + 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