package mysubaru import ( "encoding/json" "fmt" "log/slog" "net/http" "sync" "time" "github.com/Jeffail/gabs/v2" "github.com/alex-savin/go-mysubaru/config" "github.com/go-resty/resty/v2" ) // credentials . type credentials struct { username string password string pin string deviceId string deviceName string } // Client . type Client struct { baseURL string credentials credentials httpClient *resty.Client cookies []*http.Cookie country string // updateInterval int // 7200 fetchInterval int // 360 currentVin string listOfVins []string isAuthenticated bool isRegistered bool log *slog.Logger sync.RWMutex } // Option . type Option func(*Client) error // BaseURL allows overriding of API client baseURL for testing func BaseURL(baseURL string) Option { return func(c *Client) error { c.baseURL = baseURL return nil } } // Username . func Username(username string) Option { return func(c *Client) error { c.credentials.username = username return nil } } // Password . func Password(password string) Option { return func(c *Client) error { c.credentials.password = password return nil } } // auth . func (c *Client) auth() []byte { // { // "success": true, // "errorCode": null, // "dataName": "sessionData", // "data": { // "sessionChanged": false, // "vehicleInactivated": false, // "account": { // "marketId": 1, // "createdDate": 1476984644000, // "firstName": "Tatiana", // "lastName": "Savin", // "zipCode": "07974", // "accountKey": 765268, // "lastLoginDate": 1640539132000, // "zipCode5": "07974" // }, // "resetPassword": false, // "deviceId": "JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ", // "sessionId": "010A2C0C25A5D35AC3776DB6B7900A3B", // "deviceRegistered": true, // "passwordToken": null, // "vehicles": [ // { // "customer": { // "sessionCustomer": null, // "email": null, // "firstName": null, // "lastName": null, // "zip": null, // "oemCustId": null, // "phone": null // }, // "stolenVehicle": false, // "vehicleName": "Subaru Outback LXT", // "features": null, // "vin": "4S4BTGND8L3137058", // "modelYear": null, // "modelCode": null, // "engineSize": null, // "nickname": "Subaru Outback LXT", // "vehicleKey": 3832950, // "active": true, // "licensePlate": "", // "licensePlateState": "", // "email": null, // "firstName": null, // "lastName": null, // "subscriptionFeatures": null, // "accessLevel": -1, // "zip": null, // "oemCustId": "CRM-631-HQN48K", // "vehicleMileage": null, // "phone": null, // "userOemCustId": "CRM-631-HQN48K", // "subscriptionStatus": null, // "authorizedVehicle": false, // "preferredDealer": null, // "cachedStateCode": "NJ", // "subscriptionPlans": [], // "needMileagePrompt": false, // "phev": null, // "remoteServicePinExist": true, // "needEmergencyContactPrompt": false, // "vehicleGeoPosition": null, // "extDescrip": null, // "intDescrip": null, // "modelName": null, // "transCode": null, // "provisioned": true, // "timeZone": "America/New_York" // } // ], // "currentVehicleIndex": 0, // "handoffToken": "$2a$08$99me3RRpB00MNMSFxrG7AOg1T5BaDVacqXhbdTii0eRXoEoeFvEPy$1640540934344", // "enableXtime": true, // "termsAndConditionsAccepted": true, // "digitalGlobeConnectId": "0572e32b-2fcf-4bc8-abe0-1e3da8767132", // "digitalGlobeImageTileService": "https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132", // "digitalGlobeTransparentTileService": "https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132", // "tomtomKey": "DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs", // "satelliteViewEnabled": true // } // } params := map[string]string{ "env": "cloudprod", "deviceType": "android", "loginUsername": c.credentials.username, "password": c.credentials.password, "deviceId": c.credentials.deviceId, "passwordToken": "", "selectedVin": "", "pushToken": ""} reqURL := MOBILE_API_VERSION + apiURLs["API_LOGIN"] resp := c.execute(reqURL, POST, params, "", false) c.log.Debug("AUTH HTTP OUTPUT", "body", string([]byte(resp))) return resp } // GET // https://www.mysubaru.com/profile/verifyDeviceName.json?clientId=2574212&deviceName=Alex%20Google%20Pixel%204%20XL // RESP: true/false // POST // https://www.mysubaru.com/profile/editDeviceName.json?clientId=2574212&deviceName=Alex%20Google%20Pixel%204%20XL // clientId: 2574212 // deviceName: Alex Google Pixel 4 XL // RESP: true/false // GET // https://www.mysubaru.com/listMyDevices.json // {"success":true,"dataName":"authorizedDevices","data":[{"telematicsClientDeviceKey":2574212,"deviceType":"android","deviceName":"Alex Google Pixel 4 XL","createdDate":"2019-11-29T20:32:21.000+0000","modifiedDate":"2020-06-08T17:48:22.000+0000"},{"telematicsClientDeviceKey":4847533,"deviceName":"Home Assistant: Added 2021-03-03","createdDate":"2021-03-03T20:53:44.000+0000","modifiedDate":"2021-03-03T20:53:47.000+0000"},{"telematicsClientDeviceKey":7222995,"deviceType":"android","deviceName":"Alex Google Pixel 6 Pro","createdDate":"2021-10-28T15:27:36.000+0000","modifiedDate":"2021-10-28T15:27:58.000+0000"},{"telematicsClientDeviceKey":8207130,"deviceName":"Mac/iOS Chrome","createdDate":"2021-12-21T21:19:40.000+0000","modifiedDate":"2021-12-21T21:19:40.000+0000"}]} // {"success":true,"dataName":"authorizedDevices","data":[{"telematicsClientDeviceKey":2574212,"deviceType":"android","deviceName":"Alex Google Pixel 4 XL","createdDate":"2019-11-29T20:32:21.000+0000","modifiedDate":"2020-06-08T17:48:22.000+0000"},{"telematicsClientDeviceKey":4847533,"deviceName":"Home Assistant: Added 2021-03-03","createdDate":"2021-03-03T20:53:44.000+0000","modifiedDate":"2021-03-03T20:53:47.000+0000"},{"telematicsClientDeviceKey":7222995,"deviceType":"android","deviceName":"Alex Google Pixel 6 Pro","createdDate":"2021-10-28T15:27:36.000+0000","modifiedDate":"2021-10-28T15:27:58.000+0000"},{"telematicsClientDeviceKey":8210723,"deviceName":"Hassio Golang Integration","createdDate":"2021-12-22T01:38:43.000+0000","modifiedDate":"2021-12-22T01:38:43.000+0000"},{"telematicsClientDeviceKey":8207130,"deviceName":"Mac/iOS Chrome","createdDate":"2021-12-21T21:19:40.000+0000","modifiedDate":"2021-12-21T21:19:40.000+0000"}]} // registerDevice . func (c *Client) registerDevice() bool { // c.httpClient. // SetBaseURL(WEB_API_SERVER[c.country]). // R(). // SetFormData(map[string]string{ // "username": c.credentials.username, // "password": c.credentials.password, // "deviceId": c.credentials.deviceId, // }). // Post(apiURLs["WEB_API_LOGIN"]) params := map[string]string{ "username": c.credentials.username, "password": c.credentials.password, "deviceId": c.credentials.deviceId} reqURL := WEB_API_SERVER[c.country] + apiURLs["WEB_API_LOGIN"] c.execute(reqURL, POST, params, "", true) // Authorizing device via web API // c.httpClient. // SetBaseURL(WEB_API_SERVER[c.country]). // R(). // SetQueryParams(map[string]string{ // "deviceId": c.credentials.deviceId, // }). // Get(apiURLs["WEB_API_AUTHORIZE_DEVICE"]) params = map[string]string{ "deviceId": c.credentials.deviceId} reqURL = WEB_API_SERVER[c.country] + apiURLs["WEB_API_AUTHORIZE_DEVICE"] c.execute(reqURL, GET, params, "", false) return c.setDeviceName() } // setDeviceName . func (c *Client) setDeviceName() bool { params := map[string]string{ "deviceId": c.credentials.deviceId, "deviceName": c.credentials.deviceName} reqURL := WEB_API_SERVER[c.country] + apiURLs["WEB_API_NAME_DEVICE"] c.execute(reqURL, GET, params, "", false) return true } // listDevices . func (c *Client) listDevices() { // Accept: application/json, text/javascript, */*; q=0.01 // Accept-Encoding: gzip, deflate, br // Accept-Language: en-US,en;q=0.9,ru;q=0.8 // Connection: keep-alive // Cookie: ORA_OTD_JROUTE=ozLwELf5jS-NHQ2CKZorOFfRgb8uo6lL; soa-visitor=12212021VHWnkqERZYThWe87TLUhr2Db; AMCVS_94001C8B532957140A490D4D%40AdobeOrg=1; mys-referringCodes=7~direct~; s_cc=true; AMCVS_subarucom%40AdobeOrg=1; style=null; s_pv=login.html; AMCV_subarucom%40AdobeOrg=-1124106680%7CMCIDTS%7C18988%7CMCMID%7C81535064704660726005836131001032500276%7CMCAID%7CNONE%7CMCOPTOUT-1640567559s%7CNONE%7CvVersion%7C5.2.0; AMCV_94001C8B532957140A490D4D%40AdobeOrg=-1124106680%7CMCIDTS%7C18988%7CMCMID%7C76913534164341455390435376071204508177%7CMCAID%7CNONE%7CMCOPTOUT-1640567559s%7CNONE%7CvVersion%7C5.2.0; s_sq=subarumysubarucwpprod%3D%2526c.%2526a.%2526activitymap.%2526page%253Dlogin.html%2526link%253DLog%252520In%2526region%253DloginForm%2526pageIDType%253D1%2526.activitymap%2526.a%2526.c%2526pid%253Dlogin.html%2526pidt%253D1%2526oid%253DLog%252520In%2526oidt%253D3%2526ot%253DSUBMIT; JSESSIONID=9685CFEB7888A0E6E25239D559E3B580; X-Oracle-BMC-LBS-Route=89e3283ece707e8a0ba4850e1a622122e039fd3d27da03a11a2ff120e313e9b656c62fd8a7c42ae8061a49ad6e1caf63a49d7befe4ad2a0194b0aeca // Host: www.mysubaru.com // Referer: https://www.mysubaru.com/profile/authorizedDevices.html // User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 // X-Requested-With: XMLHttpRequest resp, err := c.httpClient. SetBaseURL(WEB_API_SERVER[c.country]). R(). EnableTrace(). Get(apiURLs["WEB_API_LIST_DEVICES"]) // Explore response object fmt.Println("Response Info:") fmt.Println(" Error :", err) fmt.Println(" Status Code:", resp.StatusCode()) fmt.Println(" Status :", resp.Status()) fmt.Println(" Proto :", resp.Proto()) fmt.Println(" Time :", resp.Time()) fmt.Println(" Received At:", resp.ReceivedAt()) // fmt.Println(" Body :\n", resp) fmt.Println() // Explore trace info fmt.Println("Request Trace Info:") ti := resp.Request.TraceInfo() fmt.Println(" DNSLookup :", ti.DNSLookup) fmt.Println(" ConnTime :", ti.ConnTime) fmt.Println(" TCPConnTime :", ti.TCPConnTime) fmt.Println(" TLSHandshake :", ti.TLSHandshake) fmt.Println(" ServerTime :", ti.ServerTime) fmt.Println(" ResponseTime :", ti.ResponseTime) fmt.Println(" TotalTime :", ti.TotalTime) fmt.Println(" IsConnReused :", ti.IsConnReused) fmt.Println(" IsConnWasIdle :", ti.IsConnWasIdle) fmt.Println(" ConnIdleTime :", ti.ConnIdleTime) fmt.Println(" RequestAttempt:", ti.RequestAttempt) fmt.Println(" RemoteAddr :", ti.RemoteAddr.String()) // c.log.Debug("LIST DEVICES OUTPUT", "body", string([]byte(resp.Body()))) // c.httpClient.SetBaseURL(WEB_API_SERVER[c.country]).SetCookies(c.cookies) // reqURL := apiURLs["WEB_API_LIST_DEVICES"] // resp := c.execute(reqURL, GET, map[string]string{}, "", false) // if isResponseSuccessfull(resp) { // log.Debugf("LIST DEVICES OUTPUT >> %v\n", string(resp)) // } } // validateSession . func (c *Client) validateSession() bool { // { // "success": true, // "errorCode": null, // "dataName": null, // "data": null // } reqURL := MOBILE_API_VERSION + apiURLs["API_VALIDATE_SESSION"] resp := c.execute(reqURL, GET, map[string]string{}, "", false) if !c.isResponseSuccessfull(resp) { return false } // result = False // js_resp = await self.__open(API_VALIDATE_SESSION, GET) // _LOGGER.debug(pprint.pformat(js_resp)) // if js_resp["success"]: // if vin != self._current_vin: // # API call for VIN that is not the current remote context. // _LOGGER.debug("Switching Subaru API vehicle context to: %s", vin) // if await self._select_vehicle(vin): // result = True // else: // result = True // if result is False: // await self._authenticate(vin) // # New session cookie. Must call selectVehicle.json before any other API call. // if await self._select_vehicle(vin): // result = True c.log.Debug("[DEBUG] Validate Session", "body", string([]byte(resp))) return true } func (c *Client) SelectVehicle(vin string) VehicleData { // { // "success": true, // "errorCode": null, // "dataName": "vehicle", // "data": { // "customer": { // "sessionCustomer": null, // "email": null, // "firstName": null, // "lastName": null, // "zip": null, // "oemCustId": null, // "phone": null // }, // "stolenVehicle": false, // "vehicleName": "Subaru Outback LXT", // "features": [ // "ATF_MIL", // "11.6MMAN", // "ABS_MIL", // "CEL_MIL", // "ACCS", // "RCC", // "REARBRK", // "TEL_MIL", // "VDC_MIL", // "TPMS_MIL", // "WASH_MIL", // "BSDRCT_MIL", // "OPL_MIL", // "EYESIGHT", // "RAB_MIL", // "SRS_MIL", // "ESS_MIL", // "RESCC", // "EOL_MIL", // "BSD", // "EBD_MIL", // "EPB_MIL", // "RES", // "RHSF", // "AWD_MIL", // "NAV_TOMTOM", // "ISS_MIL", // "RPOIA", // "EPAS_MIL", // "RPOI", // "AHBL_MIL", // "SRH_MIL", // "g2" // ], // "vin": "4S4BTGND8L3137058", // "modelYear": "2020", // "modelCode": "LDJ", // "engineSize": 2.4, // "nickname": "Subaru Outback LXT", // "vehicleKey": 3832950, // "active": true, // "licensePlate": "8KV8", // "licensePlateState": "NJ", // "email": null, // "firstName": null, // "lastName": null, // "subscriptionFeatures": [ // "REMOTE", // "SAFETY", // "Retail" // ], // "accessLevel": -1, // "zip": null, // "oemCustId": "CRM-631-HQN48K", // "vehicleMileage": null, // "phone": null, // "userOemCustId": "CRM-631-HQN48K", // "subscriptionStatus": "ACTIVE", // "authorizedVehicle": false, // "preferredDealer": null, // "cachedStateCode": "NJ", // "subscriptionPlans": [], // "needMileagePrompt": false, // "phev": null, // "remoteServicePinExist": true, // "needEmergencyContactPrompt": false, // "vehicleGeoPosition": { // "latitude": 40.70019, // "longitude": -74.401375, // "speed": null, // "heading": null, // "timestamp": 1640494569000 // }, // "extDescrip": "Abyss Blue Pearl", // "intDescrip": "Gray", // "modelName": "Outback", // "transCode": "CVT", // "provisioned": true, // "timeZone": "America/New_York" // } // } if vin == "" { vin = c.currentVin } vinCheck(vin) params := map[string]string{ "vin": vin, "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] resp := c.execute(reqURL, GET, params, "", false) var vData VehicleData respParsed, err := gabs.ParseJSON([]byte(resp)) if err != nil { panic(err) } vdString := respParsed.Path("data").String() json.Unmarshal([]byte(vdString), &vData) // resp := c.execute(reqURL, GET, params, "", false) // log.Debugf("SELECT VEHICLE OUTPUT >> %v\n", string([]byte(resp))) // ERRORS // {"success":false,"errorCode":"vehicleNotInAccount","dataName":null,"data":null} // """Select active vehicle for accounts with multiple VINs.""" // params = {"vin": vin, "_": int(time.time())} // js_resp = await self.get(API_SELECT_VEHICLE, params=params) // _LOGGER.debug(pprint.pformat(js_resp)) // if js_resp.get("success"): // self._current_vin = vin // _LOGGER.debug("Current vehicle: vin=%s", js_resp["data"]["vin"]) // return js_resp["data"] // if not js_resp.get("success") and js_resp.get("errorCode") == "VEHICLESETUPERROR": // # Occasionally happens every few hours. Resetting the session seems to deal with it. // _LOGGER.warning("VEHICLESETUPERROR received. Resetting session.") // self.reset_session() // return False // _LOGGER.debug("Failed to switch vehicle errorCode=%s", js_resp.get("errorCode")) // # Something else is probably wrong with the backend server context - try resetting // self.reset_session() // raise SubaruException("Failed to switch vehicle %s - resetting session." % js_resp.get("errorCode")) return vData } // New function creates a New MySubaru client func New(logger *slog.Logger, config *config.MySubaru) (*Client, error) { credentials := credentials{ username: config.Credentials.Username, password: config.Credentials.Password, pin: config.Credentials.PIN, deviceId: config.Credentials.DeviceID, deviceName: config.Credentials.DeviceName, } client := &Client{ credentials: credentials, country: config.Region, updateInterval: 7200, fetchInterval: 360, log: logger, } httpClient := resty.New() httpClient. SetBaseURL(MOBILE_API_SERVER[client.country]). SetHeaders(map[string]string{ "User-Agent": "Mozilla/5.0 (Linux; Android 10; Android SDK built for x86 Build/QSR1.191030.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.185 Mobile Safari/537.36", "Origin": "file://", "X-Requested-With": MOBILE_APP[client.country], "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept": "*/*"}) client.httpClient = httpClient resp := client.auth() respParsed, err := gabs.ParseJSON(resp) if err != nil { panic(err) } if client.isResponseSuccessfull(resp) { logger.Debug("Client authentication successful", "isRegistered", respParsed.Path("data.deviceRegistered").Data().(bool)) client.isAuthenticated = true client.isRegistered = respParsed.Path("data.deviceRegistered").Data().(bool) } else { error, _ := respParsed.Path("errorCode").Data().(string) switch { case error == apiErrors["ERROR_INVALID_ACCOUNT"]: logger.Debug("Invalid account") case error == apiErrors["ERROR_INVALID_CREDENTIALS"]: logger.Debug("Client authentication failed") case error == apiErrors["ERROR_PASSWORD_WARNING"]: logger.Debug("Multiple Password Failures.") default: logger.Debug("Uknown error") } } if !client.isRegistered { client.registerDevice() } // TODO fmt.Printf("Parcing cars: %d\n", len(respParsed.Path("data.vehicles").Children())) for _, vehicle := range respParsed.Path("data.vehicles").Children() { fmt.Printf("CAR: %s\n", vehicle.Path("vin").Data().(string)) client.listOfVins = append(client.listOfVins, vehicle.Path("vin").Data().(string)) } client.currentVin = respParsed.Path("data.vehicles.0.vin").Data().(string) return client, nil } // GetVehicles . func (c *Client) GetVehicles() []*Vehicle { var vehicles []*Vehicle for _, vin := range c.listOfVins { params := map[string]string{ "vin": vin, "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] resp := c.execute(reqURL, GET, params, "", false) respParsed, err := gabs.ParseJSON([]byte(resp)) if err != nil { panic(err) } vData := VehicleData{} vdString := respParsed.Path("data").String() json.Unmarshal([]byte(vdString), &vData) // fmt.Printf("VEHICLE STRING: %+v\n\n", vdString) // fmt.Printf("VEHICLE DATA: %+v\n\n", vData) vehicle := &Vehicle{ Vin: vin, CarName: vData.VehicleName, CarNickname: vData.Nickname, ModelName: vData.ModelName, ModelYear: vData.ModelYear, ModelCode: vData.ModelCode, ExtDescrip: vData.ExtDescrip, IntDescrip: vData.IntDescrip, TransCode: vData.TransCode, EngineSize: vData.EngineSize, VehicleKey: vData.VehicleKey, LicensePlate: vData.LicensePlate, LicensePlateState: vData.LicensePlateState, Features: vData.Features, SubscriptionFeatures: vData.SubscriptionFeatures, client: c, } vehicle.GetVehicleStatus() vehicle.GetVehicleCondition() vehicle.GetClimatePresets() vehicle.GetClimateUserPresets() vehicles = append(vehicles, vehicle) } return vehicles } // GetVehicleByVIN . func (c *Client) GetVehicleByVIN(vin string) *Vehicle { var vehicle *Vehicle if contains(c.listOfVins, vin) { params := map[string]string{ "vin": vin, "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] resp := c.execute(reqURL, GET, params, "", false) respParsed, err := gabs.ParseJSON([]byte(resp)) if err != nil { panic(err) } vData := VehicleData{} vdString := respParsed.Path("data").String() json.Unmarshal([]byte(vdString), &vData) // fmt.Printf("VEHICLE DATA: %+v\n\n", vData) vehicle = &Vehicle{ Vin: vin, CarName: vData.VehicleName, CarNickname: vData.Nickname, ModelName: vData.ModelName, ModelYear: vData.ModelYear, ModelCode: vData.ModelCode, ExtDescrip: vData.ExtDescrip, IntDescrip: vData.IntDescrip, TransCode: vData.TransCode, EngineSize: vData.EngineSize, VehicleKey: vData.VehicleKey, LicensePlate: vData.LicensePlate, LicensePlateState: vData.LicensePlateState, Features: vData.Features, SubscriptionFeatures: vData.SubscriptionFeatures, client: c, } vehicle.GetVehicleStatus() vehicle.GetVehicleCondition() vehicle.GetClimatePresets() vehicle.GetClimateUserPresets() } return vehicle } // GetVehicleStatus . func (c *Client) GetVehicleStatus() { // { // "dataName": null, // "errorCode": null, // "success": true, // "data": { // "avgFuelConsumptionLitersPer100Kilometers": 12.5, // "avgFuelConsumptionMpg": 18.8, // "distanceToEmptyFuelKilometers": 563, // "distanceToEmptyFuelKilometers10s": 560, // "distanceToEmptyFuelMiles": 349.83, // "distanceToEmptyFuelMiles10s": 350, // "evDistanceToEmptyByStateKilometers": null, // "evDistanceToEmptyByStateMiles": null, // "evDistanceToEmptyKilometers": null, // "evDistanceToEmptyMiles": null, // "evStateOfChargePercent": null, // "eventDate": 1640494569000, // "eventDateStr": "2021-12-26T04:56+0000", // "latitude": 40.700192, // "longitude": -74.401377, // "odometerValue": 24065, // "odometerValueKilometers": 38721, // "positionHeadingDegree": "150", // "tirePressureFrontLeft": "2413", // "tirePressureFrontLeftPsi": "35", // "tirePressureFrontRight": "2413", // "tirePressureFrontRightPsi": "35", // "tirePressureRearLeft": "2551", // "tirePressureRearLeftPsi": "37", // "tirePressureRearRight": "2482", // "tirePressureRearRightPsi": "36", // "vehicleStateType": "IGNITION_OFF", // "vhsId": 923920223 // } // } reqURL := MOBILE_API_VERSION + apiURLs["API_VEHICLE_STATUS"] resp := c.execute(reqURL, GET, map[string]string{}, "", false) respParsed, err := gabs.ParseJSON(resp) if err != nil { panic(err) } c.log.Debug("GET VEHICLE STATUS OUTPUT", "body", respParsed) success, ok := respParsed.Path("success").Data().(bool) // value == string, ok == false if !ok { // TODO: Work with errorCode panic(success) } } // GetVehicleStatus . func (c *Client) GetClimateSettings() { // { // "success": true, // "errorCode": null, // "dataName": null, // "data": { // "climateZoneFrontTemp": "70", // "runTimeMinutes": "10", // "climateZoneFrontAirMode": "WINDOW", // "heatedSeatFrontLeft": "LOW_HEAT", // "heatedSeatFrontRight": "LOW_HEAT", // "heatedRearWindowActive": "true", // "climateZoneFrontAirVolume": "6", // "outerAirCirculation": "outsideAir", // "airConditionOn": "false", // "startConfiguration": "START_ENGINE_ALLOW_KEY_IN_IGNITION" // } // } reqURL := MOBILE_API_VERSION + apiURLs["API_G2_FETCH_CLIMATE_SETTINGS"] resp := c.execute(reqURL, GET, map[string]string{}, "", false) respParsed, err := gabs.ParseJSON(resp) if err != nil { panic(err) } c.log.Debug("CLIMATE SETTINGS OUTPUT", "response", respParsed) // ONLY FOR THAT REQUEST BECAUSE OF API SENDS BACK ESCAPING DATA IN DATA FIELD data, ok := respParsed.Path("data").Data().(string) // rawIn := json.RawMessage(in) // bytes, err := rawIn.MarshalJSON() // if err != nil { // panic(err) // } // value == string, ok == false if !ok { // TODO: Work with errorCode panic(data) } c.log.Debug("CLIMATE SETTINGS OUTPUT", "body", data) } // func isPINRequired() {} // func getVehicles() {} // func getEVStatus() {} // func getRemoteOptionsStatus() {} // func getRemoteStartStatus() {} // func getSafetyStatus() {} // func getSubscriptionStatus() {} // func getAPIGen() {} // func getClimateData() {} // func saveClimateSettings() {} // func getVehicleName() {} // func fetch() {} // Exec method executes a Client instance with the API URL func (c *Client) execute(requestUrl string, method string, params map[string]string, pollingUrl string, json bool) []byte { defer timeTrack("[TIMETRK] Executing Get Request") // if !isNil(resp.Cookies()) { // log.Debugf("AUTH COOKIES OUTPUT >> %v\n", resp.Cookies()) // c.cookies = resp.Cookies() // } var resp *resty.Response // GET Requests if method == "GET" { resp, _ = c.httpClient. // SetBaseURL(MOBILE_API_SERVER[c.country]). R(). SetQueryParams(params). Get(requestUrl) } // POST Requests if method == "POST" { if json { // POST > JSON Body resp, _ = c.httpClient.R(). SetBody(params). Post(requestUrl) } else { // POST > Form Data resp, _ = c.httpClient.R(). SetFormData(params). Post(requestUrl) } } respParsed, err := gabs.ParseJSON([]byte(resp.Body())) if err != nil { panic(err) } c.log.Debug("HTTP OUTPUT", "body", string(resp.Body())) _, ok := respParsed.Path("success").Data().(bool) // value == string, ok == false if !ok { // TODO: Work with errorCode // panic(success) fmt.Printf("ERROR: %+v", string([]byte(resp.Body()))) } if pollingUrl != "" { serviceRequestId, _ := respParsed.Path("data.serviceRequestId").Data().(string) time.Sleep(3 * time.Second) attempts := 20 poolingLoop: for attempts > 0 { resp, _ = c.httpClient. SetBaseURL(MOBILE_API_SERVER[c.country]). R(). SetQueryParams(map[string]string{ "serviceRequestId": serviceRequestId, }). Get(pollingUrl) c.log.Debug("POLLING HTTP OUTPUT", "body", string([]byte(resp.Body()))) // {"success":false,"errorCode":"404-soa-unableToParseResponseBody","dataName":"errorResponse","data":{"errorLabel":"404-soa-unableToParseResponseBody","errorDescription":null}} respParsed, err := gabs.ParseJSON(resp.Body()) if err != nil { panic(err) } success, ok := respParsed.Path("success").Data().(bool) if !ok { panic(success) } if success { status, _ := respParsed.Path("data.remoteServiceState").Data().(string) switch { case status == "finished": c.log.Debug("Remote service request completed successfully", "request id", serviceRequestId) break poolingLoop case status == "started": c.log.Debug("Subaru API reports remote service request is in progress", "request id", serviceRequestId) } } else { c.log.Debug("Backend session expired, please try again") break poolingLoop } attempts-- time.Sleep(3 * time.Second) } } // fmt.Printf("[DEBUG] HTTP OUTPUT >> %v\n", string([]byte(resp.Body()))) return resp.Body() } // isResponseSuccessfull . func (c *Client) isResponseSuccessfull(resp []byte) bool { respParsed, err := gabs.ParseJSON(resp) if err != nil { c.log.Debug("error while parsing json response", "error", err) } success, ok := respParsed.Path("success").Data().(bool) if !ok { c.log.Debug("response is not successful", "error", resp) } // ERRORS FROM CLIENT CREATION AFTER AUTH // error, _ := respParsed.Path("errorCode").Data().(string) // switch { // case error == apiErrors["ERROR_INVALID_ACCOUNT"]: // fmt.Println("Invalid account") // case error == apiErrors["ERROR_INVALID_CREDENTIALS"]: // {"success":false,"errorCode":"InvalidCredentials","dataName":"remoteServiceStatus","data":{"serviceRequestId":null,"success":false,"cancelled":false,"remoteServiceType":null,"remoteServiceState":null,"subState":null,"errorCode":null,"result":null,"updateTime":null,"vin":null,"errorDescription":"The credentials supplied are invalid, tries left 2"}} // fmt.Println("Client authentication failed") // case error == apiErrors["ERROR_PASSWORD_WARNING"]: // fmt.Println("Multiple Password Failures.") // default: // fmt.Println("Uknown error") // } return success }