Created
January 25, 2026 11:51
-
-
Save miditkl/f44cd08bd0c47982fcdb67c7451a939a to your computer and use it in GitHub Desktop.
This script logs into your De'Longhi account, retrieves an authentication token, and then fetches information about your registered coffee machines or other De'Longhi comfort devices.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| De'Longhi Device Info Retriever | |
| This script logs into your De'Longhi account, retrieves an authentication token, | |
| and then fetches information about your registered coffee machines or other | |
| De'Longhi comfort devices. | |
| It's designed as a simple, command-line tool that leaves no trace on your system, | |
| as it cleans up the temporary token file it creates. | |
| --- | |
| Installation: | |
| You need to install two Python libraries. You can do this using pip: | |
| pip install requests cremalink | |
| --- | |
| How to run: | |
| Save this file (e.g., as `delonghi_device_info.py`) and run it from your terminal: | |
| python delonghi_device_info.py | |
| The script will then prompt you for your De'Longhi account email and password. | |
| """ | |
| import requests | |
| import base64 | |
| from datetime import datetime | |
| import urllib.parse | |
| import json | |
| import tempfile | |
| import os | |
| import sys | |
| import getpass | |
| from cremalink import Client | |
| def get_refresh_token(email, password): | |
| """Performs the full authentication flow to get a refresh token.""" | |
| print("Authenticating...") | |
| # --- Constants and Headers --- | |
| SDK_BUILD = 16650 | |
| API_KEY = "3_e5qn7USZK-QtsIso1wCelqUKAK_IVEsYshRIssQ-X-k55haiZXmKWDHDRul2e5Y2" | |
| CLIENT_ID = "1S8q1WJEs-emOB43Z0-66WnL" | |
| CLIENT_SECRET = "lmnceiD0B-4KPNN5ZS6WuWU70j9V5BCuSlz2OPsvHkyLryhMkJkPvKsivfTq3RfNYj8GpCELtOBvhaDIzKcBtg" | |
| AUTHORIZATION_HEADER = ("Basic " + base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()) | |
| APP_ID = "DeLonghiComfort2-mw-id" | |
| APP_SECRET = "DeLonghiComfort2-Yg4miiqiNcf0Or-EhJwRh7ACfBY" | |
| BROWSER_USER_AGENT = "DeLonghiComfort/5.1.1" | |
| def get_query_param(url, param): | |
| """Extracts a query parameter from a URL.""" | |
| return urllib.parse.parse_qs(urllib.parse.urlparse(url).query).get(param, [None])[0] | |
| auth_response = requests.get( | |
| f"https://fidm.eu1.gigya.com/oidc/op/v1.0/{API_KEY}/authorize", | |
| headers={"User-Agent": BROWSER_USER_AGENT}, | |
| params={"client_id": CLIENT_ID, "response_type": "code", "redirect_uri": "https://google.it", | |
| "scope": "openid email profile UID comfort en alexa", "nonce": str(int(datetime.now().timestamp()))}, | |
| allow_redirects=False, | |
| ) | |
| context = get_query_param(auth_response.headers["Location"], "context") | |
| gigya_session_response = requests.get( | |
| f"https://socialize.eu1.gigya.com/socialize.getIDs", | |
| headers={"User-Agent": BROWSER_USER_AGENT}, | |
| params={"APIKey": API_KEY, "includeTicket": True, "pageURL": "https://aylaopenid.delonghigroup.com/", | |
| "sdk": "js_latest", "sdkBuild": SDK_BUILD, "format": "json"}, | |
| ).json() | |
| ucid, gmid, gmid_ticket = gigya_session_response["ucid"], gigya_session_response["gmid"], gigya_session_response[ | |
| "gmidTicket"] | |
| login_response = requests.post( | |
| "https://accounts.eu1.gigya.com/accounts.login", | |
| headers={"User-Agent": BROWSER_USER_AGENT}, | |
| data={"loginID": email, "password": password, "sessionExpiration": 7884009, "targetEnv": "jssdk", | |
| "include": "profile,data,emails,subscriptions,preferences", "includeUserInfo": True, | |
| "loginMode": "standard", "APIKey": API_KEY, "source": "showScreenSet", "sdk": "js_latest", | |
| "authMode": "cookie", "pageURL": "https://aylaopenid.delonghigroup.com/", "gmid": gmid, "ucid": ucid, | |
| "sdkBuild": SDK_BUILD, "format": "json"}, | |
| ).json() | |
| login_token = login_response["sessionInfo"]["login_token"] | |
| user_info_response = requests.post( | |
| "https://socialize.eu1.gigya.com/socialize.getUserInfo", | |
| headers={"User-Agent": BROWSER_USER_AGENT}, | |
| data={"enabledProviders": "*", "APIKey": API_KEY, "sdk": "js_latest", "login_token": login_token, | |
| "authMode": "cookie", "pageURL": "https://aylaopenid.delonghigroup.com/", "gmid": gmid, "ucid": ucid, | |
| "sdkBuild": SDK_BUILD, "format": "json"}, | |
| ).json() | |
| user_uid, user_uid_signature, user_signature_timestamp = user_info_response["UID"], user_info_response[ | |
| "UIDSignature"], user_info_response["signatureTimestamp"] | |
| consent_response = requests.get( | |
| f"https://aylaopenid.delonghigroup.com/OIDCConsentPage.php", | |
| headers={"User-Agent": BROWSER_USER_AGENT}, | |
| params={"context": context, "clientID": CLIENT_ID, "scope": "openid+email+profile+UID+comfort+en+alexa", | |
| "UID": user_uid, "UIDSignature": user_uid_signature, "signatureTimestamp": user_signature_timestamp}, | |
| ).text | |
| signature = consent_response.split("const consentObj2Sig = '")[1].split("';")[0] | |
| auth_continue_response = requests.get( | |
| f"https://fidm.eu1.gigya.com/oidc/op/v1.0/{API_KEY}/authorize/continue", | |
| headers={"User-Agent": BROWSER_USER_AGENT}, | |
| params={"context": context, "login_token": login_token, "consent": json.dumps( | |
| {"scope": "openid email profile UID comfort en alexa", "clientID": CLIENT_ID, "context": context, | |
| "UID": user_uid, "consent": True}, separators=(",", ":")), "sig": signature, "gmidTicket": gmid_ticket}, | |
| allow_redirects=False, | |
| ) | |
| code = get_query_param(auth_continue_response.headers["Location"], "code") | |
| idp_token_response = requests.post( | |
| f"https://fidm.eu1.gigya.com/oidc/op/v1.0/{API_KEY}/token", | |
| headers={"User-Agent": BROWSER_USER_AGENT, "Authorization": AUTHORIZATION_HEADER, | |
| "Content-Type": "application/x-www-form-urlencoded"}, | |
| data={"code": code, "grant_type": "authorization_code", "redirect_uri": "https://google.it"}, | |
| ).json() | |
| idp_token = idp_token_response["access_token"] | |
| ayla_token_response = requests.post( | |
| "https://user-field-eu.aylanetworks.com/api/v1/token_sign_in", | |
| headers={"User-Agent": BROWSER_USER_AGENT}, | |
| data={"app_id": APP_ID, "app_secret": APP_SECRET, "token": idp_token}, | |
| ).json() | |
| print("Authentication successful!") | |
| return ayla_token_response["refresh_token"] | |
| def main(): | |
| """Main function to run the CLI application.""" | |
| print("De'Longhi Device Info Retriever") | |
| print("===============================\n") | |
| temp_token_path = None | |
| try: | |
| email = input("Email: ") | |
| if sys.stdin.isatty(): | |
| password = getpass.getpass("Password: ") | |
| else: | |
| print("Warning: Running in a non-interactive environment. Password will be visible.") | |
| password = input("Password: ") | |
| refresh_token = get_refresh_token(email, password) | |
| # Create a temporary file, write to it, and close it. | |
| # delete=False is important for Windows compatibility. | |
| with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json', encoding='utf-8') as temp_token_file: | |
| temp_token_path = temp_token_file.name | |
| json.dump({"refresh_token": refresh_token}, temp_token_file) | |
| print("\nFetching device information...") | |
| client = Client(token_path=temp_token_path) | |
| dsns = client.get_devices() | |
| if not dsns: | |
| print("No devices found.") | |
| return | |
| for dsn in dsns: | |
| device = client.get_device(dsn) | |
| print(f"---------------------------------") | |
| print(f" Model: {device.model}") | |
| print(f" DSN: {device.dsn}") | |
| print(f" Local IP address: {device.ip}") | |
| print(f" Lan Key: {device.lan_key}") | |
| print(f"---------------------------------") | |
| except requests.exceptions.RequestException as e: | |
| print(f"\nAn error occurred with the network request: {e}") | |
| except KeyError as e: | |
| print(f"\nAn error occurred: Could not find expected data in the response. {e}") | |
| except Exception as e: | |
| print(f"\nAn unexpected error occurred: {e}") | |
| finally: | |
| # Clean up the temporary file manually | |
| if temp_token_path and os.path.exists(temp_token_path): | |
| os.remove(temp_token_path) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment