diff --git a/client.go b/client.go index e2cf5ee..dd38514 100644 --- a/client.go +++ b/client.go @@ -9,7 +9,6 @@ import ( "time" "git.savin.nyc/alex/mysubaru/config" - "github.com/Jeffail/gabs/v2" "resty.dev/v3" ) @@ -172,10 +171,19 @@ func (c *Client) validateSession() bool { // } reqURL := MOBILE_API_VERSION + apiURLs["API_VALIDATE_SESSION"] resp := c.execute(reqURL, GET, map[string]string{}, "", false) + c.logger.Debug("http request output", "request", "GetVehicleStatus", "body", resp) - if !c.isResponseSuccessfull(resp) { + var r Response + err := json.Unmarshal(resp, &r) + if err != nil { + c.logger.Error("error while parsing json", "request", "GetClimatePresets", "error", err.Error()) + } + + if !r.Success { return false } + return true + // result = False // js_resp = await self.__open(API_VALIDATE_SESSION, GET) // _LOGGER.debug(pprint.pformat(js_resp)) @@ -193,10 +201,6 @@ func (c *Client) validateSession() bool { // # New session cookie. Must call selectVehicle.json before any other API call. // if await self._select_vehicle(vin): // result = True - - c.logger.Debug("session validation", "body", string([]byte(resp))) - - return true } // New function creates a New MySubaru client @@ -224,44 +228,59 @@ func New(config *config.Config) (*Client, error) { client.httpClient = httpClient resp := client.auth() - respParsed, err := gabs.ParseJSON(resp) + var r Response + err := json.Unmarshal(resp, &r) if err != nil { client.logger.Error("error while parsing json", "request", "auth", "error", err.Error()) } - if client.isResponseSuccessfull(resp) { - client.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"]: - client.logger.Debug("Invalid account") - case error == apiErrors["ERROR_INVALID_CREDENTIALS"]: - client.logger.Debug("Client authentication failed") - case error == apiErrors["ERROR_PASSWORD_WARNING"]: - client.logger.Debug("Multiple Password Failures.") - default: - client.logger.Debug("Uknown error") + if r.Success { + var sd SessionData + err := json.Unmarshal(r.Data, &sd) + if err != nil { + client.logger.Error("error while parsing json", "request", "auth", "error", err.Error()) } - } + client.logger.Debug("unmarshaled json data", "request", "auth", "type", "sessionData", "body", sd) - if !client.isRegistered { - client.registerDevice() - } + if client.isRegistered { + client.logger.Debug("Client authentication successful", "isRegistered", sd.DeviceRegistered) + client.isAuthenticated = true + client.isRegistered = sd.DeviceRegistered + } else { + client.registerDevice() + } - // TODO: create a list of the car with currently present data - client.logger.Debug("parcing cars assigned to the account", "quantity", len(respParsed.Path("data.vehicles").Children())) - for _, vehicle := range respParsed.Path("data.vehicles").Children() { - client.logger.Debug("parsing car", "vin", vehicle.Path("vin").Data().(string)) - client.listOfVins = append(client.listOfVins, vehicle.Path("vin").Data().(string)) + client.logger.Debug("parcing cars assigned to the account", "quantity", len(sd.Vehicles)) + if len(sd.Vehicles) > 0 { + for _, vehicle := range sd.Vehicles { + client.logger.Debug("parsing car", "vin", vehicle.Vin) + client.listOfVins = append(client.listOfVins, vehicle.Vin) + } + client.currentVin = client.listOfVins[0] + } else { + client.logger.Error("there no cars assigned to the account") + return nil, err + } + } else { + // TODO: Work on errors + // error, _ := respParsed.Path("errorCode").Data().(string) + // switch { + // case error == apiErrors["ERROR_INVALID_ACCOUNT"]: + // client.logger.Debug("Invalid account") + // case error == apiErrors["ERROR_INVALID_CREDENTIALS"]: + // client.logger.Debug("Client authentication failed") + // case error == apiErrors["ERROR_PASSWORD_WARNING"]: + // client.logger.Debug("Multiple Password Failures.") + // default: + // client.logger.Debug("Uknown error") + // } + client.logger.Error("request was not successfull", "request", "auth", "error", err.Error()) + return nil, err } - client.currentVin = respParsed.Path("data.vehicles.0.vin").Data().(string) - return client, nil } +// SelectVehicle . func (c *Client) SelectVehicle(vin string) VehicleData { // API > json > dataName > vehicle if vin == "" { @@ -325,52 +344,9 @@ func (c *Client) SelectVehicle(vin string) VehicleData { 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) - c.logger.Debug("http request output", "request", "GetVehicles", "body", resp) - - respParsed, err := gabs.ParseJSON(resp) - if err != nil { - c.logger.Error("error while parsing json", "request", "GetVehicles", "error", err.Error()) - } - c.logger.Debug("http request output", "request", "GetVehicles", "body", respParsed) - - vData := VehicleData{} - vdString := respParsed.Path("data").String() - - json.Unmarshal([]byte(vdString), &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.GetVehicleHealth() - vehicle.GetClimatePresets() - vehicle.GetClimateUserPresets() - vehicle.GetClimateQuickPresets() - + vehicle := c.GetVehicleByVIN(vin) vehicles = append(vehicles, vehicle) } - return vehicles } @@ -383,150 +359,59 @@ func (c *Client) GetVehicleByVIN(vin string) *Vehicle { "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] resp := c.execute(reqURL, GET, params, "", false) + c.logger.Debug("http request output", "request", "GetVehicleByVIN", "body", resp) - respParsed, err := gabs.ParseJSON([]byte(resp)) + var r Response + err := json.Unmarshal(resp, &r) if err != nil { c.logger.Error("error while parsing json", "request", "GetVehicleByVIN", "error", err.Error()) } - c.logger.Debug("http request output", "request", "GetVehicleByVIN", "body", respParsed) - vData := VehicleData{} - vdString := respParsed.Path("data").String() - json.Unmarshal([]byte(vdString), &vData) + if r.Success { + var vd VehicleData + err = json.Unmarshal(r.Data, &vd) + if err != nil { + c.logger.Error("error while parsing json", "request", "GetVehicleByVIN", "error", err.Error()) + } + c.logger.Debug("http request output", "request", "GetVehicleByVIN", "body", resp) - 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 = &Vehicle{ + Vin: vin, + CarName: vd.VehicleName, + CarNickname: vd.Nickname, + ModelName: vd.ModelName, + ModelYear: vd.ModelYear, + ModelCode: vd.ModelCode, + ExtDescrip: vd.ExtDescrip, + IntDescrip: vd.IntDescrip, + TransCode: vd.TransCode, + EngineSize: vd.EngineSize, + VehicleKey: vd.VehicleKey, + LicensePlate: vd.LicensePlate, + LicensePlateState: vd.LicensePlateState, + Features: vd.Features, + SubscriptionFeatures: vd.SubscriptionFeatures, + client: c, + } + vehicle.Doors = make(map[string]Door) + vehicle.Windows = make(map[string]Window) + vehicle.Tires = make(map[string]Tire) + vehicle.ClimateProfiles = make(map[string]ClimateProfile) + + vehicle.GetVehicleStatus() + vehicle.GetVehicleCondition() + vehicle.GetVehicleHealth() + vehicle.GetClimatePresets() + vehicle.GetClimateUserPresets() + vehicle.GetClimateQuickPresets() + + return vehicle } - vehicle.Doors = make(map[string]Door) - vehicle.Windows = make(map[string]Window) - vehicle.Tires = make(map[string]Tire) - vehicle.ClimateProfiles = make(map[string]ClimateProfile) - - vehicle.GetVehicleStatus() - vehicle.GetVehicleCondition() - vehicle.GetVehicleHealth() - vehicle.GetClimatePresets() - vehicle.GetClimateUserPresets() - vehicle.GetClimateQuickPresets() } - - return vehicle + c.logger.Error("error while parsing json", "request", "GetVehicleByVIN") + 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 { - c.logger.Error("error while parsing json", "request", "GetVehicleStatus", "error", err.Error()) - } - c.logger.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) - } -} - -// // GetClimateSettings . -// 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 { -// c.logger.Error("error while parsing json", "request", "GetClimateSettings", "error", err.Error()) -// } -// c.logger.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.logger.Debug("CLIMATE SETTINGS OUTPUT", "body", data) -// } - // func isPINRequired() {} // func getVehicles() {} // func getEVStatus() {} @@ -541,7 +426,7 @@ func (c *Client) GetVehicleStatus() { // 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 { +func (c *Client) execute(requestUrl string, method string, params map[string]string, pollingUrl string, j bool) []byte { defer timeTrack("[TIMETRK] Executing Get Request") var resp *resty.Response @@ -557,7 +442,7 @@ func (c *Client) execute(requestUrl string, method string, params map[string]str // POST Requests if method == "POST" { - if json { + if j { // POST > JSON Body resp, _ = c.httpClient. R(). @@ -576,95 +461,95 @@ func (c *Client) execute(requestUrl string, method string, params map[string]str c.logger.Error("error while getting body", "error", err.Error()) } - respParsed, err := gabs.ParseJSON(resBytes) + var r Response + err = json.Unmarshal(resBytes, &r) if err != nil { c.logger.Error("error while parsing json", "request", "execute", "method", method, "url", requestUrl, "error", err.Error()) } - // c.logger.Debug("HTTP OUTPUT", "body", string(resBytes)) - - _, ok := respParsed.Path("success").Data().(bool) - // value == string, ok == false - if !ok { - // TODO: Work with errorCode - // panic(success) - fmt.Printf("ERROR: %+v", string(resBytes)) - } - - 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) - resBytes, _ := io.ReadAll(resp.Body) - c.logger.Debug("POLLING HTTP OUTPUT", "body", string(resBytes)) - // {"success":false,"errorCode":"404-soa-unableToParseResponseBody","dataName":"errorResponse","data":{"errorLabel":"404-soa-unableToParseResponseBody","errorDescription":null}} - - respParsed, err := gabs.ParseJSON(resBytes) - 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.logger.Debug("Remote service request completed successfully", "request id", serviceRequestId) - break poolingLoop - case status == "started": - c.logger.Debug("Subaru API reports remote service request is in progress", "request id", serviceRequestId) - } - } else { - c.logger.Debug("Backend session expired, please try again") - break poolingLoop - } - attempts-- - time.Sleep(3 * time.Second) + if !r.Success { + c.logger.Error("request is not successfull", "request", "execute", "method", method, "url", requestUrl, "error", err.Error()) + } else { + var sr ServiceRequest + err := json.Unmarshal(r.Data, &sr) + if err != nil { + c.logger.Error("error while parsing json", "request", "HTTP POLLING", "error", err.Error()) } + + if pollingUrl != "" { + 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": *sr.ServiceRequestID, + }). + Get(pollingUrl) + resBytes, _ := io.ReadAll(resp.Body) + c.logger.Debug("POLLING HTTP OUTPUT", "body", string(resBytes)) + // {"success":false,"errorCode":"404-soa-unableToParseResponseBody","dataName":"errorResponse","data":{"errorLabel":"404-soa-unableToParseResponseBody","errorDescription":null}} + + var r Response + err := json.Unmarshal(resBytes, &r) + if err != nil { + c.logger.Error("error while parsing json", "request", "HTTP POLLING", "error", err.Error()) + } + + if r.Success { + var sr ServiceRequest + err := json.Unmarshal(r.Data, &sr) + if err != nil { + c.logger.Error("error while parsing json", "request", "HTTP POLLING", "error", err.Error()) + } + switch { + case sr.RemoteServiceState == "finished": + c.logger.Debug("Remote service request completed successfully", "request id", sr.ServiceRequestID) + break poolingLoop + case sr.RemoteServiceState == "started": + c.logger.Debug("Subaru API reports remote service request is in progress", "request id", sr.ServiceRequestID) + } + } else { + c.logger.Debug("Backend session expired, please try again") + break poolingLoop + } + attempts-- + time.Sleep(3 * time.Second) + } + } + } return resBytes } -// isResponseSuccessfull . -func (c *Client) isResponseSuccessfull(resp []byte) bool { - respParsed, err := gabs.ParseJSON(resp) - if err != nil { - c.logger.Debug("error while parsing json response", "error", err) - } +// // isResponseSuccessfull . +// func (c *Client) isResponseSuccessfull(resp []byte) bool { +// respParsed, err := gabs.ParseJSON(resp) +// if err != nil { +// c.logger.Debug("error while parsing json response", "error", err) +// } - success, ok := respParsed.Path("success").Data().(bool) - if !ok { - c.logger.Debug("response is not successful", "error", resp) - } +// success, ok := respParsed.Path("success").Data().(bool) +// if !ok { +// c.logger.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") - // } +// // 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 -} +// return success +// }