@@ -8,7 +8,7 @@ import os
88from pathlib import Path
99from PIL import Image , ImageTk
1010from psutil import process_iter
11- import requests
11+ import requests , urllib . parse
1212from shutil import copyfileobj , rmtree
1313from subprocess import run , CalledProcessError , STARTUPINFO , STARTF_USESHOWWINDOW
1414from tempfile import mkdtemp
@@ -23,6 +23,8 @@ from datetime import date
2323import webbrowser
2424import ctypes .wintypes
2525import cloudscraper
26+ from datetime import datetime , timedelta
27+ from email .utils import parsedate_to_datetime
2628
2729def debug (message :str , log :str ) -> None : # Needs string message and string log message, does not return anything
2830 if gDebugger : # Display logs when enabled
@@ -177,6 +179,7 @@ def download_update_thread(): # Download the new update as a separate thread
177179 global gStartGame_button
178180 global gStartGameButton_label
179181 global gAiDropDown
182+ global gUpdateSize
180183 process_names = ["age3m.exe" , "age3x.exe" , "age3.exe" , "age3y.exe" ]
181184 for process_name in process_names :
182185 if is_process_running (process_name ):
@@ -289,7 +292,7 @@ def download_update_thread(): # Download the new update as a separate thread
289292 else : # If download was Cancelled, show the update button again
290293 enable_all_widgets ()
291294 gThreadStop_event .set ()
292- check_updates (gModDownloadUrl ) # Check for future updates
295+ check_updates ("https://www.moddb.com/mods/improvement-mod/downloads/improvement-mod-manual-install-new" ) # Check for future updates
293296 install_check ()
294297 except Exception as e : # Handle errors while deleting ZIP file
295298 debug ("download_update_thread" , "Error deleting zip file:" + str (e ))
@@ -306,33 +309,36 @@ def download_update() -> None:
306309 gDownloadThread = Thread (target = download_update_thread , daemon = True )
307310 gApp .after (1 , gDownloadThread .start ())
308311
312+
309313def check_updates (url : str ) -> str : # Function to check for mod updates by comparing the 'Last-Modified' header from the server
310314 global gLast_updated # Global variable to store the 'Last-Modified' header of the latest update
311315 global gUpdateMod_button # Global variable to reference the update button in the UI
316+ global gUpdateSize # Stores the size of the zip
317+
312318 debug ("check_updates" , "checking for updates" ) # Log the start of the update check
313319 try : # Send a HEAD request to the URL to get metadata, but not the content
314- scraper = cloudscraper . create_scraper ( interpreter = "nodejs" , delay = 5 )
315- headers = {
316- "Referer" : " https://www.moddb.com/mods/improvement-mod/downloads/" ,
317- "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
318- }
319- # Use stream=True to avoid downloading the whole file
320- response = scraper . get ( url , headers = headers , stream = True , allow_redirects = True )
321-
322- if response . ok :
323- gLast_updated = response . headers . get ( "Last-Modified" , "Not found" )
324- update_date = gLast_updated
325- debug ( "check_updates Last updated date" , gLast_updated ) # Log the 'Last-Modified' header
326- else :
327- add_log ( "Update response not OK" )
328-
329- if gConfigUserInfo ["lastupdate" ] != gLast_updated : # Compare the stored 'Last-Modified' date with the value received in the response
320+
321+ # Use a proxy that fetches the HTML for us
322+ proxy_url = " https://api.allorigins.win/get?url=" + urllib . parse . quote ( url , safe = "" )
323+ response = requests . get ( proxy_url , timeout = 15 )
324+ response . raise_for_status ()
325+ html = response . json ()[ "contents" ]
326+
327+ soup = BeautifulSoup ( html , "html.parser" )
328+ updated_tag = soup . find ( "h5" , string = "Updated" )
329+ if updated_tag :
330+ time_tag = updated_tag . find_next ( "time" )
331+ if time_tag :
332+ gLast_updated = time_tag . text . strip ()
333+
334+ debug ( "check_updates - webcheck update" , gLast_updated )
335+ if gLast_updated != gConfigUserInfo ["lastupdate" ] :
330336 debug ("check_updates" , f"{ gConfigUserInfo ['lastupdate' ]} != { gLast_updated } " ) # Log the mismatch between the stored and received date
331- gUpdate_label .configure (text = f"Improvement Mod New Update - { truncate_string (update_date , max_length = 550 , placeholder = "..." )} " ) # Update the UI to indicate that a new update is available
337+ gUpdate_label .configure (text = f"Improvement Mod New Update - { truncate_string (gLast_updated , max_length = 550 , placeholder = "..." )} " ) # Update the UI to indicate that a new update is available
332338 gUpdateMod_button .configure (text = "Update Now" ) # Enable the update button for the user to click
333339 else : # If the dates match, the mod is up-to-date
334340 debug ("check_updates" , "UpToDate" ) # Log that the mod is up-to-date
335- gUpdate_label .configure (text = f"Improvement Mod Up-to-date - { update_date } " ) # Update the UI to indicate that the mod is up-to-date
341+ gUpdate_label .configure (text = f"Improvement Mod Up-to-date - { gLast_updated } " ) # Update the UI to indicate that the mod is up-to-date
336342 gUpdateButton_label .configure (text = "Reinstall Mod" )
337343
338344 except requests .RequestException as e : # Catch network-related errors during the HTTP request
@@ -341,6 +347,7 @@ def check_updates(url: str) -> str: # Function to check for mod updates by compa
341347 gUpdateButton_label .configure (text = "Reinstall Mod" )
342348 return "" # Return an empty string to indicate an error has occurred
343349
350+
344351def startGame_button_click () -> None :
345352 global gThreadStop_event
346353 debug ("startGame_button_click" ,"Starting game" )
@@ -693,38 +700,60 @@ def find_download_mirror() -> None:
693700 else :
694701 add_log ("Mirror Link not Found" )
695702
696- def make_config () -> None : # Make a user config file to save preferences
703+ def make_config () -> None : # Make a user config file to save preferences
697704 debug ("make_config Creating" , "config" )
698705 global gConfigPath
699706 global gConfigUserData
700707 global gConfigUserInfo
701708 global gGamePath
702- gConfigUserData = ConfigParser ()
703- gConfigPath = Path ("ImprovementModLauncher.ini" ) # Relative path to INI file
709+
710+ gConfigUserData = ConfigParser ()
711+ gConfigPath = Path ("ImprovementModLauncher.ini" )
712+
713+ # Define defaults
714+ defaults = {
715+ "gamepath" : str (os .getcwd ()),
716+ "lastupdate" : "na" ,
717+ "showlogs" : "0" ,
718+ "updatelastcheck" : ""
719+ }
720+
704721 try :
705- if gConfigPath .is_file (): # Check if the INI file exists
706- read_write_config ("r" ) # Read the config file
707- else : # Set the defaults of the INI file if the file does not exists and create it
708- gConfigUserData ["USERINFO" ] = {
709- "gamepath" : str (os .getcwd ()),
710- "lastupdate" : "na" ,
711- "showlogs" : "0" ,
712- "updatelastcheck" : ""
713- }
714- read_write_config ("w" ) # Wright to the config
715- read_write_config ("r" ) # Read the new config
716- gConfigUserInfo = gConfigUserData ["USERINFO" ] # Store the INI settings information
717- gGamePath = gConfigUserInfo ["gamepath" ] # Get the saved game path dir
722+ if gConfigPath .is_file ():
723+ # Read existing config
724+ read_write_config ("r" )
725+ else :
726+ # Create section if file doesn’t exist
727+ gConfigUserData ["USERINFO" ] = {}
728+
729+ # Ensure section exists
730+ if "USERINFO" not in gConfigUserData :
731+ gConfigUserData ["USERINFO" ] = {}
732+
733+ # Fill in any missing keys with defaults
734+ for key , value in defaults .items ():
735+ if key not in gConfigUserData ["USERINFO" ]:
736+ gConfigUserData ["USERINFO" ][key ] = value
737+
738+ # Save changes
739+ read_write_config ("w" )
740+ read_write_config ("r" )
741+
742+ gConfigUserInfo = gConfigUserData ["USERINFO" ]
743+ gGamePath = gConfigUserInfo .get ("gamepath" , str (os .getcwd ()))
718744 if gGamePath == "" :
719745 gGamePath = str (os .getcwd ())
746+
720747 debug ("make_config Config created " , "successfully" )
721- except PermissionError : # File Permission Error
748+
749+ except PermissionError : # File Permission Error
722750 debug ("make_config Permission denied" , "PermissionError" )
723- except OSError as e : # OSError
751+ except OSError as e : # OSError
724752 debug (f"make_config OS error has occurred: { e } " , "OSError" )
725- except Exception as e : # Exception
753+ except Exception as e : # Exception
726754 debug (f"make_config An unexpected error has occurred: { e } " , "Exception" )
727755
756+
728757def select_folder () -> None :
729758 global gGamePath
730759 try :
@@ -1005,7 +1034,7 @@ def main():
10051034 make_config ()
10061035 interface ()
10071036 find_download_mirror ()
1008- check_updates (gModDownloadUrl )
1037+ check_updates ("https://www.moddb.com/mods/improvement-mod/downloads/improvement-mod-manual-install-new" )
10091038 read_ai_zip ()
10101039 install_check ()
10111040
@@ -1068,8 +1097,8 @@ if __name__ == '__main__':
10681097 else : # Running as a script
10691098 gApp .iconbitmap (r"icon\icon.ico" ) # Set the path to the icon file for script
10701099
1071- gVersion :str = "1.02 " # App version
1072- gGitHubVersion :str = "version1.02 "
1100+ gVersion :str = "1.03 " # App version
1101+ gGitHubVersion :str = "version1.03 "
10731102
10741103 gModDownloadUrl :str = "" # Hold the URL for the mirror download
10751104 gLast_updated :str = "" # Stores the last date the file was installed
@@ -1116,5 +1145,6 @@ if __name__ == '__main__':
11161145 gLog_font :ctk .CTkFont = None
11171146 gAi_font :ctk .CTkFont = None
11181147 gLogTextBox :ctk .CTkTextbox = None
1148+ gUpdateSize = None
11191149
11201150 main ()
0 commit comments