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
|
# VS Code settings
|
||||||
.vscode/
|
.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.
|
`"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:
|
> ## Example useage:
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from urllib.parse import urlparse
|
|||||||
from tkinter import Text, Scrollbar
|
from tkinter import Text, Scrollbar
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import dns.resolver
|
import dns.resolver
|
||||||
dns.resolver.override_system_resolver(dns.resolver.Resolver())
|
dns.resolver.override_system_resolver(dns.resolver.Resolver())
|
||||||
dns.resolver.default_resolver = dns.resolver.Resolver()
|
dns.resolver.default_resolver = dns.resolver.Resolver()
|
||||||
@@ -305,6 +305,17 @@ def add_new_profile():
|
|||||||
if not dialog_height:
|
if not dialog_height:
|
||||||
error_message("dialog_height cannot be empty.")
|
error_message("dialog_height cannot be empty.")
|
||||||
return
|
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:
|
try:
|
||||||
parsed_headers = json.loads(raw_headers)
|
parsed_headers = json.loads(raw_headers)
|
||||||
@@ -332,6 +343,8 @@ def add_new_profile():
|
|||||||
"check_every_second": int(check_every_second),
|
"check_every_second": int(check_every_second),
|
||||||
"dialog_geometry": {"width": int(dialog_width), "height": int(dialog_height)}
|
"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():
|
if not use_ethernet_var.get():
|
||||||
new_profile["ssid"] = ssid
|
new_profile["ssid"] = ssid
|
||||||
profiles.append(new_profile)
|
profiles.append(new_profile)
|
||||||
@@ -369,6 +382,7 @@ def add_new_profile():
|
|||||||
url_var = tk.StringVar()
|
url_var = tk.StringVar()
|
||||||
internet_check_url_var = tk.StringVar(value="8.8.8.8")
|
internet_check_url_var = tk.StringVar(value="8.8.8.8")
|
||||||
check_every_second_var = tk.StringVar(value="600")
|
check_every_second_var = tk.StringVar(value="600")
|
||||||
|
session_hours_var = tk.StringVar(value="")
|
||||||
dialog_width_var = tk.StringVar(value="1024")
|
dialog_width_var = tk.StringVar(value="1024")
|
||||||
dialog_height_var = tk.StringVar(value="500")
|
dialog_height_var = tk.StringVar(value="500")
|
||||||
use_ethernet_var = tk.BooleanVar(value=False)
|
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 = tk.Entry(check_frame, textvariable=check_every_second_var)
|
||||||
check_entry.pack(side="left", fill="x", expand=True)
|
check_entry.pack(side="left", fill="x", expand=True)
|
||||||
check_entry.bind("<Button-3>", show_context_menu)
|
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 width and height
|
||||||
dialog_geometry_frame = tk.Frame(container)
|
dialog_geometry_frame = tk.Frame(container)
|
||||||
dialog_geometry_frame.pack(fill="x", pady=5)
|
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:
|
with open("log.txt", "a", encoding="utf-8") as f:
|
||||||
f.write(formatted_message)
|
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
|
# Create the system tray icon
|
||||||
def create_system_tray_icon():
|
def create_system_tray_icon():
|
||||||
image = Image.open("icon.ico")
|
image = Image.open("icon.ico")
|
||||||
@@ -999,8 +1075,9 @@ request_errorcount = 0
|
|||||||
sleepcount = check_every_second
|
sleepcount = check_every_second
|
||||||
connected_ssid_lower = None
|
connected_ssid_lower = None
|
||||||
last_auto_message = None
|
last_auto_message = None
|
||||||
|
last_session_message = None
|
||||||
def check_network_status():
|
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
|
# 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):
|
while running and (auto_mode or errorcount < 10):
|
||||||
if auto_mode:
|
if auto_mode:
|
||||||
@@ -1064,12 +1141,22 @@ def check_network_status():
|
|||||||
subprocess.check_output(['netsh', 'wlan', 'disconnect', 'interface=' + "Wi-Fi"], startupinfo=startupinfo).decode("utf-8")
|
subprocess.check_output(['netsh', 'wlan', 'disconnect', 'interface=' + "Wi-Fi"], startupinfo=startupinfo).decode("utf-8")
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
if (is_internet_available()):
|
if (is_internet_available()):
|
||||||
sleepcount = check_every_second
|
sleepcount, session_expiry, checking_quickly = get_check_interval()
|
||||||
request_success = False
|
request_success = False
|
||||||
request_errorcount = 0
|
request_errorcount = 0
|
||||||
message = "Connected to {} and internet connection is available. Checking again in {} seconds...".format(connected_ssid, str(sleepcount))
|
|
||||||
errorcount = 0
|
errorcount = 0
|
||||||
add_to_log(message, "bold_green")
|
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:
|
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
|
# If the request was successful but there is still no internet connection, wait for a few seconds and try again
|
||||||
sleepcount = 60
|
sleepcount = 60
|
||||||
@@ -1092,6 +1179,7 @@ def check_network_status():
|
|||||||
request_success = True
|
request_success = True
|
||||||
request_errorcount = 0
|
request_errorcount = 0
|
||||||
successful_logins_count += 1
|
successful_logins_count += 1
|
||||||
|
save_last_login(selected_profile['name'])
|
||||||
message = "Request was successful. Checking the internet connection in {} seconds...".format(str(sleepcount))
|
message = "Request was successful. Checking the internet connection in {} seconds...".format(str(sleepcount))
|
||||||
add_to_log(message, "green")
|
add_to_log(message, "green")
|
||||||
save_to_file(message)
|
save_to_file(message)
|
||||||
|
|||||||
Reference in New Issue
Block a user