commit 008f8d55e06585ba9f80dbd6a64ed74020c6601c Author: 编码猿 Date: Fri Sep 27 01:02:47 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..403adbc --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules +/dist + + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.pydio b/.pydio new file mode 100644 index 0000000..cfe9645 --- /dev/null +++ b/.pydio @@ -0,0 +1 @@ +72b23aef-badd-4a65-8020-b36e0310c36c \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..99d4d73 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "token_extractor", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..54fa9ea --- /dev/null +++ b/test.py @@ -0,0 +1,40 @@ +# 控制米家吸顶灯 +from miio.yeelight import Yeelight +ip='192.168.31.24' +token='9264fdba5c6225ea4df82641c17bcb0d' +s = Yeelight(ip=ip, token=token) +print(s.on()) +print(s.set_brightness(10)) + + +# 控制窗帘 +# from miio.curtain_youpin import CurtainMiot, CurtainStatus +# +# ip='192.168.31.69' +# token='3da5703ea963b4b83c8a7a9213284ce9' +# x = CurtainMiot(ip=ip, token=token) +# print(x.info()) +# # 设置窗帘打开的百分比 +# print(x.set_target_position(100)) + + +# 控制台灯 +# from miio.philips_rwread import PhilipsRwread +# ip='192.168.31.250' +# token='a806248c4a754aa1af8d101ac0c742ca' +# x = PhilipsRwread(ip=ip, token=token) +# # print(x.send_handshake()) +# print(x.set_brightness(80)) + + + +# from miio.chuangmi_plug import ChuangmiPlug +# +# ip='192.168.31.24' +# token='9264fdba5c6225ea4df82641c17bcb0d' +# d = ChuangmiPlug(ip=ip, token=token) +# +# print(d.status()) +# # print(d.info()) +# d.off() # 关闭书房灯 +# # d.on() # 打开书房灯 \ No newline at end of file diff --git a/token_extractor.py b/token_extractor.py new file mode 100644 index 0000000..b71053c --- /dev/null +++ b/token_extractor.py @@ -0,0 +1,253 @@ +import base64 +import hashlib +import hmac +import json +import os +import random +import time +from sys import platform + +import requests + +if platform != "win32": + import readline + + +class XiaomiCloudConnector: + + def __init__(self, username, password): + self._username = username + self._password = password + self._agent = self.generate_agent() + self._device_id = self.generate_device_id() + self._session = requests.session() + self._sign = None + self._ssecurity = None + self._userId = None + self._cUserId = None + self._passToken = None + self._location = None + self._code = None + self._serviceToken = None + + def login_step_1(self): + url = "https://account.xiaomi.com/pass/serviceLogin?sid=xiaomiio&_json=true" + headers = { + "User-Agent": self._agent, + "Content-Type": "application/x-www-form-urlencoded" + } + cookies = { + "userId": self._username + } + response = self._session.get(url, headers=headers, cookies=cookies) + valid = response.status_code == 200 and "_sign" in self.to_json(response.text) + if valid: + self._sign = self.to_json(response.text)["_sign"] + return valid + + def login_step_2(self): + url = "https://account.xiaomi.com/pass/serviceLoginAuth2" + headers = { + "User-Agent": self._agent, + "Content-Type": "application/x-www-form-urlencoded" + } + fields = { + "sid": "xiaomiio", + "hash": hashlib.md5(str.encode(self._password)).hexdigest().upper(), + "callback": "https://sts.api.io.mi.com/sts", + "qs": "%3Fsid%3Dxiaomiio%26_json%3Dtrue", + "user": self._username, + "_sign": self._sign, + "_json": "true" + } + response = self._session.post(url, headers=headers, params=fields) + valid = response is not None and response.status_code == 200 + if valid: + json_resp = self.to_json(response.text) + valid = "ssecurity" in json_resp and len(str(json_resp["ssecurity"])) > 4 + if valid: + self._ssecurity = json_resp["ssecurity"] + self._userId = json_resp["userId"] + self._cUserId = json_resp["cUserId"] + self._passToken = json_resp["passToken"] + self._location = json_resp["location"] + self._code = json_resp["code"] + else: + if "notificationUrl" in json_resp: + print("Two factor authentication required, please use following url and restart extractor:") + print(json_resp["notificationUrl"]) + print() + return valid + + def login_step_3(self): + headers = { + "User-Agent": self._agent, + "Content-Type": "application/x-www-form-urlencoded" + } + response = self._session.get(self._location, headers=headers) + if response.status_code == 200: + self._serviceToken = response.cookies.get("serviceToken") + return response.status_code == 200 + + def login(self): + self._session.cookies.set("sdkVersion", "accountsdk-18.8.15", domain="mi.com") + self._session.cookies.set("sdkVersion", "accountsdk-18.8.15", domain="xiaomi.com") + self._session.cookies.set("deviceId", self._device_id, domain="mi.com") + self._session.cookies.set("deviceId", self._device_id, domain="xiaomi.com") + if self.login_step_1(): + if self.login_step_2(): + if self.login_step_3(): + return True + else: + print("Unable to get service token.") + else: + print("Invalid login or password.") + else: + print("Invalid username.") + return False + + def get_devices(self, country): + url = self.get_api_url(country) + "/home/device_list" + params = { + "data": '{"getVirtualModel":false,"getHuamiDevices":0}' + } + return self.execute_api_call(url, params) + + def get_beaconkey(self, country, did): + url = self.get_api_url(country) + "/v2/device/blt_get_beaconkey" + params = { + "data": '{"did":"' + did + '","pdid":1}' + } + return self.execute_api_call(url, params) + + def execute_api_call(self, url, params): + headers = { + "Accept-Encoding": "gzip", + "User-Agent": self._agent, + "Content-Type": "application/x-www-form-urlencoded", + "x-xiaomi-protocal-flag-cli": "PROTOCAL-HTTP2" + } + cookies = { + "userId": str(self._userId), + "yetAnotherServiceToken": str(self._serviceToken), + "serviceToken": str(self._serviceToken), + "locale": "en_GB", + "timezone": "GMT+02:00", + "is_daylight": "1", + "dst_offset": "3600000", + "channel": "MI_APP_STORE" + } + millis = round(time.time() * 1000) + nonce = self.generate_nonce(millis) + signed_nonce = self.signed_nonce(nonce) + signature = self.generate_signature(url.replace("/app", ""), signed_nonce, nonce, params) + fields = { + "signature": signature, + "_nonce": nonce, + "data": params["data"] + } + response = self._session.post(url, headers=headers, cookies=cookies, params=fields) + if response.status_code == 200: + return response.json() + return None + + def get_api_url(self, country): + return "https://" + ("" if country == "cn" else (country + ".")) + "api.io.mi.com/app" + + def signed_nonce(self, nonce): + hash_object = hashlib.sha256(base64.b64decode(self._ssecurity) + base64.b64decode(nonce)) + return base64.b64encode(hash_object.digest()).decode('utf-8') + + @staticmethod + def generate_nonce(millis): + nonce_bytes = os.urandom(8) + (int(millis / 60000)).to_bytes(4, byteorder='big') + return base64.b64encode(nonce_bytes).decode() + + @staticmethod + def generate_agent(): + agent_id = "".join(map(lambda i: chr(i), [random.randint(65, 69) for _ in range(13)])) + return f"Android-7.1.1-1.0.0-ONEPLUS A3010-136-{agent_id} APP/xiaomi.smarthome APPV/62830" + + @staticmethod + def generate_device_id(): + return "".join(map(lambda i: chr(i), [random.randint(97, 122) for _ in range(6)])) + + @staticmethod + def generate_signature(url, signed_nonce, nonce, params): + signature_params = [url.split("com")[1], signed_nonce, nonce] + for k, v in params.items(): + signature_params.append(f"{k}={v}") + signature_string = "&".join(signature_params) + signature = hmac.new(base64.b64decode(signed_nonce), msg=signature_string.encode(), digestmod=hashlib.sha256) + return base64.b64encode(signature.digest()).decode() + + @staticmethod + def to_json(response_text): + return json.loads(response_text.replace("&&&START&&&", "")) + + +def print_tabbed(value, tab): + print(" " * tab + value) + + +def print_entry(key, value, tab): + if value: + print_tabbed(f'{key + ":": <10}{value}', tab) + + +servers = ["cn", "de", "us", "ru", "tw", "sg", "in", "i2"] +servers_str = ", ".join(servers) +print("Username (email or user ID):") +username = input() +print("Password:") +password = input() +print(f"Server (one of: {servers_str}) Leave empty to check all available:") +server = input() +while server not in ["", *servers]: + print(f"Invalid server provided. Valid values: {servers_str}") + print("Server:") + server = input() + +print() +if not server == "": + servers = [server] + +connector = XiaomiCloudConnector(username, password) +print("Logging in...") +logged = connector.login() +if logged: + print("Logged in.") + print() + for current_server in servers: + devices = connector.get_devices(current_server) + if devices is not None: + if len(devices["result"]["list"]) == 0: + print(f"No devices found for server \"{current_server}\".") + continue + print(f"Devices found for server \"{current_server}\":") + for device in devices["result"]["list"]: + print_tabbed("---------", 3) + if "name" in device: + print_entry("NAME", device["name"], 3) + if "did" in device: + print_entry("ID", device["did"], 3) + if "blt" in device["did"]: + beaconkey = connector.get_beaconkey(current_server, device["did"]) + if beaconkey and "result" in beaconkey and "beaconkey" in beaconkey["result"]: + print_entry("BLE KEY", beaconkey["result"]["beaconkey"], 3) + if "localip" in device: + print_entry("IP", device["localip"], 3) + if "token" in device: + print_entry("TOKEN", device["token"], 3) + if "model" in device: + print_entry("MODEL", device["model"], 3) + print_tabbed("---------", 3) + print() + else: + print("Unable to get devices.") +else: + print("Unable to log in.") + +print() +print("Press ENTER to finish") +input() diff --git a/笔记.txt b/笔记.txt new file mode 100644 index 0000000..b2acd4b --- /dev/null +++ b/笔记.txt @@ -0,0 +1,172 @@ + --------- + NAME: 台灯 + ID: 244045663 + IP: 192.168.31.250 + TOKEN: a806248c4a754aa1af8d101ac0c742ca + MODEL: philips.light.sread3 + --------- + NAME: 客厅灯 + ID: 317886303 + IP: 192.168.31.140 + TOKEN: ca897b4739938a9f03743ad825fb6980 + MODEL: yeelink.light.ceiling21 + --------- + NAME: 书房灯 + ID: 360170570 + IP: 192.168.31.24 + TOKEN: 9264fdba5c6225ea4df82641c17bcb0d + MODEL: yeelink.light.ceiling23 + --------- + NAME: 卧室灯 + ID: 393790814 + IP: 192.168.31.251 + TOKEN: fe3f0b13f767f864c58516da753e3eec + MODEL: yeelink.light.ceiling22 + --------- + NAME: 书房夜灯 + ID: blt.3.16hdeevig5c00 + BLE KEY: 6d047ee3eb4e14fe7b8141300c3de696 + TOKEN: f814990797b78ecf5a90b533 + MODEL: yeelink.light.nl1 + --------- + NAME: 过道夜灯 + ID: blt.3.16hdgfe1s5o00 + BLE KEY: 1811a5b2645adf0a296c5a20f990b2a1 + TOKEN: 7c68ebc0252ae1e0cf5eeb9b + MODEL: yeelink.light.nl1 + --------- + NAME: 路由器 + ID: miwifi.71459f2f-4990-8254-5edc-8968cb80c70b + TOKEN: Ybsitd+7vNu1ewTb79oNRAFhH0F4m7BgeJMJydOb8f8= + MODEL: xiaomi.router.rm1800 + --------- + NAME: 客厅环境灯 + ID: 1021927010 + TOKEN: 09acebbf30774781c42639dfba0da6bb + MODEL: zimi.switch.dhkg02 + --------- + NAME: 小乖 + ID: 1023017928 + IP: 192.168.31.56 + TOKEN: 4a5631494371736f734c696e4b497834 + MODEL: dreame.vacuum.p2041 + --------- + NAME: 厕所灯 + ID: 1023534550 + IP: 112.32.24.238 + TOKEN: ab60e7852342bb2fa2878435c4da0ad0 + MODEL: zimi.switch.dhkg01 + --------- + NAME: 背景墙 + ID: 1023583056 + TOKEN: c28c082025f113fd5ecc8707e0475978 + MODEL: zimi.switch.dhkg02 + --------- + NAME: 过道环境灯 + ID: 1023977850 + IP: 112.32.22.196 + TOKEN: 8a3c00326bdd316d0f961cae1b028fe9 + MODEL: zimi.switch.dhkg02 + --------- + NAME: 摄像头 + ID: 1024903226 + IP: 192.168.31.104 + TOKEN: 726853767149764f663836745665666a + MODEL: isa.camera.hlc7 + --------- + NAME: 卧室环境灯 + ID: 1043617831 + TOKEN: 02a994f896ccee5ae9b46bcef83f031f + MODEL: isa.switch.kg03hl + --------- + NAME: 四面八方 + ID: 247969539 + IP: 192.168.31.178 + TOKEN: b1c57de5fbc8badf5594c5c84f7c187f + MODEL: midjd6.fridge.v1 + --------- + NAME: 洗碗机 + ID: 248198501 + IP: 192.168.31.15 + TOKEN: 23a9e26128dfac7107b68f5baca1ded1 + MODEL: viomi.dishwasher.m02 + --------- + NAME: 窗帘 + ID: 358904559 + IP: 192.168.31.69 + TOKEN: 3da5703ea963b4b83c8a7a9213284ce9 + MODEL: lumi.curtain.hmcn01 + --------- + NAME: 卧室窗帘 + ID: 359677862 + IP: 192.168.31.7 + TOKEN: 057968299e569f032f4cecd0129fcefd + MODEL: lumi.curtain.hmcn01 + --------- + NAME: 甩甩 + ID: 360186790 + IP: 192.168.31.107 + TOKEN: ada7f667f5b1f579715f9a4277eb3d83 + MODEL: minij.washer.v11 + --------- + NAME: 饭饭 + ID: 392783495 + IP: 192.168.31.151 + TOKEN: 824c2955bdcf54755715b7176713fa02 + MODEL: chunmi.cooker.normalcd2 + --------- + NAME: 小爱音箱 + ID: 425451131 + IP: 192.168.31.135 + TOKEN: 4431684a4539775a624c5a446d58784b + MODEL: xiaomi.wifispeaker.lx06 + --------- + NAME: 风扇 + ID: 426805939 + IP: 192.168.31.112 + TOKEN: cb141f1a71c2c1693a11482ac2d8fc9c + MODEL: dmaker.fan.1e + --------- + NAME: 小爱音箱2 + ID: 445641071 + IP: 192.168.31.9 + TOKEN: 6a6568617734576e533676693362326c + MODEL: xiaomi.wifispeaker.l7a + --------- + NAME: 牙刷1 + ID: blt.3.16i6kptvolk00 + BLE KEY: fb69fe3670def89ac078baacFFFFFFFF + TOKEN: 7065a93b675bdcd3767c02c6 + MODEL: soocare.toothbrush.m1s + --------- + NAME: 牙刷2 + ID: blt.3.16i6l6cjclk00 + BLE KEY: 42e91c130688d510f23b9c6aFFFFFFFF + TOKEN: 86f496b48e80491d439d88fe + MODEL: soocare.toothbrush.m1s + --------- + NAME: 大门传感器 + ID: blt.3.16o8olgdsls00 + BLE KEY: fa7272f3320bd39ad37641f4a0a364cb + IP: 39.144.34.159 + TOKEN: a50f44781545ed0c0a6295e9 + MODEL: isa.magnet.dw2hl + --------- + NAME: 卫生间传感器 + ID: blt.3.17eqndfv45k01 + BLE KEY: f9a1b4cc01fb4aac715b2b22a3d6e3e6 + IP: 39.144.34.159 + TOKEN: 2673fd6035c2579717f4d0b8 + MODEL: lumi.motion.bmgl01 + --------- + NAME: 空调 + ID: ir.1427929297838354432 + MODEL: miir.aircondition.ir02 + --------- + NAME: 电视 + ID: ir.1429160518148894720 + MODEL: miir.tvbox.ir01 + --------- + + + \ No newline at end of file