So, hab es nun selbst zusammen gebracht - Für alle die auch gerne NodeRed verwenden wollen:
[{"id":"fd45e125eed8839a","type":"function","z":"8f1e61194be045a8","name":"getAllCarData","func":"// Code added here will be run once\n// whenever the node is started.\n\nclass Polestar {\n #credentials = {\n email: null,\n password: null,\n }\n\n #token = {\n access: null,\n refresh: null,\n expires: null,\n }\n\n #vehicle = {\n vin: null,\n id: null,\n }\n\n constructor(email, password) {\n if (!email || !password) {\n throw new Error(\"Email and password must be provided\")\n }\n this.#credentials.email = email\n this.#credentials.password = password\n }\n\n async login() {\n const { pathToken, cookie } = await this.#getLoginFlowTokens()\n const tokenRequestCode = await this.#performLogin(pathToken, cookie)\n const apiCreds = await this.#getApiToken(tokenRequestCode)\n await this.#storeToken(apiCreds)\n if (!await this.#checkAuthenticated()) {\n throw new Error(\"Login failed, token is not valid\")\n }\n }\n\n async #storeToken(token) {\n const apiCreds = token\n\n const tokenExpiryTime = new Date()\n const secondsToAdd = apiCreds.expires_in\n tokenExpiryTime.setSeconds(tokenExpiryTime.getSeconds() + (secondsToAdd - 120))\n\n this.#token.access = apiCreds.access_token\n this.#token.refresh = apiCreds.refresh_token\n this.#token.expires = tokenExpiryTime\n }\n\n async #checkAuthenticated() {\n if (!this.#token.access || !this.#token.refresh || !this.#token.expires) {\n throw new Error(\"Not logged in\")\n }\n if (this.#token.expires < new Date()) {\n const apiCreds = await this.#refreshToken()\n this.#storeToken(apiCreds)\n }\n\n if (await this.#checkTokenValidity()) {\n return true\n } else {\n throw new Error(\"Token is not valid or refresh token has expired\")\n }\n\n }\n\n async #refreshToken() {\n console.log(\"Refreshing token\")\n const response = await axios.post(\n \"https://pc-api.polestar.com/eu-north-1/auth\",\n \"{\\\"query\\\":\\\"\\\\n query refreshAuthToken($token: String!) {\\\\n refreshAuthToken(token: $token) {\\\\n access_token\\\\n expires_in\\\\n id_token\\\\n refresh_token\\\\n }\\\\n }\\\\n\\\",\\\"variables\\\":{\\\"token\\\":\\\"\" + this.#token.refresh + \"\\\"}}\",\n {\n headers: {\n \"cache-control\": \"no-cache\",\n \"content-type\": \"application/json\",\n \"Authorization\": \"Bearer \" + this.#token.access,\n pragma: \"no-cache\",\n },\n maxRedirects: 0,\n validateStatus: function (status) {\n return true\n },\n }\n )\n const data = await response.data\n const apiCreds = data.data.refreshAuthToken\n return {\n access_token: apiCreds.access_token,\n refresh_token: apiCreds.refresh_token,\n expires_in: apiCreds.expires_in,\n }\n }\n\n async #checkTokenValidity() {\n const response = await axios.get(\n \"https://pc-api.polestar.com/eu-north-1/my-star/?query=query%20introspectToken(%24token%3A%20String!)%20%7B%0A%20%20introspectToken(token%3A%20%24token)%20%7B%0A%20%20%20%20active%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=introspectToken&variables=%7B%22token%22%3A%22\" + this.#token.access + \"%22%7D\",\n {\n headers: {\n \"cache-control\": \"no-cache\",\n \"content-type\": \"application/json\",\n \"Authorization\": \"Bearer \" + this.#token.access,\n pragma: \"no-cache\",\n },\n maxRedirects: 0,\n validateStatus: function (status) {\n return true\n },\n }\n )\n const data = await response.data.data\n if (!data.introspectToken || !data.introspectToken.active) {\n return false\n } else {\n return true\n }\n }\n\n async #performLogin(pathToken, cookie) {\n const response = await axios.post(\n \"https://polestarid.eu.polestar.com/as/\" + pathToken + \"/resume/as/authorization.ping?client_id=polmystar\",\n {\n \"pf.username\": this.#credentials.email,\n \"pf.pass\": this.#credentials.password,\n },\n {\n headers: {\n \"cache-control\": \"no-cache\",\n \"content-type\": \"application/x-www-form-urlencoded\",\n pragma: \"no-cache\",\n cookie: cookie,\n },\n maxRedirects: 0,\n validateStatus: function (status) {\n return true\n },\n }\n )\n\n const redirectUrl = response.headers.location\n const regex = /code=([^&]+)/\n const match = redirectUrl.match(regex)\n const tokenRequestCode = match ? match[1] : null\n\n return tokenRequestCode\n }\n\n async #getLoginFlowTokens() {\n const response = await axios.get(\n \"https://polestarid.eu.polestar.com/as/authorization.oauth2?response_type=code&client_id=polmystar&redirect_uri=https%3A%2F%2Fwww.polestar.com%2Fsign-in-callback&scope=openid%20profile%20email%20customer:attributes%20customer:attributes:write\",\n {\n headers: {\n \"cache-control\": \"no-cache\",\n pragma: \"no-cache\",\n },\n referrerPolicy: \"strict-origin-when-cross-origin\",\n body: null,\n maxRedirects: 0,\n method: \"GET\",\n validateStatus: function (status) {\n return true\n },\n }\n )\n const data = await response\n const redirectUrl = response.headers.location\n const regex = /resumePath=(\\w+)/\n const match = redirectUrl.match(regex)\n const pathToken = match ? match[1] : null\n const cookies = response.headers[\"set-cookie\"]\n const cookie = cookies[0].split('; ')[0] + \";\"\n return {\n pathToken: pathToken,\n cookie: cookie\n }\n }\n\n async #getApiToken(tokenRequestCode) {\n const response = await axios.get(\n \"https://pc-api.polestar.com/eu-north-1/auth/?query=query%20getAuthToken(%24code%3A%20String!)%20%7B%0A%20%20getAuthToken(code%3A%20%24code)%20%7B%0A%20%20%20%20id_token%0A%20%20%20%20access_token%0A%20%20%20%20refresh_token%0A%20%20%20%20expires_in%0A%20%20%7D%0A%7D%0A&operationName=getAuthToken&variables=%7B%22code%22%3A%22\" + tokenRequestCode + \"%22%7D\",\n {\n headers: {\n \"cache-control\": \"no-cache\",\n \"content-type\": \"application/json\",\n pragma: \"no-cache\",\n },\n maxRedirects: 0,\n validateStatus: function (status) {\n return true\n },\n }\n )\n const data = await response.data\n const apiCreds = data.data.getAuthToken\n return {\n access_token: apiCreds.access_token,\n refresh_token: apiCreds.refresh_token,\n expires_in: apiCreds.expires_in,\n }\n }\n\n async getVehicles() {\n if (!await this.#checkAuthenticated()) {\n throw new Error(\"Not authenticated\")\n }\n const response = await axios.get(\n \"https://pc-api.polestar.com/eu-north-1/my-star/?query=query%20getCars%20%7B%0A%20%20getConsumerCarsV2%20%7B%0A%20%20%20%20vin%0A%20%20%20%20internalVehicleIdentifier%0A%20%20%20%20modelYear%0A%20%20%20%20content%20%7B%0A%20%20%20%20%20%20model%20%7B%0A%20%20%20%20%20%20%20%20code%0A%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20images%20%7B%0A%20%20%20%20%20%20%20%20studio%20%7B%0A%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20angles%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20hasPerformancePackage%0A%20%20%20%20registrationNo%0A%20%20%20%20deliveryDate%0A%20%20%20%20currentPlannedDeliveryDate%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=getCars&variables=%7B%7D\",\n {\n headers: {\n \"cache-control\": \"no-cache\",\n \"content-type\": \"application/json\",\n \"Authorization\": \"Bearer \" + this.#token.access,\n pragma: \"no-cache\",\n },\n maxRedirects: 0,\n }\n )\n if (!response.data.data.getConsumerCarsV2) {\n throw new Error(\"No vehicles found\")\n }\n const vehicles = response.data.data.getConsumerCarsV2\n return vehicles\n }\n\n async setVehicle(vin) {\n if (!await this.#checkAuthenticated()) {\n throw new Error(\"Not authenticated\")\n }\n const vehicles = await this.getVehicles()\n let vehicle = null\n if (vin) {\n vehicle = vehicles.find((vehicle) => vehicle.vin === vin)\n } else {\n vehicle = vehicles[0]\n }\n if (!vehicle) {\n throw new Error(\"Vehicle not found\")\n }\n this.#vehicle.vin = vehicle.vin\n this.#vehicle.id = vehicle.internalVehicleIdentifier\n\n return this.#vehicle\n }\n\n async getBattery() {\n if (!await this.#checkAuthenticated()) {\n throw new Error(\"Not authenticated\")\n }\n\n if (!this.#vehicle.vin) {\n throw new Error(\"No vehicle selected\")\n }\n\n const response = await axios.get(\n \"https://pc-api.polestar.com/eu-north-1/mystar-v2?query=query%20GetBatteryData(%24vin%3A%20String!)%20%7B%0A%20%20getBatteryData(vin%3A%20%24vin)%20%7B%0A%20%20%20%20averageEnergyConsumptionKwhPer100Km%0A%20%20%20%20batteryChargeLevelPercentage%0A%20%20%20%20chargerConnectionStatus%0A%20%20%20%20chargingCurrentAmps%0A%20%20%20%20chargingPowerWatts%0A%20%20%20%20chargingStatus%0A%20%20%20%20estimatedChargingTimeMinutesToTargetDistance%0A%20%20%20%20estimatedChargingTimeToFullMinutes%0A%20%20%20%20estimatedDistanceToEmptyKm%0A%20%20%20%20estimatedDistanceToEmptyMiles%0A%20%20%20%20eventUpdatedTimestamp%20%7B%0A%20%20%20%20%20%20iso%0A%20%20%20%20%20%20unix%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=GetBatteryData&variables=%7B%22vin%22%3A%22\" + this.#vehicle.vin + \"%22%7D\",\n {\n headers: {\n \"cache-control\": \"no-cache\",\n \"content-type\": \"application/json\",\n \"Authorization\": \"Bearer \" + this.#token.access,\n pragma: \"no-cache\",\n },\n maxRedirects: 0,\n }\n )\n const data = await response.data.data.getBatteryData\n return data\n }\n\n async getOdometer() {\n if (!await this.#checkAuthenticated()) {\n throw new Error(\"Not authenticated\")\n }\n\n if (!this.#vehicle.vin) {\n throw new Error(\"No vehicle selected\")\n }\n\n const response = await axios.get(\n \"https://pc-api.polestar.com/eu-north-1/mystar-v2?query=query%20GetOdometerData(%24vin%3A%20String!)%20%7B%0A%20%20getOdometerData(vin%3A%20%24vin)%20%7B%0A%20%20%20%20averageSpeedKmPerHour%0A%20%20%20%20eventUpdatedTimestamp%20%7B%0A%20%20%20%20%20%20iso%0A%20%20%20%20%20%20unix%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20odometerMeters%0A%20%20%20%20tripMeterAutomaticKm%0A%20%20%20%20tripMeterManualKm%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=GetOdometerData&variables=%7B%22vin%22%3A%22\" + this.#vehicle.vin + \"%22%7D\",\n {\n headers: {\n \"cache-control\": \"no-cache\",\n \"content-type\": \"application/json\",\n \"Authorization\": \"Bearer \" + this.#token.access,\n pragma: \"no-cache\",\n },\n maxRedirects: 0,\n }\n )\n const data = await response.data.data.getOdometerData\n return data\n }\n}\n\n\n\nconst polestar = new Polestar(\"DEINE EMAIL\", \"DEIN PASSWORD\");\nawait polestar.login();\nconst vehicles = await polestar.getVehicles();\nawait polestar.setVehicle(vehicles[0].vin);\nconst batt = await polestar.getBattery();\nconst odo = await polestar.getOdometer();\n\nmsg.odo = odo;\nmsg.vehicles = vehicles;\nmsg.bat = batt;\nmsg.payload = vehicles[0].content.model.name;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"axios","module":"axios"}],"x":700,"y":200,"wires":[["cd43d91816497c1e","5667b87be88d9f5d","34467ccf11dbacf2","a0aec7100a98d524","d5f544c8916f6ffd","9737340fef439758","377e31a58cc94d81","d59067120006bbd4"]]},{"id":"bf870353702a3547","type":"inject","z":"8f1e61194be045a8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":440,"y":200,"wires":[["fd45e125eed8839a"]]},{"id":"cd43d91816497c1e","type":"debug","z":"8f1e61194be045a8","name":"debug 31","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":980,"y":120,"wires":[]},{"id":"71042cdc04822d04","type":"debug","z":"8f1e61194be045a8","name":"Soc","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1350,"y":200,"wires":[]},{"id":"5667b87be88d9f5d","type":"function","z":"8f1e61194be045a8","name":"SoC","func":"msg.payload = msg.bat.batteryChargeLevelPercentage;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":200,"wires":[["71042cdc04822d04"]]},{"id":"a0aec7100a98d524","type":"function","z":"8f1e61194be045a8","name":"Verbrauch/100km","func":"msg.payload = msg.bat.averageEnergyConsumptionKwhPer100Km\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":300,"wires":[["00ba973a886207f7"]]},{"id":"d5f544c8916f6ffd","type":"function","z":"8f1e61194be045a8","name":"Steckerstatus","func":"msg.payload = msg.bat.chargerConnectionStatus\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":980,"y":350,"wires":[["d4237b28ebf84db4"]]},{"id":"34467ccf11dbacf2","type":"function","z":"8f1e61194be045a8","name":"Distance","func":"msg.payload = msg.bat.estimatedDistanceToEmptyKm\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":960,"y":250,"wires":[["2ca76bfddd6b8b9e"]]},{"id":"9737340fef439758","type":"function","z":"8f1e61194be045a8","name":"Restladezeit","func":"msg.payload = msg.bat.estimatedChargingTimeToFullMinutes;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":970,"y":400,"wires":[["97cfe32175d2262d"]]},{"id":"377e31a58cc94d81","type":"function","z":"8f1e61194be045a8","name":"Kilometerstand","func":"msg.payload = msg.odo.odometerMeters / 1000;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":980,"y":450,"wires":[["4fc69f5975c3c82c"]]},{"id":"d59067120006bbd4","type":"function","z":"8f1e61194be045a8","name":"Durchschnitsgeschwindigkeit","func":"msg.payload = msg.odo.averageSpeedKmPerHour;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1020,"y":500,"wires":[["fa41b75b538a01b1"]]},{"id":"2ca76bfddd6b8b9e","type":"debug","z":"8f1e61194be045a8","name":"Reichweite","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1370,"y":250,"wires":[]},{"id":"00ba973a886207f7","type":"debug","z":"8f1e61194be045a8","name":"Durchschnitsverbrauch","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1400,"y":300,"wires":[]},{"id":"d4237b28ebf84db4","type":"debug","z":"8f1e61194be045a8","name":"Steckerstatus","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1380,"y":350,"wires":[]},{"id":"4fc69f5975c3c82c","type":"debug","z":"8f1e61194be045a8","name":"Kilometerstand","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1380,"y":450,"wires":[]},{"id":"fa41b75b538a01b1","type":"debug","z":"8f1e61194be045a8","name":"Durchschnitsgeschwindigkeit","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1420,"y":500,"wires":[]},{"id":"97cfe32175d2262d","type":"debug","z":"8f1e61194be045a8","name":"Restladezeit","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1370,"y":400,"wires":[]}]