├── README.md ├── ota.ini ├── user.ini ├── xiaoai-cookie.exe ├── xiaoai-ota.exe └── 源代码 ├── user.ini ├── xiaoai-cookie.py └── xiaoai-ota.py /README.md: -------------------------------------------------------------------------------- 1 | # 小爱音箱降级 # 2 | 3 | ## # 前言 4 | 5 | ##### 目前程序只适用此版本(见下图)。降级请勿大版本降级,如15 降到12,小米关闭了部分接口,可能导致小爱变砖。 6 | 7 | ![产品](https://p3.toutiaoimg.com/origin/2b28e00034d22d5c3cb25 "产品") 8 | 9 | 10 | ## #1 配置user.ini 文件 ## 11 | 12 | user 对应你的小米账号 13 | pwd 对应你的密码 14 | 15 | 配置前请确认你user.ini 是否存在,如不存在则无法继续下一步。 16 | 17 | ## #2 运行xiaoai-cookie.exe ## 18 | 19 | **进入文件所在的文件夹下,选择空白处,然后按住 shift + 鼠标右键,选择“在此处打开powershell 窗口”** 20 | 21 | 运行 ```./xiaoai-cookie.exe``` 22 | 23 | 一般来说,一次运行即可,如果不在常用地登录,可能会出现验证码或其他无法登录情况。 24 | 这个功能目前还没设计,暂时不做开发,留待后续填坑。 25 | 26 | 运行完成后,会在同一路径下生成一个 ``xiaoai-res.txt `` 文件,一般包含这些信息 27 | 28 | ``` 29 | -------账号参数------- 30 | serviceToken:sXEJMjSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCP807vgDIiGmi8o+c3ORITf57CvDWs9JBW2Zodvd8EaoqrSWKNe3c1z/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx== 31 | userId:10100000x 32 | cUserId:52Dxxxxxxxxxxx 33 | -------设备参数------- 34 | deviceID:xxxx50-e4e5-xxxx-xxxx-57faef904889 35 | sn:xxxx/8823xxx 36 | ``` 37 | 38 | 如果你家里有多台小爱同学,可能设备会参数有多个,请通过确认SN参数,确认你要降级的设备。 39 | 40 | ## #3 配置 ota.ini 文件 ## 41 | 42 | 打开 ` xiaoai-res.txt ` 文件,将对应参数一一复制到 ` ota.ini ` 里,注意serviceToken 参数后的等号要保留。 43 | 44 | 然后运行 ` ./xiaoai-ota.exe `,如果返回以下内容则表示成功 45 | 46 | ``` 47 | { 48 | "code":0,"message":"Success","data":"" 49 | } 50 | ``` 51 | 52 | 其他内容则表示需要检测上述步骤。 53 | 54 | ## # 固件可选版本 55 | 对应 `` ota.ini `` 文件 ``version`` 参数 56 | 57 | 58 | - mico_all_f86a5_1.44.4.bin 59 | - mico_all_c731c_1.52.1.bin 60 | - mico_all_9d15e_1.54.13.bin 61 | -------------------------------------------------------------------------------- /ota.ini: -------------------------------------------------------------------------------- 1 | { 2 | "userId": "1018xxxx", 3 | "cUserId": "52Dxxxxxxxxxx", 4 | "deviceId": "xxxxxxxx-xxxx-xxxx-a147-xxxxxxxxxxxxx", 5 | "serviceToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/YOKPKE95mlY93Z6bd2Etwq1eRF5dSkKQrzqq9lmIc2a1e1nm8Cif+CR+xxxxxxxxxxxxxxxxx/lIXPIJu1XYJKf8KtnMWxu7D+xxxxxxxxxxxx==", 6 | "sn": "xxxx/xxxxxxxx", 7 | "version": "mico_all_f86a5_1.44.4.bin" 8 | } -------------------------------------------------------------------------------- /user.ini: -------------------------------------------------------------------------------- 1 | { 2 | "user":"账号", 3 | "pwd":"密码" 4 | } 5 | -------------------------------------------------------------------------------- /xiaoai-cookie.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeLingQi/xiaoai/b0062fe92ca20fa7460fc9ba7f573765bb9aff68/xiaoai-cookie.exe -------------------------------------------------------------------------------- /xiaoai-ota.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeLingQi/xiaoai/b0062fe92ca20fa7460fc9ba7f573765bb9aff68/xiaoai-ota.exe -------------------------------------------------------------------------------- /源代码/user.ini: -------------------------------------------------------------------------------- 1 | { 2 | "user":"账号", 3 | "pwd":"密码" 4 | } 5 | -------------------------------------------------------------------------------- /源代码/xiaoai-cookie.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | import sys 4 | import hashlib 5 | import json 6 | import urllib 7 | import base64 8 | import os 9 | import time 10 | 11 | 12 | # MD5 混淆 13 | def MD5(str): 14 | hl = hashlib.md5() 15 | hl.update(str.encode(encoding='utf-8')) 16 | return hl.hexdigest() 17 | 18 | 19 | # 登录 20 | def loginByAccount(user, pwd): 21 | if len(user) == 0 or len(pwd) == 0: 22 | print('请输入账号或密码') 23 | sys.exit() 24 | sign = getLoginSign() 25 | time.sleep(3) 26 | authInfo = serviceAuth(sign, user, pwd) 27 | if authInfo['code'] != 0: 28 | print(authInfo['desc']) 29 | sys.exit() 30 | time.sleep(3) 31 | resp = loginMiAi(authInfo) 32 | return {'serviceToken': resp['serviceToken'], 'userId': resp['userId'], 'cUserId': authInfo['cUserId']} 33 | 34 | 35 | 36 | # 获取登录签名 37 | def getLoginSign(): 38 | headers = { 39 | "Content-Type": "application/json; charset=utf-8", 40 | "Connection": "keep-alive", 41 | "Accept-Language": "zh-cn", 42 | "User-Agent": MINA_UA, 43 | } 44 | try: 45 | resp = requests.get(API['SERVICE_LOGIN'], 46 | params=COMMON_PARAM, headers=headers, timeout=times) 47 | result = (resp.text).replace('&&&START&&&', '') 48 | info = json.loads(result) 49 | return {'_sign': info['_sign'], 'qs': info['qs']} 50 | except Exception as e: 51 | print('getLoginSign {}'.format(e)) 52 | sys.exit() 53 | 54 | 55 | # 授权 56 | def serviceAuth(signData, user, pwd): 57 | data = { 58 | 'user': user, 59 | 'hash': MD5(pwd).upper(), 60 | 'callback': 'https://api.mina.mi.com/sts', 61 | 'sid': COMMON_PARAM['sid'], 62 | '_json': COMMON_PARAM['_json'], 63 | 'serviceParam': '{"checkSafePhone":false}', 64 | 'qs': signData['qs'], 65 | '_sign': signData['_sign'], 66 | } 67 | headers = { 68 | "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", 69 | "Connection": "keep-alive", 70 | "Accept-Language": "zh-cn", 71 | "User-Agent": MINA_UA, 72 | 'Cookie': "deviceId="+APP_DEVICE_ID+";sdkVersion="+SDK_VER, 73 | } 74 | 75 | try: 76 | resp = requests.post(API['SERVICE_AUTH'], params=data, 77 | headers=headers, timeout=times) 78 | result = (resp.text).replace('&&&START&&&', '') 79 | authInfo = json.loads(result) 80 | return authInfo 81 | except Exception as e: 82 | print('serviceAuth {}'.format(e)) 83 | sys.exit() 84 | 85 | 86 | # 登录小米音箱 87 | def loginMiAi(authInfo): 88 | clientSign = genClientSign(authInfo['nonce'], authInfo['ssecurity']) 89 | headers = { 90 | 'User-Agent': APP_UA, 91 | 'Accept-Language': 'zh-cn', 92 | 'Connection': 'keep-alive' 93 | } 94 | url = authInfo['location']+"&clientSign=" + \ 95 | urllib.parse.quote(clientSign.decode()) 96 | try: 97 | resp = requests.get(url, headers=headers, timeout=times) 98 | if resp.status_code == 200: 99 | cookies = resp.cookies.get_dict() 100 | return {'userId': cookies['userId'], 'serviceToken': cookies['serviceToken']} 101 | else: 102 | return False 103 | except Exception as e: 104 | print('出错了,loginMiAi {}'.format(e)) 105 | sys.exit() 106 | 107 | 108 | # 签名 109 | def genClientSign(nonce, secrity): 110 | tempStr = "nonce={}&{}".format(nonce, secrity) 111 | hashStr = hashlib.sha1(tempStr.encode('utf-8')).digest() 112 | return base64.b64encode(hashStr) 113 | 114 | 115 | # 获取设备 116 | def getDevice(serviceToken, userId): 117 | headers = { 118 | 'User-Agent': APP_UA, 119 | 'Accept-Language': 'zh-cn', 120 | 'Connection': 'keep-alive', 121 | 'Cookie': 'userId={};serviceToken={}'.format(userId, serviceToken) 122 | } 123 | 124 | try: 125 | resp = requests.get(API['DEVICE_LIST'], headers=headers, timeout=times) 126 | if resp.status_code != 200: 127 | return False 128 | else: 129 | resDist = json.loads(resp.text, encoding="UTF-8") 130 | if resDist['code'] != 0: 131 | print(resDist['message']) 132 | sys.exit() 133 | else: 134 | return resDist['data'] 135 | except Exception as e: 136 | print('getDevice {}'.format(e)) 137 | sys.exit() 138 | 139 | 140 | COMMON_PARAM = { 141 | 'sid': 'micoapi', 142 | '_json': 'true' 143 | } 144 | APP_DEVICE_ID = '3C861A5820190429' 145 | SDK_VER = '3.4.1' 146 | APP_UA = 'APP/com.xiaomi.mico APPV/2.1.17 iosPassportSDK/3.4.1 iOS/13.5' 147 | MINA_UA = 'MISoundBox/2.1.17 (com.xiaomi.mico; build:2.1.55; iOS 13.5) Alamofire/4.8.2 MICO/iOSApp/appStore/2.1.17' 148 | API = { 149 | 'USBS': 'https://api.mina.mi.com/remote/ubus', 150 | 'SERVICE_AUTH': 'https://account.xiaomi.com/pass/serviceLoginAuth2', 151 | 'SERVICE_LOGIN': 'https://account.xiaomi.com/pass/serviceLogin', 152 | 'DEVICE_LIST': 'https://api.mina.mi.com/admin/v2/device_list', 153 | } 154 | 155 | times = 20 156 | if __name__ == "__main__": 157 | path = os.path.dirname(os.path.abspath(__file__)) 158 | if os.path.exists(path+'/user.ini') is False: 159 | print('账号配置文件不存在,请确认') 160 | sys.exit() 161 | userFile = open(path+'/user.ini', 'r', encoding='UTF-8').read() 162 | array = json.loads(userFile) 163 | print('登录中……') 164 | res = loginByAccount(array['user'], array['pwd']) 165 | if res is None: 166 | print('登录失败') 167 | sys.exit() 168 | else: 169 | print('登录成功') 170 | time.sleep(5) 171 | f = open(path+'/xiaoai-res.txt', 'a', encoding="UTF-8") 172 | f.write('-------账号参数-------\n\n') 173 | content = "serviceToken:{}\nuserId:{}\ncUserId:{} \n".format( 174 | res['serviceToken'], res['userId'], res['cUserId']) 175 | print(content) 176 | f.write(content) 177 | f.write('-------设备参数-------\n\n') 178 | print('获取设备中……') 179 | devices = getDevice(res['serviceToken'], res['userId']) 180 | if isinstance(devices, list) is False: 181 | print('你没有小爱同学') 182 | sys.exit() 183 | for device in devices: 184 | deviceStr = "deviceID:{}\nsn:{} \n".format( 185 | device['deviceID'], device['serialNumber']) 186 | print(deviceStr) 187 | f.write(deviceStr) 188 | -------------------------------------------------------------------------------- /源代码/xiaoai-ota.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | import urllib 4 | import time 5 | import os 6 | import sys 7 | import json 8 | 9 | 10 | def uniqid(prefix=''): 11 | return prefix + hex(int(time.time()))[2:10] + hex(int(time.time() * 1000000) % 0x100000)[2:7] 12 | 13 | 14 | api = 'https://bigota.miwifi.com/xiaoqiang/rom/s12a/' 15 | # 自行替换低版本文件地址 16 | path = os.path.dirname(os.path.abspath(__file__)) 17 | if os.path.exists(path + '/user.ini') is False: 18 | print('账号配置文件不存在,请确认') 19 | sys.exit() 20 | userFile = open(path + '/ota.ini', 'r', encoding='UTF-8').read() 21 | array = json.loads(userFile) 22 | if isinstance(array, dict) is False: 23 | print('参数配置错误,请检查') 24 | sys.exit() 25 | else: 26 | userId = array['userId'] 27 | serviceToken = array['serviceToken'] 28 | cUserId = array['cUserId'] 29 | deviceId = array['deviceId'] 30 | sn = array['sn'] 31 | version = array['version'] 32 | if not all([userId, serviceToken, cUserId, cUserId, deviceId, sn, version]): 33 | print('参数配置不完整,请确认') 34 | sys.exit() 35 | 36 | headers = { 37 | "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", 38 | "Connection": "keep-alive", 39 | "Cookie": "userId=" + userId + ";serviceToken=" + serviceToken + ";cUserId=" + cUserId + ";deviceId=" + deviceId + ";sn=" + sn, 40 | "Accept-Language": "zh-cn", 41 | "User-Agent": "MiSoundBox/2.0.41 CFNetwork/978.0.7 Darwin/18.5.0", 42 | "Pragma": "no-cache", 43 | "Cache-Control": "no-cache" 44 | } 45 | bodyData = { 46 | "checksum": "00c355fbae2104aa6051aa34893f86a5", 47 | "deviceId": deviceId, 48 | "extra": '{"cfe":1000002,"linux":1,"rootfs":1,"weight":1,"sqafs":1,"ramfs":1}', 49 | "hardware": "S12A", 50 | "requestId": uniqid(), 51 | "url": api + version, 52 | "version": "1.44.4" 53 | } 54 | body = urllib.parse.urlencode(bodyData) 55 | url = "https://api.mina.mi.com/remote/ota/v2" 56 | try: 57 | response = requests.post(url, data=body, headers=headers) 58 | print(response.text) 59 | except Exception as e: 60 | print(e) 61 | --------------------------------------------------------------------------------