commit fa5cd5e33780fe71540b5ca3688b53491d0c6041 Author: kunmeng <811096647@qq.com> Date: Sat Apr 27 22:50:40 2024 +0800 First submission diff --git a/api/getDevices.go b/api/getDevices.go new file mode 100644 index 0000000..25e6924 --- /dev/null +++ b/api/getDevices.go @@ -0,0 +1,142 @@ +package api + +import ( + "encoding/json" + "strconv" +) + +type MiJia struct { + Certificate Authorize + HomeData Home +} + +func NewMiJia(Certificate Authorize) MiJia { + return MiJia{ + Certificate: Certificate, + } +} + +// 获取设备列表 +func (mi *MiJia) Devices() { + data := map[string]interface{}{ + "getVirtualModel": false, + "getHuamiDevices": 0, + } + PostData("/home/device_list", data, mi.Certificate) +} + +func (mi *MiJia) Rooms() { + data := map[string]interface{}{ + "fg": false, + "fetch_share": true, + "fetch_share_dev": true, + "limit": 300, + "app_ver": 7, + } + jsonData := PostData("/v2/homeroom/gethome", data, mi.Certificate) + json.Unmarshal(jsonData, &mi.HomeData) +} + +// 获取场景列表 +func (mi *MiJia) Scenes(roomIdx int) { + HomeId := mi.HomeData.Result.Homelist[roomIdx].Id + data := map[string]interface{}{ + "home_id": HomeId, + } + PostData("/appgateway/miot/appsceneservice/AppSceneService/GetSceneList", data, mi.Certificate) +} + +// 根据房间获取耗材列表 +func (mi *MiJia) Consumables(roomIdx int) { + HomeId := mi.HomeData.Result.Homelist[roomIdx].Id + Homei, err := strconv.Atoi(HomeId) + if err != nil { + return + } + data := map[string]interface{}{ + "home_id": Homei, + "owner_id": mi.Certificate.UserId, + } + PostData("/v2/home/standard_consumable_items", data, mi.Certificate) +} + +func (mi *MiJia) RunScene(SceneId string) { + data := map[string]interface{}{ + "scene_id": SceneId, + "trigger_key": "user.click", + } + PostData("/appgateway/miot/appsceneservice/AppSceneService/RunScene", data, mi.Certificate) +} + +type HomeList struct { + Id string `json:"id"` + Name string `json:"name"` + Bssid string `json:"bssid"` + Dids []string `json:"dids"` + TempDids interface{} `json:"temp_dids"` + Icon string `json:"icon"` + Shareflag int `json:"shareflag"` + PermitLevel int `json:"permit_level"` + Status int `json:"status"` + Background string `json:"background"` + SmartRoomBackground string `json:"smart_room_background"` + Longitude float64 `json:"longitude"` + Latitude float64 `json:"latitude"` + CityId int `json:"city_id"` + Address string `json:"address"` + CreateTime int `json:"create_time"` + Roomlist []struct { + Id string `json:"id"` + Name string `json:"name"` + Bssid string `json:"bssid"` + Parentid string `json:"parentid"` + Dids []string `json:"dids"` + Icon string `json:"icon"` + Background string `json:"background"` + Shareflag int `json:"shareflag"` + CreateTime int `json:"create_time"` + } + Uid int64 `json:"uid"` + AppearHomeList interface{} `json:"appear_home_list"` + PopupFlag int `json:"popup_flag"` + PopupTimeStamp int `json:"popup_time_stamp"` + CarDid string `json:"car_did"` +} + +type Home struct { + Code int `json:"code"` + Message string `json:"message"` + Result struct { + Homelist []HomeList `json:"homelist"` + HasMore bool `json:"has_more"` + MaxId string `json:"max_id"` + } `json:"result"` +} + +func (mi *MiJia) GetDeviceVar(devs []map[string]interface{}) { + //参数说明 + //did: 设备ID + //siid: 功能分类ID + //piid: 设备属性ID + //aiid: 设备方法ID + //从下述网站查询 + //米家产品库,网站不稳定,不知道siid和piid无法使用`getDevAtt`和`setDevAtt` + //一个取巧的办法是使用在米家APP手动设置批量控制,然后使用`runScene` + //https://home.miot-spec.com/ + // + //获取全部设备列表函数`getDevices`返回结果说明 + //返回结果说明 + //name: 设备名称 + //did: 设备ID + //isOnline: 设备是否在线 + //model: 设备产品型号, 根据这个去米家产品库查该产品相关的信息 + // + //获取设备属性,一次可以请求多个 + //Atts = getDevAtt([{"did":"111111111","siid":2,"piid":1},{"did":"111111111","siid":2,"piid":2}, + // {"did":"111111111","siid":2,"piid":3},{"did":"111111111","siid":2,"piid":4}]) + //print(Atts) + data := map[string]interface{}{ + "params": devs, + } + PostData("/miotspec/prop/get", data, mi.Certificate) +} diff --git a/api/login.go b/api/login.go new file mode 100644 index 0000000..33cfe6b --- /dev/null +++ b/api/login.go @@ -0,0 +1,180 @@ +package api + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +func generateRandomDeviceId(size int) string { + tempStr := "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + deviceId := make([]byte, size) + + _, err := rand.Read(deviceId) + if err != nil { + panic(err) + } + + for i := range deviceId { + deviceId[i] = tempStr[deviceId[i]%byte(len(tempStr))] + } + + return string(deviceId) +} + +type Authorize struct { + CUserId string `json:"cUserId"` + Code int `json:"code"` + DeviceId string `json:"deviceId"` + Message string `json:"message"` + SecurityToken string `json:"securityToken"` + ServiceToken string `json:"serviceToken"` + Sid string `json:"sid"` + UserId int64 `json:"userId"` +} + +func mapToStruct(data map[string]interface{}) (Authorize, error) { + jsonStr, err := json.Marshal(data) + if err != nil { + return Authorize{}, err + } + + var myStruct Authorize + err = json.Unmarshal(jsonStr, &myStruct) + if err != nil { + return Authorize{}, err + } + + return myStruct, nil +} + +func Login(user, pwd string) (Authorize, error) { + msgURL := fmt.Sprintf("https://account.xiaomi.com/pass/serviceLogin?sid=xiaomiio&_json=true") + loginURL := "https://account.xiaomi.com/pass/serviceLoginAuth2" + deviceID := generateRandomDeviceId(16) + authorize := make(map[string]interface{}) + userAgent := "APP/com.xiaomi.mihome APPV/6.0.103 iosPassportSDK/3.9.0 iOS/14.4 miHSTS" + client := &http.Client{} + req, _ := http.NewRequest("GET", msgURL, nil) + req.Header.Set("User-Agent", userAgent) + req.Header.Set("Accept", "*/*") + req.Header.Set("Accept-Language", "zh-tw") + req.Header.Set("Cookie", fmt.Sprintf("deviceId=%s; sdkVersion=3.4.1", deviceID)) + resp, _ := client.Do(req) + defer resp.Body.Close() + bodyBytes, _ := io.ReadAll(resp.Body) + var result map[string]interface{} + json.Unmarshal(bodyBytes[11:], &result) + body := url.Values{} + body.Set("qs", result["qs"].(string)) + body.Set("sid", result["sid"].(string)) + body.Set("_sign", result["_sign"].(string)) + body.Set("callback", result["callback"].(string)) + body.Set("user", user) + pwdHash := md5.Sum([]byte(pwd)) + pwdHashStr := strings.ToUpper(hex.EncodeToString(pwdHash[:])) + body.Set("hash", pwdHashStr) + body.Set("_json", "true") + loginReq, _ := http.NewRequest("POST", loginURL, strings.NewReader(body.Encode())) + loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + loginResp, _ := client.Do(loginReq) + defer loginResp.Body.Close() + loginBodyBytes, _ := io.ReadAll(loginResp.Body) + json.Unmarshal(loginBodyBytes[11:], &result) + if result["code"].(float64) != 0 { + authorize["code"] = result["code"] + authorize["message"] = result["desc"] + //return authorize + } + redirectURL := result["location"].(string) + redirectReq, _ := http.NewRequest("GET", redirectURL, nil) + redirectResp, _ := client.Do(redirectReq) + defer redirectResp.Body.Close() + cookies := redirectResp.Header["Set-Cookie"] + for _, cookie := range cookies { + cookieParts := strings.Split(strings.Split(cookie, "; ")[0], "=") + authorize[cookieParts[0]] = cookieParts[1] + } + authorize["code"] = 0 + authorize["sid"] = "xiaomiio" + authorize["userId"] = result["userId"] + authorize["securityToken"] = result["ssecurity"] + authorize["deviceId"] = deviceID + authorize["message"] = "成功" + return mapToStruct(authorize) +} + +func generateSignedNonce(secret, nonce string) string { + hash := sha256.New() + decodeString, _ := base64.StdEncoding.DecodeString(secret) + hash.Write(decodeString) + V, _ := base64.StdEncoding.DecodeString(nonce) + hash.Write(V) + return base64.StdEncoding.EncodeToString(hash.Sum(nil)) +} + +func generateSignature(uri, signedNonce, nonce, data string) string { + sign := uri + "&" + signedNonce + "&" + nonce + "&data=" + data + decodeString, _ := base64.StdEncoding.DecodeString(signedNonce) + mac := hmac.New(sha256.New, decodeString) + mac.Write([]byte(sign)) + return base64.StdEncoding.EncodeToString(mac.Sum(nil)) +} + +func mapToJSON(data map[string]interface{}) (string, error) { + jsonBytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(jsonBytes), nil +} + +func PostData(uri string, data map[string]interface{}, Certificate Authorize) []byte { + + Certificate.UserId = 2251648609 + Certificate.ServiceToken = "x2K8nowPRvFEaeEKt2aj35xTgEZLTspxURrRIbbETcLJ5WLNdzxFrI1DfI3M3+/tvxJyfQMdaYu+8EfedWgSfB81T0P8R5zofX6GUd/jE2KlslBLyzn3vabqhHIL93ahoPUog1Q8pKBJJTRqOrL2pypDy1ODWkQ07jUC6fWqf+k=" + Certificate.Code = 0 + Certificate.Sid = "xiaomiio" + Certificate.SecurityToken = "btZAHcCqLvQZp0eot8IlOQ==" + Certificate.DeviceId = "3P5v99CdV4vwkpsS" + + dataStr, err := mapToJSON(data) + if err != nil { + return nil + } + + nonce := generateRandomDeviceId(16) + nonce = "H9qOd9J9wRRMTXj0" + signedNonce := generateSignedNonce(Certificate.SecurityToken, nonce) + signature := generateSignature(uri, signedNonce, nonce, dataStr) + + body := url.Values{} + body.Set("_nonce", nonce) + body.Set("data", dataStr) + body.Set("signature", signature) + + userAgent := "APP/com.xiaomi.mihome APPV/6.0.103 iosPassportSDK/3.9.0 iOS/14.4 miHSTS" + client := &http.Client{} + req, _ := http.NewRequest("POST", "https://api.io.mi.com/app"+uri, strings.NewReader(body.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("User-Agent", userAgent) + req.Header.Set("x-xiaomi-protocal-flag-cli", "PROTOCAL-HTTP2") + req.Header.Set("Cookie", fmt.Sprintf("PassportDeviceId=%s;userId=%v;serviceToken=%s;", Certificate.DeviceId, Certificate.UserId, Certificate.ServiceToken)) + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error:", err) + } + defer resp.Body.Close() + + respBody, _ := io.ReadAll(resp.Body) + return respBody +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e52a70d --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module mijia-go-sdk + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..d240bbd --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "encoding/json" + "fmt" + "mijia-go-sdk/api" +) + +func main() { + user := "15542100924" + pwd := "1XAII@wsk" + result, _ := api.Login(user, pwd) + marshal, err := json.Marshal(result) + if err != nil { + return + } + Mi := api.NewMiJia(result) + //Mi.Rooms() + //Mi.Devices() + //Mi.Scenes(0) + //Mi.RunScene("1784216758920822784") + + var data []map[string]interface{} + + data = append(data, map[string]interface{}{ + "did": "538261193", + "siid": 2, + "piid": 1, + }) + data = append(data, map[string]interface{}{ + "did": "538261193", + "siid": 2, + "piid": 2, + }) + data = append(data, map[string]interface{}{ + "did": "538261193", + "siid": 2, + "piid": 3, + }) + + Mi.GetDeviceVar(data) + //Mi.Consumables(0) + //Mi.Scenes(0) + println(marshal) + fmt.Println(result) +}