Compare commits

...
6 Commits
Author SHA1 Message Date
James Woglom 822589e4b2 more ignore 2024-09-08 15:11:23 -04:00
James Woglom 90f2406b4f ignore more unrelated pipenv check failures 2024-09-08 15:10:20 -04:00
James Woglom 9d8554bd03 to str 2024-09-08 15:08:34 -04:00
James Woglom c60b19dbed handle WAF block 2024-09-08 15:07:54 -04:00
James Woglom 492963e240 bump to v1.0.0 2024-09-08 15:02:50 -04:00
James Woglom c9f6362928 Work-around for broken web-based auth method 2024-09-08 15:01:42 -04:00
4 changed files with 42 additions and 10 deletions
+5 -1
View File
@@ -42,7 +42,11 @@ jobs:
--ignore 61893 \
--ignore 61601 \
--ignore 62044 \
--ignore 67599
--ignore 67599 \
--ignore 72083 \
--ignore 71064 \
--ignore 71608 \
--ignore 72236
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata]
name = tconnectsync
version = 0.9.8
version = 1.0.0
author = James Woglom
author_email = j@wogloms.net
description = Syncs Tandem t:connect pump data to Nightscout for the t:slim X2
+10 -4
View File
@@ -29,6 +29,10 @@ class AndroidApi:
ANDROID_API_USERNAME = base64.b64decode('QzIzMzFDRDYtRDQ1MC00OTVFLTlDMTktNjcyMTUyMzBDODVD').decode()
ANDROID_API_PASSWORD = base64.b64decode('dHo0MzNLVzVRREM5VjdmIXo2QF4ybyZZNlNHR1lo').decode()
# These credentials are used by tconnect web
TCONNECT_WEB_USERNAME = base64.b64decode('M0U2MzU3QkEtRjYyNS00REQyLUI2NUYtNEI1RTgxNDRBQTZG').decode()
TCONNECT_WEB_PASSWORD = base64.b64decode('cUMyaXFIc2w3OFFoR0RYdCpMenFwb1pxZTl3eHN6').decode()
ANDROID_USER_AGENT = 'Dalvik/2.1.0 (Linux; U; Android 12; Pixel 4a Build/SP2A.220305.012)'
# These tokens are separate from the "standard" tdcservices API
@@ -65,15 +69,17 @@ class AndroidApi:
raise ApiLoginException(r.status_code, 'Received HTTP %s during login: %s' % (r.status_code, r.text))
j = r.json()
if "user" not in j or not j["user"]:
raise ApiException(r.status_code, 'No user details present in AndroidApi oauth response: %s' % r.text)
# tconnect web returns a null user
# if "user" not in j or not j["user"]:
# raise ApiException(r.status_code, 'No user details present in AndroidApi oauth response: %s' % r.text)
self.accessToken = j["accessToken"]
self.accessTokenExpiresAt = j["accessTokenExpiresAt"]
# NOTE: the refresh token is currently unused, instead a new access
# token is obtained from scratch by re-logging in when it expires.
self.refreshToken = j["refreshToken"]
self.refreshTokenExpiresAt = j["refreshTokenExpiresAt"]
if "refreshToken" in j and "refreshTokenExpiresAt" in j:
self.refreshToken = j["refreshToken"]
self.refreshTokenExpiresAt = j["refreshTokenExpiresAt"]
self.userId = j["user"]["id"]
logger.info("Logged in to AndroidApi successfully (expiration: %s, %s)" % (self.accessTokenExpiresAt, timeago(self.accessTokenExpiresAt)))
+26 -4
View File
@@ -14,7 +14,7 @@ class ControlIQApi:
BASE_URL = 'https://tdcservices.tandemdiabetes.com/'
LOGIN_URL = 'https://tconnect.tandemdiabetes.com/login.aspx?ReturnUrl=%2f'
LAST_CONFIRMED_SOFTWARE_VERSION = 't:connect 7.17.1.2'
LAST_CONFIRMED_SOFTWARE_VERSION = 't:connect 7.17.2.3'
userGuid = None
accessToken = None
@@ -51,9 +51,21 @@ class ControlIQApi:
raise ApiException(fwd.status_code, 'Error retrieving t:connect login cookies.')
self.userGuid = req.cookies['UserGUID']
self.accessToken = req.cookies['accessToken']
self.accessTokenExpiresAt = req.cookies['accessTokenExpiresAt']
logger.info("Logged in to ControlIQApi successfully (expiration: %s, %s)" % (self.accessTokenExpiresAt, timeago(self.accessTokenExpiresAt)))
if 'accessToken' in req.cookies and 'accessTokenExpiresAt' in req.cookies:
self.accessToken = req.cookies['accessToken']
self.accessTokenExpiresAt = req.cookies['accessTokenExpiresAt']
logger.info("Logged in to ControlIQApi successfully via accessToken cookie (expiration: %s, %s)" % (self.accessTokenExpiresAt, timeago(self.accessTokenExpiresAt)))
else:
logger.info("No accessToken cookie found when logging in to ControlIQApi. Triggering AndroidApi auth")
from .android import AndroidApi
android = AndroidApi(email, password)
self.accessToken = android.accessToken
self.accessTokenExpiresAt = android.accessTokenExpiresAt
logger.info("Logged in to AndroidApi successfully via accessToken param (expiration: %s, %s)" % (self.accessTokenExpiresAt, timeago(self.accessTokenExpiresAt)))
self.loginSession = s
return True
@@ -74,6 +86,13 @@ class ControlIQApi:
contents = "%s[SNIP]%s" % (contents[:500], contents[-500:])
logger.info("BeautifulSoup parsed contents: %s" % contents)
pass
if not soup.select_one("#__VIEWSTATE"):
enc_contents = str(soup.encode_contents())
if "Web Page Blocked!" in enc_contents or "Attack ID:" in enc_contents:
logger.warn("Being ratelimited/blocked by web application firewall. Sleeping for 30 minutes before retrying.")
logger.info("BeautifulSoup parsed contents: %s" % enc_contents)
time.sleep(1800)
exit(1)
return {
"__LASTFOCUS": "",
"__EVENTTARGET": "ctl00$ContentBody$LoginControl$linkLogin",
@@ -96,6 +115,9 @@ class ControlIQApi:
return None
def needs_relogin(self):
if not self.accessTokenExpiresAt:
return False
diff = (arrow.get(self.accessTokenExpiresAt) - arrow.get())
return (diff.seconds <= 5 * 60)