You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
5.3 KiB
Go
171 lines
5.3 KiB
Go
package mijia
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/md5"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// 生成随机设备ID
|
|
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)
|
|
}
|
|
|
|
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 SetReqHeader(req *http.Request) {
|
|
req.Header.Set("User-Agent", "APP/com.xiaomi.mihome APPV/6.0.103 iosPassportSDK/3.9.0 iOS/14.4 miHSTS")
|
|
}
|
|
|
|
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{})
|
|
client := &http.Client{}
|
|
req, _ := http.NewRequest("GET", msgURL, nil)
|
|
SetReqHeader(req)
|
|
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)
|
|
|
|
client := &http.Client{}
|
|
req, _ := http.NewRequest("POST", "https://api.io.mi.com/app"+uri, strings.NewReader(body.Encode()))
|
|
SetReqHeader(req)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
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
|
|
}
|