mirror of
https://github.com/Stone-Red-Code/HotspotAutoLogin.git
synced 2026-11-01 23:05:40 +01:00
Check quickly around the expected session expiry
Many portals log you out a fixed time after login (e.g. every 24 hours). With check_every_second at 600, it could take up to 10 minutes to notice. - New optional profile setting "session_hours" (also in the Add new dialog). - The time of each successful login is saved per profile in session_state.json, so it survives restarts. - While connected, the next check is timed to land 2 minutes before the expected expiry, then the connection is checked every 5 seconds until 10 minutes after it, so the program logs in again within seconds. - If still connected after that window, the session was renewed some other way and the normal interval is used again. - Ignore runtime files (log.txt, session_state.json) in git. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
53bf01733e
commit
e9428675e9
@@ -21,3 +21,7 @@ venv/
|
||||
|
||||
# VS Code settings
|
||||
.vscode/
|
||||
# Runtime files
|
||||
main/log.txt
|
||||
main/session_state.json
|
||||
main/last_known_directory.txt
|
||||
|
||||
@@ -66,6 +66,8 @@ HotspotAutoLogin is a script that designed to automate the login process for Wi-
|
||||
|
||||
`"check_every_second":` The frequency of the script for checking your internet connection status. For example "100", it will try to check the internet every 100 seconds.
|
||||
|
||||
`"session_hours":` *(optional)* If your network logs you out a fixed time after logging in (for example every 24 hours), enter that here, e.g. `24`. The program remembers when it last logged in (in `session_state.json`) and checks the connection every 5 seconds from 2 minutes before the expected expiry until 10 minutes after it, so you are logged back in within seconds instead of waiting for the next `check_every_second` check.
|
||||
|
||||
> ## Example useage:
|
||||
>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from urllib.parse import urlparse
|
||||
from tkinter import Text, Scrollbar
|
||||
from PIL import Image
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import dns.resolver
|
||||
dns.resolver.override_system_resolver(dns.resolver.Resolver())
|
||||
dns.resolver.default_resolver = dns.resolver.Resolver()
|
||||
@@ -305,6 +305,17 @@ def add_new_profile():
|
||||
if not dialog_height:
|
||||
error_message("dialog_height cannot be empty.")
|
||||
return
|
||||
session_hours = session_hours_var.get().strip()
|
||||
if session_hours:
|
||||
try:
|
||||
session_hours = float(session_hours)
|
||||
if session_hours <= 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
error_message("session_hours must be a positive number (or empty).")
|
||||
return
|
||||
if session_hours.is_integer():
|
||||
session_hours = int(session_hours)
|
||||
|
||||
try:
|
||||
parsed_headers = json.loads(raw_headers)
|
||||
@@ -332,6 +343,8 @@ def add_new_profile():
|
||||
"check_every_second": int(check_every_second),
|
||||
"dialog_geometry": {"width": int(dialog_width), "height": int(dialog_height)}
|
||||
}
|
||||
if session_hours:
|
||||
new_profile["session_hours"] = session_hours
|
||||
if not use_ethernet_var.get():
|
||||
new_profile["ssid"] = ssid
|
||||
profiles.append(new_profile)
|
||||
@@ -369,6 +382,7 @@ def add_new_profile():
|
||||
url_var = tk.StringVar()
|
||||
internet_check_url_var = tk.StringVar(value="8.8.8.8")
|
||||
check_every_second_var = tk.StringVar(value="600")
|
||||
session_hours_var = tk.StringVar(value="")
|
||||
dialog_width_var = tk.StringVar(value="1024")
|
||||
dialog_height_var = tk.StringVar(value="500")
|
||||
use_ethernet_var = tk.BooleanVar(value=False)
|
||||
@@ -435,6 +449,13 @@ def add_new_profile():
|
||||
check_entry = tk.Entry(check_frame, textvariable=check_every_second_var)
|
||||
check_entry.pack(side="left", fill="x", expand=True)
|
||||
check_entry.bind("<Button-3>", show_context_menu)
|
||||
# session_hours (optional)
|
||||
session_frame = tk.Frame(container)
|
||||
session_frame.pack(fill="x", pady=5)
|
||||
tk.Label(session_frame, text="session_hours (optional):").pack(side="left")
|
||||
session_entry = tk.Entry(session_frame, textvariable=session_hours_var)
|
||||
session_entry.pack(side="left", fill="x", expand=True)
|
||||
session_entry.bind("<Button-3>", show_context_menu)
|
||||
# dialog_geometry width and height
|
||||
dialog_geometry_frame = tk.Frame(container)
|
||||
dialog_geometry_frame.pack(fill="x", pady=5)
|
||||
@@ -981,6 +1002,61 @@ def save_to_file(message):
|
||||
with open("log.txt", "a", encoding="utf-8") as f:
|
||||
f.write(formatted_message)
|
||||
|
||||
# Session expiry tracking: many portals end the session a fixed time after login (e.g. 24 hours).
|
||||
# If a profile has "session_hours", the time of the last successful login is remembered, and around the
|
||||
# expected expiry the connection is checked every few seconds, so the program logs in again right away.
|
||||
SESSION_STATE_FILE = "session_state.json"
|
||||
SESSION_CHECK_BEFORE_SECONDS = 120 # Start checking quickly this long before the expected expiry
|
||||
SESSION_CHECK_AFTER_SECONDS = 600 # Keep checking quickly this long after it (in case the portal's clock differs)
|
||||
SESSION_FAST_CHECK_SECONDS = 5
|
||||
|
||||
def load_session_state():
|
||||
try:
|
||||
with open(SESSION_STATE_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
# Remember when the given profile last logged in successfully (survives restarts)
|
||||
def save_last_login(profile_name):
|
||||
state = load_session_state()
|
||||
state[profile_name] = datetime.now().isoformat(timespec="seconds")
|
||||
try:
|
||||
with open(SESSION_STATE_FILE, "w") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Expected expiry of the current profile's session, or None if unknown
|
||||
def get_session_expiry():
|
||||
hours = selected_profile.get("session_hours") if selected_profile else None
|
||||
if not hours:
|
||||
return None
|
||||
last_login = load_session_state().get(selected_profile["name"])
|
||||
if not last_login:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(last_login) + timedelta(hours=float(hours))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
# Seconds until the next internet check while connected: the normal interval, shortened so that
|
||||
# the check lands right before the expected session expiry, and every few seconds around it.
|
||||
# Returns (seconds, expected_expiry or None, checking_quickly)
|
||||
def get_check_interval():
|
||||
expiry = get_session_expiry()
|
||||
if expiry is None:
|
||||
return check_every_second, None, False
|
||||
now = datetime.now()
|
||||
window_start = expiry - timedelta(seconds=SESSION_CHECK_BEFORE_SECONDS)
|
||||
window_end = expiry + timedelta(seconds=SESSION_CHECK_AFTER_SECONDS)
|
||||
if window_start <= now <= window_end:
|
||||
return SESSION_FAST_CHECK_SECONDS, expiry, True
|
||||
if now < window_start:
|
||||
return max(1, min(check_every_second, int((window_start - now).total_seconds()) + 1)), expiry, False
|
||||
# Still connected well after the expected expiry, so the session was renewed some other way
|
||||
return check_every_second, None, False
|
||||
|
||||
# Create the system tray icon
|
||||
def create_system_tray_icon():
|
||||
image = Image.open("icon.ico")
|
||||
@@ -999,8 +1075,9 @@ request_errorcount = 0
|
||||
sleepcount = check_every_second
|
||||
connected_ssid_lower = None
|
||||
last_auto_message = None
|
||||
last_session_message = None
|
||||
def check_network_status():
|
||||
global running, errorcount, sleepcount, connected_ssid_lower, ssid, response, request_success, request_errorcount, successful_logins_count, last_auto_message, selected_profile
|
||||
global running, errorcount, sleepcount, connected_ssid_lower, ssid, response, request_success, request_errorcount, successful_logins_count, last_auto_message, last_session_message, selected_profile
|
||||
# In auto mode the program keeps running (e.g. started with Windows), so it never exits on errors
|
||||
while running and (auto_mode or errorcount < 10):
|
||||
if auto_mode:
|
||||
@@ -1064,11 +1141,21 @@ def check_network_status():
|
||||
subprocess.check_output(['netsh', 'wlan', 'disconnect', 'interface=' + "Wi-Fi"], startupinfo=startupinfo).decode("utf-8")
|
||||
time.sleep(5)
|
||||
if (is_internet_available()):
|
||||
sleepcount = check_every_second
|
||||
sleepcount, session_expiry, checking_quickly = get_check_interval()
|
||||
request_success = False
|
||||
request_errorcount = 0
|
||||
message = "Connected to {} and internet connection is available. Checking again in {} seconds...".format(connected_ssid, str(sleepcount))
|
||||
errorcount = 0
|
||||
if checking_quickly:
|
||||
# Log once instead of every few seconds, so the log isn't flooded
|
||||
message = "Connected to {}. The session is expected to expire around {}, checking the connection every {} seconds...".format(connected_ssid, session_expiry.strftime("%H:%M:%S"), str(sleepcount))
|
||||
if message != last_session_message:
|
||||
add_to_log(message, "bold_green")
|
||||
last_session_message = message
|
||||
else:
|
||||
last_session_message = None
|
||||
message = "Connected to {} and internet connection is available. Checking again in {} seconds...".format(connected_ssid, str(sleepcount))
|
||||
if session_expiry:
|
||||
message += " (Session expires around {})".format(session_expiry.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
add_to_log(message, "bold_green")
|
||||
elif request_success and request_errorcount < 3:
|
||||
# If the request was successful but there is still no internet connection, wait for a few seconds and try again
|
||||
@@ -1092,6 +1179,7 @@ def check_network_status():
|
||||
request_success = True
|
||||
request_errorcount = 0
|
||||
successful_logins_count += 1
|
||||
save_last_login(selected_profile['name'])
|
||||
message = "Request was successful. Checking the internet connection in {} seconds...".format(str(sleepcount))
|
||||
add_to_log(message, "green")
|
||||
save_to_file(message)
|
||||
|
||||
Reference in New Issue
Block a user