mirror of
https://github.com/bckelley/tconnectsync.git
synced 2026-08-24 03:34:12 -05:00
Add script to update events.json from minified url
This commit is contained in:
committed by
James Woglom
parent
594ee19450
commit
bf894bce02
Executable
+225
@@ -0,0 +1,225 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Sync event definitions from Tandem's official webapp and regenerate event classes.
|
||||||
|
|
||||||
|
Extracts the complete events.json from the JSON.parse() statement embedded in
|
||||||
|
Tandem's reports module JavaScript, updates local events.json, and regenerates
|
||||||
|
events.py with all event class definitions.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 scripts/sync_tandem_events.py [--output FILE] <URL>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python3 scripts/sync_tandem_events.py \\
|
||||||
|
https://modules.us.tandemdiabetes.com/webapp/modules/reports-module/v1.8.0/2451.97042bc1.chunk.js
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_EVENTS_FILE = "tconnectsync/eventparser/events.json"
|
||||||
|
DEFAULT_GENERATOR = "build_events.py"
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_module(url):
|
||||||
|
"""Fetch the minified JavaScript module from Tandem."""
|
||||||
|
print(f"Fetching Tandem module...", file=sys.stderr)
|
||||||
|
response = requests.get(url, timeout=30)
|
||||||
|
response.raise_for_status()
|
||||||
|
content = response.text
|
||||||
|
print(f"Fetched {len(content):,} bytes", file=sys.stderr)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def extract_events_json_from_parse(js_content):
|
||||||
|
"""
|
||||||
|
Extract the complete events.json from the JSON.parse() statement.
|
||||||
|
|
||||||
|
Finds: JSON.parse('{"events":{...}}')
|
||||||
|
And returns the parsed events dictionary.
|
||||||
|
"""
|
||||||
|
start = js_content.find("JSON.parse('")
|
||||||
|
if start < 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
start += len("JSON.parse('")
|
||||||
|
|
||||||
|
# Find the matching closing brace
|
||||||
|
brace_count = 0
|
||||||
|
end = start
|
||||||
|
escape_next = False
|
||||||
|
|
||||||
|
for i in range(start, len(js_content)):
|
||||||
|
char = js_content[i]
|
||||||
|
|
||||||
|
if escape_next:
|
||||||
|
escape_next = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if char == '\\':
|
||||||
|
escape_next = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if char == '{':
|
||||||
|
brace_count += 1
|
||||||
|
elif char == '}':
|
||||||
|
brace_count -= 1
|
||||||
|
if brace_count == 0:
|
||||||
|
end = i + 1
|
||||||
|
break
|
||||||
|
|
||||||
|
if brace_count != 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
json_str = js_content[start:end]
|
||||||
|
|
||||||
|
# Unescape the string
|
||||||
|
json_str = json_str.replace('\\"', '"')
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.loads(json_str)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_existing_events(filepath):
|
||||||
|
"""Load the existing events.json file."""
|
||||||
|
if not Path(filepath).exists():
|
||||||
|
return {"events": {}}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
print(f"Loaded {len(data.get('events', {}))} existing events", file=sys.stderr)
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not load existing events.json: {e}", file=sys.stderr)
|
||||||
|
return {"events": {}}
|
||||||
|
|
||||||
|
|
||||||
|
def merge_events(existing_data, extracted_data):
|
||||||
|
"""
|
||||||
|
Merge extracted events with existing events.
|
||||||
|
|
||||||
|
Keeps all existing events and updates/adds with extracted ones.
|
||||||
|
"""
|
||||||
|
existing = existing_data.get('events', {})
|
||||||
|
extracted = extracted_data.get('events', {})
|
||||||
|
|
||||||
|
before_count = len(existing)
|
||||||
|
|
||||||
|
# Add/update extracted events
|
||||||
|
existing.update(extracted)
|
||||||
|
|
||||||
|
after_count = len(existing)
|
||||||
|
added = after_count - before_count
|
||||||
|
|
||||||
|
if added > 0:
|
||||||
|
print(f"Added {added} new events from Tandem module", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(f"Updated {len(extracted)} events from Tandem module", file=sys.stderr)
|
||||||
|
|
||||||
|
return existing_data
|
||||||
|
|
||||||
|
|
||||||
|
def write_events_file(filepath, data):
|
||||||
|
"""Write events.json with proper formatting."""
|
||||||
|
if 'events' in data:
|
||||||
|
data['events'] = {
|
||||||
|
k: data['events'][k]
|
||||||
|
for k in sorted(data['events'].keys(), key=lambda x: int(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath = Path(filepath)
|
||||||
|
filepath.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Wrote {len(data.get('events', {}))} events to {filepath}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def regenerate_events_py(events_json_path, generator_name):
|
||||||
|
"""Regenerate events.py from updated events.json."""
|
||||||
|
events_dir = Path(events_json_path).parent
|
||||||
|
generator_path = events_dir / generator_name
|
||||||
|
|
||||||
|
if not generator_path.exists():
|
||||||
|
print(f"⚠ Generator not found at {generator_path}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Regenerating events.py...", file=sys.stderr)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, generator_name],
|
||||||
|
cwd=str(events_dir),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
# Write generated code to events.py
|
||||||
|
events_py = events_dir / 'events.py'
|
||||||
|
events_py.write_text(result.stdout)
|
||||||
|
print(f"✓ events.py regenerated ({len(result.stdout):,} bytes)", file=sys.stderr)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"✗ Generator failed: {result.stderr}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print("✗ Generator timed out", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error running generator: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Sync event definitions from Tandem and regenerate events.py'
|
||||||
|
)
|
||||||
|
parser.add_argument('url', help='URL to Tandem JavaScript module')
|
||||||
|
parser.add_argument('--output', default=DEFAULT_EVENTS_FILE, help='Output events.json path')
|
||||||
|
parser.add_argument('--no-generate', action='store_true', help='Skip events.py regeneration')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
js_content = fetch_module(args.url)
|
||||||
|
|
||||||
|
extracted_data = extract_events_json_from_parse(js_content)
|
||||||
|
if not extracted_data:
|
||||||
|
print("✗ Could not extract events.json from Tandem module", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
extracted_events = extracted_data.get('events', {})
|
||||||
|
print(f"✓ Extracted {len(extracted_events)} events from Tandem module", file=sys.stderr)
|
||||||
|
|
||||||
|
existing_data = load_existing_events(args.output)
|
||||||
|
merged_data = merge_events(existing_data, extracted_data)
|
||||||
|
write_events_file(args.output, merged_data)
|
||||||
|
|
||||||
|
if not args.no_generate:
|
||||||
|
regenerate_events_py(args.output, DEFAULT_GENERATOR)
|
||||||
|
|
||||||
|
print(f"\n✓ Done", file=sys.stderr)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"✗ Network error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -2263,8 +2263,8 @@
|
|||||||
"8": "EGV was successfully added to CGM subsystem array (e.g., not a duplicate)",
|
"8": "EGV was successfully added to CGM subsystem array (e.g., not a duplicate)",
|
||||||
"9": "OMR reading type",
|
"9": "OMR reading type",
|
||||||
"11": "Sensor Type (see CGMTxType enum)",
|
"11": "Sensor Type (see CGMTxType enum)",
|
||||||
"12": "Sensor Type (see CGMTxType enum) 2",
|
"12": "Sensor Type (see CGMTxType enum)",
|
||||||
"13": "Sensor Type (see CGMTxType enum) 3"
|
"13": "Sensor Type (see CGMTxType enum)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5413,8 +5413,8 @@ class LidCgmDataFsl3(BaseEvent):
|
|||||||
"8": "EGV was successfully added to CGM subsystem array (e.g., not a duplicate)",
|
"8": "EGV was successfully added to CGM subsystem array (e.g., not a duplicate)",
|
||||||
"9": "OMR reading type",
|
"9": "OMR reading type",
|
||||||
"11": "Sensor Type (see CGMTxType enum)",
|
"11": "Sensor Type (see CGMTxType enum)",
|
||||||
"12": "Sensor Type (see CGMTxType enum) 2",
|
"12": "Sensor Type (see CGMTxType enum)",
|
||||||
"13": "Sensor Type (see CGMTxType enum) 3"
|
"13": "Sensor Type (see CGMTxType enum)"
|
||||||
}
|
}
|
||||||
|
|
||||||
class EgvinfobitmaskBitmask(IntFlag):
|
class EgvinfobitmaskBitmask(IntFlag):
|
||||||
@@ -5426,9 +5426,9 @@ class LidCgmDataFsl3(BaseEvent):
|
|||||||
ValidAlgstateAlgstateIs100 = 2**7
|
ValidAlgstateAlgstateIs100 = 2**7
|
||||||
EgvWasSuccessfullyAddedToCgmSubsystemArrayE = 2**8
|
EgvWasSuccessfullyAddedToCgmSubsystemArrayE = 2**8
|
||||||
OmrReadingType = 2**9
|
OmrReadingType = 2**9
|
||||||
SensorTypeSeeCgmtxtypeEnum = 2**11
|
SensorTypeSeeCgmtxtypeEnum_11 = 2**11
|
||||||
SensorTypeSeeCgmtxtypeEnum2 = 2**12
|
SensorTypeSeeCgmtxtypeEnum_12 = 2**12
|
||||||
SensorTypeSeeCgmtxtypeEnum3 = 2**13
|
SensorTypeSeeCgmtxtypeEnum_13 = 2**13
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def egvInfoBitmask(self):
|
def egvInfoBitmask(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user