- 改动:go.mod module 声明与全部内部导入路径补全为完整域名路径,README 同步 - 原因:裸模块名无法被 go get 解析,发版前必须修正为 GOPRIVATE 可拉取路径
132 lines
3.5 KiB
Go
132 lines
3.5 KiB
Go
// E1 — OAuth authorization flow example.
|
|
// Demonstrates: generate auth URL → get code → exchange for token → refresh token.
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"git.zeroonesoft.cn/golib/xiaomihome/miot"
|
|
"git.zeroonesoft.cn/golib/xiaomihome/miot/examples/config"
|
|
)
|
|
|
|
func main() {
|
|
// 1. Load config (or use defaults)
|
|
cfg, err := config.LoadConfig(config.DefaultPath())
|
|
if err != nil {
|
|
fmt.Printf("Load config error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// Ensure UUID is set (may be empty from old-format config)
|
|
if cfg.UUID == "" {
|
|
cfg.UUID = config.NewUUID()
|
|
}
|
|
|
|
// 2. Create OAuth client (Go constructor appends "ha." internally)
|
|
oauth := miot.NewMIoTOauthClient(
|
|
miot.OAUTH2_CLIENT_ID,
|
|
cfg.RedirectURL,
|
|
cfg.CloudServer,
|
|
cfg.UUID,
|
|
)
|
|
|
|
// 3. Generate authorization URL
|
|
authURL := oauth.GenAuthURL("", "", nil, false)
|
|
fmt.Println("=== OAuth 授权 ===")
|
|
fmt.Println("请打开以下URL完成授权:")
|
|
fmt.Println(authURL)
|
|
|
|
// 4. Read authorization code from stdin
|
|
fmt.Print("\n输入回调URL中的 code: ")
|
|
var code string
|
|
fmt.Scanln(&code)
|
|
if code == "" {
|
|
fmt.Println("未输入 code,退出")
|
|
return
|
|
}
|
|
|
|
// 5. Exchange code for access token
|
|
result, err := oauth.GetAccessToken(code)
|
|
if err != nil {
|
|
fmt.Printf("获取 token 失败: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// 6. Save tokens to config (aligns with Python auth_info structure)
|
|
cfg.AuthInfo = config.AuthInfo{
|
|
AccessToken: safeStr(result, "access_token"),
|
|
RefreshToken: safeStr(result, "refresh_token"),
|
|
MacKey: safeStr(result, "mac_key"),
|
|
ExpiresIn: safeInt(result, "expires_in"),
|
|
ExpiresTS: config.CalcExpiresTS(safeInt(result, "expires_in")),
|
|
}
|
|
|
|
// 7. Get UID from home list (uid is in GetHomeInfos response, not token response)
|
|
http, err := miot.NewMIoTHttpClient(cfg.CloudServer, miot.OAUTH2_CLIENT_ID, cfg.AuthInfo.AccessToken)
|
|
if err == nil {
|
|
defer http.Close()
|
|
if homes, e := http.GetHomeInfos(); e == nil {
|
|
cfg.UID = safeStr(homes, "uid")
|
|
fmt.Printf("UID: %s\n", cfg.UID)
|
|
}
|
|
}
|
|
|
|
if err := config.SaveConfig(config.DefaultPath(), cfg); err != nil {
|
|
fmt.Printf("保存 config 失败: %v\n", err)
|
|
} else {
|
|
fmt.Println("\n=== Token 已保存 ===")
|
|
t := cfg.AuthInfo.AccessToken
|
|
fmt.Printf("access_token: %s...\n", t[:min(20, len(t))])
|
|
t = cfg.AuthInfo.RefreshToken
|
|
fmt.Printf("refresh_token: %s...\n", t[:min(20, len(t))])
|
|
fmt.Printf("uid: %s\n", cfg.UID)
|
|
fmt.Printf("uuid: %s\n", cfg.UUID)
|
|
}
|
|
|
|
// 8. Demonstrate token refresh
|
|
fmt.Println("\n=== 刷新 Token ===")
|
|
newResult, err := oauth.RefreshAccessToken(cfg.AuthInfo.RefreshToken)
|
|
if err != nil {
|
|
fmt.Printf("刷新 token 失败: %v\n", err)
|
|
return
|
|
}
|
|
cfg.AuthInfo.AccessToken = safeStr(newResult, "access_token")
|
|
cfg.AuthInfo.RefreshToken = safeStr(newResult, "refresh_token")
|
|
cfg.AuthInfo.ExpiresTS = config.CalcExpiresTS(safeInt(newResult, "expires_in"))
|
|
config.SaveConfig(config.DefaultPath(), cfg)
|
|
fmt.Println("Token 已刷新并保存")
|
|
}
|
|
|
|
func safeStr(m map[string]interface{}, key string) string {
|
|
if v, ok := m[key]; ok {
|
|
if s, ok := v.(string); ok {
|
|
return s
|
|
}
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return fmt.Sprintf("%.0f", n)
|
|
case json.Number:
|
|
return n.String()
|
|
default:
|
|
return fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func safeInt(m map[string]interface{}, key string) int {
|
|
if v, ok := m[key]; ok {
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return int(n)
|
|
case int:
|
|
return n
|
|
case json.Number:
|
|
i, _ := n.Int64()
|
|
return int(i)
|
|
}
|
|
}
|
|
return 0
|
|
}
|