Skip to content

Commit

Permalink
Fixed MyBMWQuotaError due to missing hCaptcha
Browse files Browse the repository at this point in the history
  • Loading branch information
Jargendas authored Dec 31, 2024
1 parent 7552689 commit 3b0136c
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ typings/

# car renders
car*.png

# auth data
*.json
4 changes: 3 additions & 1 deletion MMM-MyBMW.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

Module.register("MMM-MyBMW", {
defaults: {
hCaptchaToken: '',
authStorePath: 'modules/MMM-MyBMW/mybmw_auth.json',
region: 'rest',
refresh: 15,
vehicleOpacity: 0.75,
Expand Down Expand Up @@ -33,7 +35,7 @@ Module.register("MMM-MyBMW", {
this.timer = null;
this.sendSocketNotification("MMM-MYBMW-GET", {
instanceId: this.identifier,
vin: this.config.vin
vin: this.config.vin
});

var self = this;
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Go to the MagicMirror/config directory and edit the config.js file. Add the modu

You'll need your MyBMW email and password, and your car's VIN number.

Additionally, an hCaptcha token is required on first startup. The session data will be saved afterwards, so it should only be required once. You can generate this token [here](https://bimmer-connected.readthedocs.io/en/stable/captcha/north_america.html) for North America or [here](https://bimmer-connected.readthedocs.io/en/stable/captcha/rest_of_world.html) for the rest of the world.

Enter these details in the config.js for your MagicMirror installation:

{
Expand All @@ -37,7 +39,8 @@ Enter these details in the config.js for your MagicMirror installation:
config: {
email: "email@example.com",
password: "myComplexPassword",
vin: "XXXXXXXXXXXXXXXXX"
vin: "XXXXXXXXXXXXXXXXX",
hCaptchaToken: "<token>",
}
},

Expand All @@ -64,6 +67,10 @@ The module has a few configuration options:
<td><code>vin</code></td>
<td>Your car's VIN code, required.<br /><br /><strong>Default: </strong><code>undefined</code></td>
</tr>
<tr>
<td><code>hCaptchaToken</code></td>
<td>An hCaptcha token for authentication, required on first startup. Can be generated <a href="https://bimmer-connected.readthedocs.io/en/stable/captcha/north_america.html">here</a> for North America or <a href="https://bimmer-connected.readthedocs.io/en/stable/captcha/rest_of_world.html">here</a> for the rest of the world.<br /><br /><strong>Default: </strong><code>''</code></td>
</tr>
<tr>
<td><code>region</code></td>
<td>Must be set to the region your car is operating in, required for the MyBMW API. Can be <code>us</code>, <code>cn</code>, or <code>rest</code>.<br /><br /><strong>Default: </strong><code>rest</code></td>
Expand Down Expand Up @@ -100,9 +107,14 @@ The module has a few configuration options:
<td><code>lastUpdatedText</code></td>
<td>The text to be shown before the last updated timestamp. <br /><br /><strong>Default: </strong><code>last updated</code>
</tr>
<tr>
<td><code>authStorePath</code></td>
<td>Path to store the auth data to for future access without a new hCaptcha token. <br /><br /><strong>Default: </strong><code>modules/MMM-MyBMW/mybmw_auth.json</code></td>
</tr>
</tbody>
</table>

## Changelog

**2024-03-12** Forked from MMM-BMWConnected and migrated to MyBMW via bimmer_connected.
**2024-03-12** Forked from MMM-BMWConnected and migrated to MyBMW via bimmer_connected.<br />
**2024-12-30** Implemented hCaptcha handling which is now required for bimmer_connected operation.
59 changes: 55 additions & 4 deletions getMyBMWData.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,63 @@
import sys
import os
import json
import time
from pathlib import Path
from typing import Dict, Optional
from bimmer_connected.account import MyBMWAccount
from bimmer_connected.api.regions import Regions
from bimmer_connected.vehicle.vehicle import VehicleViewDirection
from bimmer_connected.vehicle.doors_windows import LockState

async def main(email, password, vin, region):
# Load and store functions from bimmer_connected cli
def load_oauth_store_from_file(oauth_store: Path, account: MyBMWAccount) -> Dict:
"""Load the OAuth details from a file if it exists."""
if not oauth_store.exists():
return {}
try:
oauth_data = json.loads(oauth_store.read_text())
except json.JSONDecodeError:
return {}

session_id_timestamp = oauth_data.pop("session_id_timestamp", None)
# Pop session_id every 14 days so it gets recreated
if (time.time() - (session_id_timestamp or 0)) > 14 * 24 * 60 * 60:
oauth_data.pop("session_id", None)
session_id_timestamp = None

account.set_refresh_token(**oauth_data)

return {**oauth_data, "session_id_timestamp": session_id_timestamp}


def store_oauth_store_to_file(
oauth_store: Path, account: MyBMWAccount, session_id_timestamp: Optional[float] = None
) -> None:
"""Store the OAuth details to a file."""
oauth_store.parent.mkdir(parents=True, exist_ok=True)
oauth_store.write_text(
json.dumps(
{
"refresh_token": account.config.authentication.refresh_token,
"gcid": account.config.authentication.gcid,
"access_token": account.config.authentication.access_token,
"session_id": account.config.authentication.session_id,
"session_id_timestamp": session_id_timestamp or time.time(),
}
),
)

async def main(email, password, vin, region='row', hcaptcha_token=None, oauth_store_path='modules/MMM-MyBMW/mybmw_auth.json'):
if (region == 'cn'):
region = Regions.CHINA
elif (region == 'us'):
region = Regions.NORTH_AMERICA
else:
region = Regions.REST_OF_WORLD

account = MyBMWAccount(email, password, region)
account = MyBMWAccount(email, password, region, hcaptcha_token=hcaptcha_token)
oauth_store_data = load_oauth_store_from_file(Path(oauth_store_path), account)

await account.get_vehicles()
vehicle = account.get_vehicle(vin)

Expand Down Expand Up @@ -45,10 +88,18 @@ async def main(email, password, vin, region):
print(json.dumps(data))
sys.stdout.flush()

store_oauth_store_to_file(Path(oauth_store_path), account, oauth_store_data.get("session_id_timestamp"))

region = 'rest'
hcaptcha_token = None
oauth_store_path = 'modules/MMM-MyBMW/mybmw_auth.json'
if (len(sys.argv) > 4):
region = sys.argv[4]
if (len(sys.argv) > 5):
hcaptcha_token = sys.argv[5]
if (len(sys.argv) > 6):
oauth_store_path = sys.argv[6]
if (len(sys.argv) < 4):
print('Usage: python getMyBMWData.py <email> <password> <vin> <region:us|cn|rest>')
print('Usage: python getMyBMWData.py <email> <password> <vin> <region:us|cn|rest (Optional)> <hCaptcha Token (Optional)> <OAuth2 Session Store Path (Optional)>')
else:
asyncio.run(main(sys.argv[1], sys.argv[2], sys.argv[3], region))
asyncio.run(main(sys.argv[1], sys.argv[2], sys.argv[3], region, hcaptcha_token, oauth_store_path))
19 changes: 14 additions & 5 deletions node_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,37 @@ module.exports = NodeHelper.create({
console.log("Starting node_helper for module: " + this.name);
this.bmwInfo = {};
this.config = {};
this.resourceLocked = false;
},

socketNotificationReceived: function (notification, payload) {
socketNotificationReceived: async function (notification, payload) {

var self = this;
var vin = payload.vin;

if (notification == "MMM-MYBMW-CONFIG") {
self.config[vin] = payload;
self.bmwInfo[vin] = null;
} else if (notification == "MMM-MYBMW-GET") {
const config = self.config[vin];
const config = self.config[vin];

while (self.resourceLocked) {
console.log('Resource is locked, waiting...');
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
await delay(10000);
}
self.resourceLocked = true;
const pythonProcess = spawn('python3',["modules/MMM-MyBMW/getMyBMWData.py", config.email, config.password, config.vin, config.region, config.hCaptchaToken, config.authStorePath]);

const pythonProcess = spawn('python3',["modules/MMM-MyBMW/getMyBMWData.py", config.email, config.password, config.vin, config.region]);

pythonProcess.stdout.on('data', (data) => {
self.bmwInfo[vin] = JSON.parse(data);
self.sendResponse(payload);
self.resourceLocked = false;
});

pythonProcess.stderr.on('data', (data) => {
console.error(`bimmer_connected error: ${data}`);
self.resourceLocked = false;
});

}
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
bimmer-connected
bimmer-connected>=0.17.0

0 comments on commit 3b0136c

Please sign in to comment.