package mysubaru import ( "encoding/json" "errors" "io" "log/slog" "regexp" "slices" "sync" "time" "git.savin.nyc/alex/mysubaru/config" "resty.dev/v3" ) // Client represents a MySubaru API client that interacts with the MySubaru API. type Client struct { credentials config.Credentials httpClient *resty.Client country string // USA | CA contactMethods dataMap // List of contact methods for 2FA currentVin string listOfVins []string isAuthenticated bool isRegistered bool isAlive bool updateInterval int // 7200 fetchInterval int // 360 logger *slog.Logger sync.RWMutex } // New function creates a New MySubaru API client func New(config *config.Config) (*Client, error) { client := &Client{ credentials: config.MySubaru.Credentials, country: config.MySubaru.Region, updateInterval: 7200, fetchInterval: 360, logger: config.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 if ok, err := client.auth(); !ok { client.logger.Error("error while executing auth request", "request", "auth", "error", err.Error()) return nil, errors.New("error while executing auth request: " + err.Error()) } return client, nil } // auth authenticates the client with the MySubaru API using the provided credentials. func (c *Client) auth() (bool, error) { 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, err := c.execute(POST, reqURL, params, false) if err != nil { c.logger.Error("error while executing auth request", "request", "auth", "error", err.Error()) return false, errors.New("error while executing auth request: " + err.Error()) } c.logger.Debug("http request output", "request", "auth", "body", resp) var sd SessionData err = json.Unmarshal(resp.Data, &sd) if err != nil { c.logger.Error("error while parsing json", "request", "auth", "error", err.Error()) } // client.logger.Debug("unmarshaled json data", "request", "auth", "type", "sessionData", "body", sd) if !sd.DeviceRegistered { err := c.getContactMethods() if err != nil { c.logger.Error("error while getting contact methods", "request", "auth", "error", err.Error()) return false, errors.New("error while getting contact methods: " + err.Error()) } c.logger.Error("device is not registered", "request", "auth", "deviceId", c.credentials.DeviceID) return false, errors.New("device is not registered: " + c.credentials.DeviceID) } if sd.DeviceRegistered && sd.RegisteredDevicePermanent { c.isAuthenticated = true c.isRegistered = true c.isAlive = true } c.logger.Debug("MySubaru API client authenticated") if len(sd.Vehicles) > 0 { for _, vehicle := range sd.Vehicles { c.listOfVins = append(c.listOfVins, vehicle.Vin) } c.currentVin = c.listOfVins[0] } else { c.logger.Error("there are no vehicles associated with the account", "request", "auth", "error", "no vehicles found") return false, err } return true, nil } // SelectVehicle selects a vehicle by its VIN. If no VIN is provided, it uses the current VIN. func (c *Client) SelectVehicle(vin string) (*VehicleData, error) { if vin == "" { vin = c.currentVin } vinCheck(vin) params := map[string]string{ "vin": vin, "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] resp, err := c.execute(GET, reqURL, params, false) if err != nil { c.logger.Error("error while executing SelectVehicle request", "request", "SelectVehicle", "error", err.Error()) return nil, errors.New("error while executing SelectVehicle request: " + err.Error()) } // c.logger.Debug("http request output", "request", "SelectVehicle", "body", resp) var vd VehicleData err = json.Unmarshal(resp.Data, &vd) if err != nil { c.logger.Error("error while parsing json", "request", "SelectVehicle", "error", err.Error()) return nil, errors.New("error while parsing json while vehicle selection") } // c.logger.Debug("http request output", "request", "SelectVehicle", "body", resp) return &vd, nil } // GetVehicles retrieves a list of vehicles associated with the client's account. func (c *Client) GetVehicles() ([]*Vehicle, error) { var vehicles []*Vehicle for _, vin := range c.listOfVins { vehicle, err := c.GetVehicleByVin(vin) if err != nil { c.logger.Error("cannot get vehicle data", "request", "GetVehicles", "error", err.Error()) return nil, errors.New("cannot get vehicle data: " + err.Error()) } vehicles = append(vehicles, vehicle) } return vehicles, nil } // GetVehicleByVin retrieves a vehicle by its VIN from the client's list of vehicles. func (c *Client) GetVehicleByVin(vin string) (*Vehicle, error) { var vehicle *Vehicle if slices.Contains(c.listOfVins, vin) { params := map[string]string{ "vin": vin, "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] resp, err := c.execute(GET, reqURL, params, false) if err != nil { c.logger.Error("error while executing GetVehicleByVin request", "request", "GetVehicleByVin", "error", err.Error()) return nil, errors.New("error while executing GetVehicleByVin request: " + err.Error()) } // c.logger.Debug("http request output", "request", "GetVehicleByVin", "body", resp) var vd VehicleData err = json.Unmarshal(resp.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: 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.Troubles = make(map[string]Trouble) if vehicle.isEV() { vehicle.EV = true } else { vehicle.EV = false } vehicle.GetVehicleStatus() vehicle.GetVehicleCondition() vehicle.GetVehicleHealth() vehicle.GetClimatePresets() vehicle.GetClimateUserPresets() vehicle.GetClimateQuickPresets() return vehicle, nil } c.logger.Error("vin code is not in the list of the available vin codes", "request", "GetVehicleByVIN") return nil, errors.New("vin code is not in the list of the available vin codes") } // RefreshVehicles refreshes the list of vehicles associated with the client's account. // {"success":true,"dataName":"sessionData","data":{"sessionChanged":true,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751835464000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"0E154A99FA3D014D866840123E5E666A","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":{"firstName":"Tatiana","lastName":"Savin","title":"","suffix":"","email":"tanya@savin.nyc","address":"29a Marion Ave","address2":"","city":"New Providence","state":"NJ","zip":"07974-1906","cellularPhone":"","workPhone":"","homePhone":"4013050505","countryCode":"USA","relationshipType":null,"gender":"","dealerCode":null,"oemCustId":"CRM-41PLM-5TYE","createMysAccount":null,"sourceSystemCode":"mys","vehicles":[{"vin":"4S4BSETC4H3265676","siebelVehicleRelationship":"Previous Owner","primary":false,"oemCustId":"1-8K7OBOJ","status":""},{"vin":"4S4BSETC4H3265676","siebelVehicleRelationship":"Previous TM Subscriber","primary":false,"oemCustId":"1-8JY3UVS","status":"Inactive"},{"vin":"4S4BTGND8L3137058","siebelVehicleRelationship":"Previous TM Subscriber","primary":false,"oemCustId":"CRM-44UFUA14-V","status":"Draft"},{"vin":"4S4BTGPD0P3199198","siebelVehicleRelationship":"TM Subscriber","primary":true,"oemCustId":"CRM-41PLM-5TYE","status":"Active"}],"phone":"","zip5Digits":"07974","primaryPersonalCountry":"USA"},"email":"","firstName":"Tatiana","lastName":"Savin","zip":"07974-1906","oemCustId":"CRM-41PLM-5TYE","phone":""},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"4S4BTGPD0P3199198","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":"tanya@savin.nyc","firstName":"Tatiana","lastName":"Savin","subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":"07974-1906","oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":"","timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false,"vehicleBranded":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","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","currentVehicleIndex":0,"handoffToken":"$2a$08$EvONydxMhrhVgFsv4OgX3O8QaOu3naCFYex2Crqhl27cPQwJYXera$1751837730624","satelliteViewEnabled":true,"registeredDevicePermanent":true}} func (c *Client) RefreshVehicles() error { params := map[string]string{} reqURL := MOBILE_API_VERSION + apiURLs["API_REFRESH_VEHICLES"] resp, err := c.execute(GET, reqURL, params, false) if err != nil { c.logger.Error("error while executing RefreshVehicles request", "request", "RefreshVehicles", "error", err.Error()) return errors.New("error while executing RefreshVehicles request: " + err.Error()) } c.logger.Debug("http request output", "request", "RefreshVehicles", "body", resp) // var vd VehicleData // err = json.Unmarshal(resp.Data, &vd) // if err != nil { // c.logger.Error("error while parsing json", "request", "SelectVehicle", "error", err.Error()) // return errors.New("error while parsing json while vehicle selection") // } // c.logger.Debug("http request output", "request", "SelectVehicle", "body", resp) return nil } // RequestAuthCode requests an authentication code for two-factor authentication (2FA). // (?!^).(?=.*@) // (?!^): This is a negative lookbehind assertion. It ensures that the matched character is not at the beginning of the string. // .: This matches any single character (except newline, by default). // (?=.*@): This is a positive lookahead assertion. It ensures that the matched character is followed by any characters (.*) and then an "@" symbol. This targets the username part of the email address. func (c *Client) RequestAuthCode(email string) error { email, err := emailMasking(email) if err != nil { c.logger.Error("error while hiding email", "request", "RequestAuthCode", "error", err.Error()) return errors.New("error while hiding email: " + err.Error()) } if !containsValueInStruct(c.contactMethods, email) { c.logger.Error("email is not in the list of contact methods", "request", "RequestAuthCode", "email", email) return errors.New("email is not in the list of contact methods: " + email) } params := map[string]string{ "contactMethod": email, "languagePreference": "EN"} reqUrl := MOBILE_API_VERSION + apiURLs["API_2FA_SEND_VERIFICATION"] resp, err := c.execute(POST, reqUrl, params, false) if err != nil { c.logger.Error("error while executing RequestAuthCode request", "request", "RequestAuthCode", "error", err.Error()) return errors.New("error while executing RequestAuthCode request: " + err.Error()) } c.logger.Debug("http request output", "request", "RequestAuthCode", "body", resp) return nil } // SubmitAuthCode submits the authentication code received from the RequestAuthCode method. func (c *Client) SubmitAuthCode(code string, permanent bool) error { regex := regexp.MustCompile(`^\d{6}$`) if !regex.MatchString(code) { c.logger.Error("invalid verification code format", "request", "SubmitAuthCode", "code", code) return errors.New("invalid verification code format, must be 6 digits") } params := map[string]string{ "deviceId": c.credentials.DeviceID, "deviceName": c.credentials.DeviceName, "verificationCode": code} if permanent { params["rememberDevice"] = "on" } reqUrl := MOBILE_API_VERSION + apiURLs["API_2FA_AUTH_VERIFY"] resp, err := c.execute(POST, reqUrl, params, false) if err != nil { c.logger.Error("error while executing SubmitAuthCode request", "request", "SubmitAuthCode", "error", err.Error()) return errors.New("error while executing SubmitAuthCode request: " + err.Error()) } c.logger.Debug("http request output", "request", "SubmitAuthCode", "body", resp) // Device registration does not always immediately take effect time.Sleep(time.Second * 3) // Reauthenticate after submitting the code if ok, err := c.auth(); !ok { c.logger.Error("error while executing auth request", "request", "auth", "error", err.Error()) return errors.New("error while executing auth request: " + err.Error()) } return nil } // getContactMethods retrieves the available contact methods for two-factor authentication (2FA). // {"success":true,"dataName":"dataMap","data":{"userName":"a**x@savin.nyc","email":"t***a@savin.nyc"}} func (c *Client) getContactMethods() error { params := map[string]string{} reqUrl := MOBILE_API_VERSION + apiURLs["API_2FA_CONTACT"] resp, err := c.execute(POST, reqUrl, params, false) if err != nil { c.logger.Error("error while executing getContactMethods request", "request", "getContactMethods", "error", err.Error()) return errors.New("error while executing getContactMethods request: " + err.Error()) } c.logger.Debug("http request output", "request", "getContactMethods", "body", resp) var dm dataMap err = json.Unmarshal(resp.Data, &dm) if err != nil { c.logger.Error("error while parsing json", "request", "getContactMethods", "error", err.Error()) return errors.New("error while parsing json while getting contact methods: " + err.Error()) } c.contactMethods = dm c.logger.Debug("contact methods successfully retrieved", "request", "getContactMethods", "methods", dm) return nil } // IsAlive checks if the Client instance is alive func (c *Client) IsAlive() bool { return c.isAlive } // execute executes an HTTP request based on the method, URL, and parameters provided. func (c *Client) execute(method string, url string, params map[string]string, j bool) (*Response, error) { c.Lock() // defer timeTrack("[TIMETRK] Executing HTTP Request") var resp *resty.Response var err error // c.logger.Debug("executing http request", "method", method, "url", url, "params", params) // GET Requests if method == GET { resp, err = c.httpClient. R(). SetQueryParams(params). Get(url) if err != nil { c.logger.Error("error while executing GET request", "request", "execute", "method", method, "url", url, "error", err.Error()) return nil, err } c.logger.Debug("executed GET request", "method", method, "url", url, "params", params) } // POST Requests if method == POST { if j { // POST > JSON Body resp, err = c.httpClient. R(). SetBody(params). Post(url) if err != nil { c.logger.Error("error while executing POST request", "request", "execute", "method", method, "url", url, "error", err.Error()) return nil, err } } else { // POST > Form Data resp, err = c.httpClient. R(). SetFormData(params). Post(url) if err != nil { c.logger.Error("error while executing POST request", "request", "execute", "method", method, "url", url, "error", err.Error()) return nil, err } } c.logger.Debug("executed POST request", "method", method, "url", url, "params", params) } if resp.IsSuccess() { resBytes, err := io.ReadAll(resp.Body) if err != nil { c.logger.Error("error while getting body", "error", err.Error()) } c.logger.Debug("parsed http request output", "data", string(resBytes)) c.httpClient.SetCookies(resp.Cookies()) if r, ok := c.parseResponse(resBytes); ok { c.isAlive = true c.Unlock() return &r, nil } else { if r.DataName == "errorResponse" { var er ErrorResponse err := json.Unmarshal(r.Data, &er) if err != nil { c.logger.Error("error while parsing json", "request", "errorResponse", "error", err.Error()) } if _, ok := API_ERRORS[er.ErrorLabel]; ok { c.logger.Error("request got an error", "request", "execute", "method", method, "url", url, "label", er.ErrorLabel, "descrip[tion", er.ErrorDescription) } c.logger.Error("request got an unknown error", "request", "execute", "method", method, "url", url, "label", er.ErrorLabel, "descrip[tion", er.ErrorDescription) c.Unlock() return nil, errors.New("request is not successfull, HTTP code: " + resp.Status()) } c.logger.Error("request is not successfull", "request", "execute", "method", method, "url", url, "error", err.Error()) } } c.isAlive = false c.Unlock() return nil, errors.New("request is not successfull, HTTP code: " + resp.Status()) } // parseResponse parses the JSON response from the MySubaru API into a Response struct. func (c *Client) parseResponse(b []byte) (Response, bool) { var r Response err := json.Unmarshal(b, &r) if err != nil { c.logger.Error("error while parsing json", "error", err.Error()) return r, false } return r, true } // ValidateSession checks if the current session is valid by making a request to the vehicle status API. func (c *Client) ValidateSession() bool { reqURL := MOBILE_API_VERSION + apiURLs["API_VEHICLE_STATUS"] resp, err := c.execute(GET, reqURL, map[string]string{}, false) if err != nil { c.logger.Error("error while executing validateSession request", "request", "validateSession", "error", err.Error()) return false } c.logger.Debug("http request output", "request", "validateSession", "body", resp) return true } // func isPINRequired() {} // func getEVStatus() {} // func getRemoteOptionsStatus() {} // func getRemoteStartStatus() {} // func getSafetyStatus() {} // func getSubscriptionStatus() {} // validateSession . // TODO: add session validation process and add it to the proper functions // 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", "validateSession", "body", resp) // if r, ok := c.parseResponse(resp); ok { // c.logger.Error("error while parsing json", "request", "validateSession", "error", err.Error()) // return true // } else { // resp := c.auth() // return true // } // return false // } // 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"] // resp, _ := c.execute(POST, reqURL, 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.Duration()) // 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) // // c.logger.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) { // // logger.Debugf("LIST DEVICES OUTPUT >> %v\n", string(resp)) // // } // } // c.logger.Debug("parsed http request output", "data", r.Data) // ERROR // {"httpCode":500,"errorCode":"error","errorMessage":"org.springframework.web.HttpMediaTypeNotSupportedException - Content type 'application/x-www-form-urlencoded' not supported"} // {"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"4S4BTGND8L3137058_1640203129607_19_@NGTP","success":false,"cancelled":false,"remoteServiceType":"unlock","remoteServiceState":"started","subState":null,"errorCode":null,"result":null,"updateTime":null,"vin":"4S4BTGND8L3137058"}} // API_REMOTE_SVC_STATUS // {"success":false,"errorCode":"404-soa-unableToParseResponseBody","dataName":"errorResponse","data":{"errorLabel":"404-soa-unableToParseResponseBody","errorDescription":null}} // if js_resp["errorCode"] == sc.ERROR_SOA_403: // raise RemoteServiceFailure("Backend session expired, please try again") // if js_resp["data"]["remoteServiceState"] == "finished": // if js_resp["data"]["success"]: // _LOGGER.info("Remote service request completed successfully: %s", req_id) // return True, js_resp // _LOGGER.error( // "Remote service request completed but failed: %s Error: %s", // req_id, // js_resp["data"]["errorCode"], // ) // raise RemoteServiceFailure( // "Remote service request completed but failed: %s" % js_resp["data"]["errorCode"] // ) // if js_resp["data"].get("remoteServiceState") == "started": // _LOGGER.info( // "Subaru API reports remote service request is in progress: %s", // req_id, // ) // attempts_left -= 1 // await asyncio.sleep(2) // 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") // }