github.com/dim13/unifi@v0.0.0-20230308161331-9b04946f5e93/unifi.go (about) 1 // Copyright (c) 2014 The unifi Authors. All rights reserved. 2 // Use of this source code is governed by ISC-style license 3 // that can be found in the LICENSE file. 4 5 package unifi 6 7 import ( 8 "bytes" 9 "crypto/tls" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 "log" 15 "net/http" 16 "net/http/cookiejar" 17 "net/url" 18 "reflect" 19 ) 20 21 var ( 22 ErrLoginFirst = errors.New("login first") 23 minimalVersion = 5 24 ) 25 26 type Unifi struct { 27 client *http.Client 28 baseURL string 29 apiURL string 30 version int 31 } 32 33 type meta struct { 34 Rc string 35 } 36 37 type login struct { 38 Username string `json:"username"` 39 Password string `json:"password"` 40 } 41 42 // Initializes a session. Only controller versions 5 and newer are supported 43 func Login(user, pass, host, port, site string, version int) (*Unifi, error) { 44 if version < 4 { 45 return nil, fmt.Errorf("API version %d unsuported. (Minimal version: %d)", version, minimalVersion) 46 } 47 48 u := new(Unifi) 49 tr := &http.Transport{ 50 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 51 } 52 53 cj, _ := cookiejar.New(nil) 54 u.client = &http.Client{ 55 Transport: tr, 56 Jar: cj, 57 } 58 u.baseURL = "https://" + host + ":" + port + "/" 59 u.version = version 60 61 l := new(login) 62 l.Username = user 63 l.Password = pass 64 j, err := json.Marshal(l) 65 if err != nil { 66 return nil, err 67 } 68 69 r := bytes.NewReader(j) 70 if _, err := u.client.Post(u.baseURL+"api/login", "application/json", r); err != nil { 71 fmt.Println(err) 72 return nil, err 73 } 74 75 u.apiURL = u.baseURL + "api/" 76 77 return u, nil 78 } 79 80 // Terminates a session 81 func (u *Unifi) Logout() { 82 u.client.Get(u.baseURL + "logout") 83 } 84 85 func (u *Unifi) apicmd(site *Site, cmd string, payload any) ([]byte, error) { 86 87 url := u.apiURL 88 89 // For site specific command, add site settings 90 if site != nil { 91 url += fmt.Sprintf("s/%s/", site.Name) 92 } 93 94 // Add the command to the url 95 url += cmd 96 97 var resp *http.Response 98 var err error 99 100 if payload == nil { 101 resp, err = u.client.Get(url) 102 if err != nil { 103 return nil, err 104 } 105 } else { 106 json, err := json.Marshal(payload) 107 if err != nil { 108 return nil, err 109 } 110 111 resp, err = u.client.Post(url, "application/json", bytes.NewBuffer(json)) 112 if err != nil { 113 return nil, err 114 } 115 } 116 117 defer resp.Body.Close() 118 body, err := io.ReadAll(resp.Body) 119 if err != nil { 120 return nil, err 121 } 122 return body, nil 123 } 124 125 func (u *Unifi) apicmdPut(site *Site, cmd string, data any) error { 126 127 url := u.apiURL 128 129 // For site specific command, add site settings 130 if site != nil { 131 url += fmt.Sprintf("s/%s/", site.Name) 132 } 133 134 // Add the command to the url 135 url += cmd 136 137 j, err := json.Marshal(data) 138 if err != nil { 139 return err 140 } 141 142 r := bytes.NewReader(j) 143 144 req, err := http.NewRequest(http.MethodPut, url, r) 145 146 if err != nil { 147 return err 148 } 149 req.Header.Add("Content-Type", "application/json;charset=UTF-8") 150 151 // Send request 152 resp, err := u.client.Do(req) 153 if err != nil { 154 return err 155 } 156 157 defer resp.Body.Close() 158 159 if err != nil { 160 return err 161 } 162 fmt.Println(resp.Status) 163 if resp.StatusCode != 200 { 164 return fmt.Errorf(resp.Status) 165 } 166 167 return nil 168 } 169 170 type command struct { 171 Mac string `json:"mac"` 172 Cmd string `json:"cmd"` 173 Minutes int `json:"minutes,omitempty"` 174 } 175 176 func (u *Unifi) devcmd(mac, cmd string) error { 177 return u.maccmd("devmgr", command{Mac: mac, Cmd: cmd}) 178 } 179 180 func (u *Unifi) stacmd(mac, cmd string, min ...int) error { 181 minutes := 0 182 if len(min) > 0 { 183 minutes = min[0] 184 } 185 return u.maccmd("stamgr", command{Mac: mac, Cmd: cmd, Minutes: minutes}) 186 } 187 188 func (u *Unifi) maccmd(mgr string, args any) error { 189 param, err := json.Marshal(args) 190 if err != nil { 191 return err 192 } 193 val := url.Values{"json": {string(param)}} 194 _, err = u.client.PostForm(u.apiURL+"cmd/"+mgr, val) 195 return err 196 } 197 198 func (u *Unifi) parse(site *Site, cmd string, payload any, v any) error { 199 body, err := u.apicmd(site, cmd, payload) 200 if err != nil { 201 return err 202 } 203 if err := json.Unmarshal(body, &v); err != nil { 204 log.Println(cmd) 205 log.Println(string(body)) 206 return err 207 } 208 m := reflect.ValueOf(v).Elem().FieldByName("Meta").Interface().(meta) 209 if m.Rc != "ok" { 210 return fmt.Errorf("%s returned result code: %s", cmd, m.Rc) 211 } 212 return nil 213 } 214 215 // Returns a slice of stations 216 func (u *Unifi) Sta(site *Site) ([]Sta, error) { 217 var response struct { 218 Data []Sta 219 Meta meta 220 } 221 err := u.parse(site, "stat/sta", nil, &response) 222 for i := range response.Data { 223 response.Data[i].u = u 224 } 225 return response.Data, err 226 } 227 228 // Returns a map of stations with MAC as a key 229 func (u *Unifi) StaMap(site *Site) (StaMap, error) { 230 sta, err := u.Sta(site) 231 if err != nil { 232 return nil, err 233 } 234 m := make(StaMap) 235 for _, s := range sta { 236 m[s.Mac] = s 237 } 238 return m, nil 239 } 240 241 // Returns a slice of known users 242 func (u *Unifi) Users(site *Site) ([]User, error) { 243 var response struct { 244 Data []User 245 Meta meta 246 } 247 err := u.parse(site, "list/user", nil, &response) 248 return response.Data, err 249 } 250 251 // Returns a slice of known portconfigs 252 func (u *Unifi) PortProfiles(site *Site) ([]PortProfile, error) { 253 var response struct { 254 Data []PortProfile 255 Meta meta 256 } 257 err := u.parse(site, "list/portconf", nil, &response) 258 return response.Data, err 259 } 260 261 // Returns a map of networkconfigs with ID as key 262 func (u *Unifi) PortProfileMap(site *Site) (PortprofileMap, error) { 263 profiles, err := u.PortProfiles(site) 264 if err != nil { 265 return nil, err 266 } 267 m := make(PortprofileMap) 268 for _, p := range profiles { 269 m[p.ID] = p 270 } 271 return m, nil 272 } 273 274 // Returns a map of networkconfigs with ID as key 275 func (u *Unifi) PortProfile(site *Site, name string) (*PortProfile, error) { 276 277 profiles, err := u.PortProfiles(site) 278 if err != nil { 279 return nil, err 280 } 281 282 for _, p := range profiles { 283 if p.Name == name { 284 return &p, err 285 } 286 } 287 return nil, fmt.Errorf("no profile with name: %s", name) 288 } 289 290 // Sets the portoverrides of a given device 291 func (u *Unifi) SetPortoverrides(site *Site, deviceid string, o []PortOverride) error { 292 293 cmd := fmt.Sprintf("rest/device/%s", deviceid) 294 295 // Create a map with port_overrides as key and a slice of overrides as value 296 m := make(map[string][]PortOverride) 297 m["port_overrides"] = o 298 err := u.apicmdPut(site, cmd, m) 299 300 return err 301 } 302 303 // Returns user groups 304 func (u *Unifi) UserGroups(site *Site) ([]UserGroup, error) { 305 var response struct { 306 Data []UserGroup 307 Meta meta 308 } 309 err := u.parse(site, "list/usergroup", nil, &response) 310 return response.Data, err 311 } 312 313 // Returns a Wlan config 314 func (u *Unifi) WlanConf(site *Site) ([]WlanConf, error) { 315 var response struct { 316 Data []WlanConf 317 Meta meta 318 } 319 err := u.parse(site, "list/wlanconf", nil, &response) 320 return response.Data, err 321 } 322 323 func (u *Unifi) CreateBackup() error { 324 return nil 325 } 326 327 func (u *Unifi) GetBackup() error { 328 return nil 329 }