summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/constants/constants.py110
-rw-r--r--app/energy_request/energy_request.py78
-rw-r--r--app/energy_service.py10
-rw-r--r--app/login/login.py53
4 files changed, 251 insertions, 0 deletions
diff --git a/app/constants/constants.py b/app/constants/constants.py
new file mode 100644
index 0000000..f3ab71d
--- /dev/null
+++ b/app/constants/constants.py
@@ -0,0 +1,110 @@
+from app.login.credentials import *
+
+# TODO: Organize this better
+
+# Shared
+genericRequestHeaders = {
+ # set csrftoken
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
+ 'Accept-Language': 'en-US,en;q=0.9,la;q=0.8',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json; charset=UTF-8',
+ 'DNT': '1',
+ 'Origin': 'https://myaccount.cityutilities.net',
+ 'Pragma': 'no-cache',
+ 'Referer': 'https://myaccount.cityutilities.net/',
+ 'Sec-Fetch-Dest': 'empty',
+ 'Sec-Fetch-Mode': 'cors',
+ 'Sec-Fetch-Site': 'same-origin',
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'isajax': '1',
+ 'sec-ch-ua': '"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Linux"',
+}
+################################################################
+# Login
+loginPageHeaders = {
+ # no setting needed
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+ 'Accept-Language': 'en-US,en;q=0.9,la;q=0.8',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ 'DNT': '1',
+ 'Pragma': 'no-cache',
+ 'Sec-Fetch-Dest': 'document',
+ 'Sec-Fetch-Mode': 'navigate',
+ 'Sec-Fetch-Site': 'none',
+ 'Sec-Fetch-User': '?1',
+ 'Upgrade-Insecure-Requests': '1',
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
+ 'sec-ch-ua': '"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Linux"',
+}
+
+loginRequestJson = {
+ # no setting needed
+ 'username': username,
+ 'password': password,
+ 'rememberme': False,
+ 'calledFrom': 'LN',
+ 'ExternalLoginId': '',
+ 'LoginMode': '1',
+ 'utilityAcountNumber': '',
+}
+################################################################
+# Usage
+
+usageRequestCookies = {
+ # set the following:
+ # ApplicationGatewayAffinityCORS
+ # ApplicationGatewayAffinity
+ # ASP.NET_SessionId
+ # SCP
+ 'HomeInfoStatus': 'JTvtoeyEoms3X37GlOdLTgqBBOA=',
+ 'ClientTimeZone': 'fHqu9CayhyHioR9w4GOAmCGNfuI=',
+ 'ClientTimeId': 'YHGrE6y4VMYjLFGwicApVDP8CQ==',
+ 'Language_code': 'FAeYmCxPzfN/s2ABidwmk2yR',
+ 'IsModernStyle': 'BTvtoZ0jt+/Sat+a9yZhduxHX60=',
+ 'Language_Name': 'FCf/qJCh2GYtR4LS0rM5/Zpmrv4n89w=',
+ 'UName': uName,
+}
+
+electricUsageRequestJson = {
+ # Set Mode
+ 'UsageOrGeneration': '1',
+ 'Type': 'K',
+ 'strDate': '',
+ 'hourlyType': 'H',
+ 'SeasonId': '',
+ 'weatherOverlay': 0,
+ 'usageyear': '',
+ 'MeterNumber': electricMeterNumber,
+ 'DateFromDaily': '',
+ 'DateToDaily': '',
+}
+
+waterUsageRequestJson = {
+ # Set Mode
+ "Type":"W",
+ "strDate":"",
+ "hourlyType":"H",
+ "seasonId":"",
+ "weatherOverlay":0,
+ "usageyear":"",
+ "MeterNumber": waterMeterNumber,
+ "DateFromDaily":"",
+ "DateToDaily":"",
+ "isNoDashboard":True
+}
+
+################################################################
+# Endpoints/URIs
+
+waterRequestEndpoint = 'https://myaccount.cityutilities.net/Portal/Usages.aspx/LoadWaterUsage'
+electricRequestEndpoint = 'https://myaccount.cityutilities.net/Portal/Usages.aspx/LoadUsage'
+loginRequestEndpoint = 'https://myaccount.cityutilities.net/Portal/Default.aspx/validateLogin'
+loginPageUri = 'https://myaccount.cityutilities.net/Portal/default.aspx' \ No newline at end of file
diff --git a/app/energy_request/energy_request.py b/app/energy_request/energy_request.py
new file mode 100644
index 0000000..f6aac3f
--- /dev/null
+++ b/app/energy_request/energy_request.py
@@ -0,0 +1,78 @@
+import json
+import requests
+from ..constants.constants import usageRequestCookies, genericRequestHeaders, electricUsageRequestJson, waterUsageRequestJson, waterRequestEndpoint, electricRequestEndpoint
+
+# Electric
+
+def dayElectricRequest():
+ electricUsageRequestJson['Mode'] = 'D'
+ return performElectricRequest()
+
+def monthElectricRequest():
+ electricUsageRequestJson['Mode'] = 'M'
+ return performElectricRequest()
+
+def performElectricRequest():
+ electricUsageResponse = requests.post(
+ electricRequestEndpoint,
+ cookies=usageRequestCookies,
+ headers=genericRequestHeaders,
+ json=electricUsageRequestJson
+ )
+ return parseResponse(electricUsageResponse)
+
+def requestElectric():
+ return {
+ "day": dayElectricRequest(),
+ "month": monthElectricRequest()
+ }
+
+# Water
+
+def dayWaterRequest():
+ waterUsageRequestJson['Mode'] = 'D'
+ return performWaterRequest()
+
+def monthWaterRequest():
+ waterUsageRequestJson['Mode'] = 'M'
+ return performWaterRequest()
+
+def performWaterRequest():
+ waterUsageResponse = requests.post(
+ waterRequestEndpoint,
+ cookies=usageRequestCookies,
+ headers=genericRequestHeaders,
+ json=waterUsageRequestJson
+ )
+ return parseResponse(waterUsageResponse)
+
+def requestWater():
+ return {
+ "day": dayWaterRequest(),
+ "month": monthWaterRequest()
+ }
+
+# Utility methods
+
+def parseResponse(response):
+ # such an icky response from an endpoint
+ # TODO: Remove unneeded JSON entries?
+ return json.loads(response.text.replace("\\\"", "\"").replace("\\\"", "\"").replace("\"{\"", "{\"").replace("}\"}", "}}"))['d']['objUsageGenerationResultSetTwo']
+
+def setupRequestParameters(parameters):
+ # Setup cookies and csrftoken to perform requests
+ usageRequestCookies['ApplicationGatewayAffinityCORS'] = parameters['aga']
+ usageRequestCookies['ApplicationGatewayAffinity'] = parameters['aga']
+ usageRequestCookies['ASP.NET_SessionId'] = parameters['asi']
+ usageRequestCookies['SCP'] = parameters['lt']
+ genericRequestHeaders['csrftoken'] = parameters['ct']
+
+# Service calling method
+
+def request(requestParameters):
+ setupRequestParameters(requestParameters)
+ return {
+ "electric": requestElectric(),
+ "water": requestWater()
+ }
+
diff --git a/app/energy_service.py b/app/energy_service.py
new file mode 100644
index 0000000..8c43c8a
--- /dev/null
+++ b/app/energy_service.py
@@ -0,0 +1,10 @@
+from fastapi import FastAPI
+from app.login.login import login
+from app.energy_request.energy_request import request
+
+app = FastAPI(docs_url=None, redoc_url=None)
+
+@app.get('/get')
+def get():
+ sessionKeys = login()
+ return request(sessionKeys) \ No newline at end of file
diff --git a/app/login/login.py b/app/login/login.py
new file mode 100644
index 0000000..511be44
--- /dev/null
+++ b/app/login/login.py
@@ -0,0 +1,53 @@
+import requests
+import re
+from ..constants.constants import loginPageHeaders, loginRequestJson, genericRequestHeaders, loginPageUri, loginRequestEndpoint
+
+def login():
+ # Grab generated session keys from viewing the webpage
+ aga, asi, ct = grabRequiredKeys()
+ # Perform a login request using keys found on the page and JSON data (credentials)
+ lt = performLoginRequest(aga, asi, ct)
+ # Return the keys required to make endpoint calls
+ return {
+ "lt": lt,
+ "aga": aga,
+ "asi": asi,
+ "ct": ct
+ }
+
+
+def grabRequiredKeys():
+ loginPageResponse = requests.get(loginPageUri, headers=loginPageHeaders)
+ affinityMatcher = re.compile("CORS=(.+?);")
+ affinityResults = affinityMatcher.search(loginPageResponse.headers['Set-Cookie'])
+ appGatewayAffinity = affinityResults.group(1)
+
+ aspNetSessionIdMatcher = re.compile("ASP\.NET\_SessionId=(.+?);")
+ aspnetResults = aspNetSessionIdMatcher.search(loginPageResponse.headers['Set-Cookie'])
+ aspNetSessionId = aspnetResults.group(1)
+
+ csrfMatcher = re.compile("id=\"hdnCSRFToken\" value=\"(.+)\"")
+ csrfResults = csrfMatcher.search(loginPageResponse.text)
+ csrfToken = csrfResults.group(1)
+ return appGatewayAffinity, aspNetSessionId, csrfToken
+
+def performLoginRequest(appGatewayAffinity, aspNetSessionId, csrfToken):
+ loginRequestCookies = {
+ 'ApplicationGatewayAffinityCORS': appGatewayAffinity,
+ 'ApplicationGatewayAffinity': appGatewayAffinity,
+ 'ASP.NET_SessionId': aspNetSessionId,
+ }
+
+ genericRequestHeaders['csrftoken'] = csrfToken
+
+ loginResponse = requests.post(
+ loginRequestEndpoint,
+ cookies=loginRequestCookies,
+ headers=genericRequestHeaders,
+ json=loginRequestJson,
+ )
+
+ scpMatcher = re.compile("SCP=(.{36});")
+ scpSearchResults = scpMatcher.search(loginResponse.headers['Set-Cookie'])
+ loginToken = scpSearchResults.group(1)
+ return loginToken