mirror of
https://codeberg.org/vanous/huafetcher.git
synced 2025-01-10 07:02:03 +01:00
initial commit
This commit is contained in:
parent
fb57ce94fe
commit
7b449719f4
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.buildozer
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
23
README.md
23
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
|
||||
|
||||
|
BIN
data/icon.png
Normal file
BIN
data/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
80
data/icon.svg
Normal file
80
data/icon.svg
Normal file
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 24 24"
|
||||
fill="white"
|
||||
width="18px"
|
||||
height="18px"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="icon.svg"
|
||||
inkscape:export-filename="/tmp/icon.png"
|
||||
inkscape:export-xdpi="2730.6699"
|
||||
inkscape:export-ydpi="2730.6699"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1042"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="40.833333"
|
||||
inkscape:cx="9"
|
||||
inkscape:cy="9"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<path
|
||||
d="M0 0h24v24H0V0z"
|
||||
fill="none"
|
||||
id="path2"
|
||||
inkscape:export-filename="/tmp/icon.png"
|
||||
inkscape:export-xdpi="2730.6699"
|
||||
inkscape:export-ydpi="2730.6699" />
|
||||
<path
|
||||
d="M14.31 2l.41 2.48C13.87 4.17 12.96 4 12 4c-.95 0-1.87.17-2.71.47L9.7 2h4.61m.41 17.52L14.31 22H9.7l-.41-2.47c.84.3 1.76.47 2.71.47.96 0 1.87-.17 2.72-.48M16 0H8l-.95 5.73C5.19 7.19 4 9.45 4 12s1.19 4.81 3.05 6.27L8 24h8l.96-5.73C18.81 16.81 20 14.54 20 12s-1.19-4.81-3.04-6.27L16 0zm-4 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z"
|
||||
id="path4"
|
||||
inkscape:export-filename="/tmp/icon.png"
|
||||
inkscape:export-xdpi="2730.6699"
|
||||
inkscape:export-ydpi="2730.6699"
|
||||
style="stroke:#000000;stroke-opacity:1;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8.88889px;line-height:124%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;fill:#fffffc;fill-opacity:1;stroke:#000000;stroke-width:0.26666667;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
x="7.1302075"
|
||||
y="15.376737"
|
||||
id="text839"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan837"
|
||||
x="7.1302075"
|
||||
y="15.376737"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8.88889px;font-family:Sans;-inkscape-font-specification:Sans;fill:#fffffc;fill-opacity:1;stroke-width:0.26666667;stroke:#000000;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none">hf</tspan></text>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
193
huami_token.py
Normal file
193
huami_token.py
Normal file
@ -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.")
|
||||
|
||||
|
394
main.py
Normal file
394
main.py
Normal file
@ -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('''
|
||||
<MyInputy>:
|
||||
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)
|
||||
|
||||
|
||||
<MyLabel>:
|
||||
font_size: 30
|
||||
halign: 'center'
|
||||
bcolor: .7, .7, .7, 1
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: root.bcolor
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
<MyLeftLabel>:
|
||||
font_size: 30
|
||||
halign: 'left'
|
||||
bcolor: .7, .7, .7, 1
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: root.bcolor
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
<MyButton>:
|
||||
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()
|
||||
|
221
requirements.txt
Normal file
221
requirements.txt
Normal file
@ -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
|
53
urls.py
Normal file
53
urls.py
Normal file
@ -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
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue
Block a user