From 8cf01e1e4499cfc4afa0a051d7940e4eccb0debe Mon Sep 17 00:00:00 2001 From: Dylan Bolger Date: Sun, 15 Oct 2023 12:33:52 -0500 Subject: Add Dockerfile and organize structure for running modules --- Dockerfile | 10 ++++ app/constants/constants.py | 110 +++++++++++++++++++++++++++++++++++ app/energy_request/energy_request.py | 78 +++++++++++++++++++++++++ app/energy_service.py | 10 ++++ app/login/login.py | 53 +++++++++++++++++ constants.py | 110 ----------------------------------- energy_request.py | 78 ------------------------- energy_service.py | 10 ---- login.py | 53 ----------------- 9 files changed, 261 insertions(+), 251 deletions(-) create mode 100644 Dockerfile create mode 100644 app/constants/constants.py create mode 100644 app/energy_request/energy_request.py create mode 100644 app/energy_service.py create mode 100644 app/login/login.py delete mode 100644 constants.py delete mode 100644 energy_request.py delete mode 100644 energy_service.py delete mode 100644 login.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a12ab80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.9 +WORKDIR /server + +COPY ./requirements.txt /server/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /server/requirements.txt + +COPY ./app /server/app + +CMD ["uvicorn", "app.energy_service:app", "--host", "0.0.0.0", "--port", "80"] 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 diff --git a/constants.py b/constants.py deleted file mode 100644 index ed594ec..0000000 --- a/constants.py +++ /dev/null @@ -1,110 +0,0 @@ -from 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/energy_request.py b/energy_request.py deleted file mode 100644 index 9b8542a..0000000 --- a/energy_request.py +++ /dev/null @@ -1,78 +0,0 @@ -import json -import requests -from 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/energy_service.py b/energy_service.py deleted file mode 100644 index ac3f6fe..0000000 --- a/energy_service.py +++ /dev/null @@ -1,10 +0,0 @@ -from fastapi import FastAPI -from login import login -from 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/login.py b/login.py deleted file mode 100644 index 67f4f67..0000000 --- a/login.py +++ /dev/null @@ -1,53 +0,0 @@ -import requests -import re -from 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 -- cgit v1.2.3