2023-08-12 12:32:54 -04:00
from CTkMessagebox import CTkMessagebox
from bs4 import BeautifulSoup
import customtkinter as ctk
from CTkToolTip import *
from PIL import Image
2023-07-31 02:02:53 -04:00
import configparser
2023-08-12 12:32:54 -04:00
import webbrowser
import subprocess
import threading
2023-08-23 16:27:24 -04:00
import datetime
2023-08-12 12:32:54 -04:00
import requests
2023-07-31 16:44:31 -04:00
import zipfile
2023-08-12 12:32:54 -04:00
import shutil
2023-07-31 12:40:41 -04:00
import psutil
2023-08-12 12:32:54 -04:00
import json
import math
2023-07-31 02:02:53 -04:00
import time
2023-08-12 12:32:54 -04:00
import sys
import io
import os
import re
2023-07-31 02:02:53 -04:00
2023-08-27 15:37:29 -04:00
VERSION = " v0.2.3 "
2023-08-03 04:13:02 -04:00
GITHUB_REPO = " faroukbmiled/BOIIIWD "
LATEST_RELEASE_URL = " https://github.com/faroukbmiled/BOIIIWD/releases/latest/download/Release.zip "
UPDATER_FOLDER = " update "
2023-07-31 02:02:53 -04:00
CONFIG_FILE_PATH = " config.ini "
2023-08-18 20:05:04 -04:00
RESOURCES_DIR = os . path . join ( os . path . dirname ( __file__ ) , ' resources ' )
2023-08-14 20:59:35 -04:00
2023-08-16 05:05:59 -04:00
# fuck it we ball, ill get rid of globals when i finish everything cant be bothered rn
2023-08-25 15:08:08 -04:00
global stopped , steampid , console , clean_on_finish , continuous , estimated_progress , steam_fail_counter , \
2023-08-27 15:37:29 -04:00
steam_fail_counter_toggle , steam_fail_number , steamcmd_reset , show_fails
2023-07-31 12:40:41 -04:00
steampid = None
2023-07-31 02:02:53 -04:00
stopped = False
2023-08-03 04:13:02 -04:00
console = False
2023-08-14 20:59:35 -04:00
clean_on_finish = True
2023-08-16 05:05:59 -04:00
continuous = True
estimated_progress = True
2023-08-27 15:37:29 -04:00
steam_fail_counter_toggle = False
2023-08-25 15:08:08 -04:00
steam_fail_counter = 0
steam_fail_number = 10
steamcmd_reset = False
2023-08-27 15:37:29 -04:00
show_fails = True
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
# Start Helper Functions
2023-08-23 16:27:24 -04:00
def cwd ( ) :
if getattr ( sys , ' frozen ' , False ) :
return os . path . dirname ( sys . executable )
else :
return os . path . dirname ( os . path . abspath ( __file__ ) )
def check_config ( name , fallback = None ) :
config = configparser . ConfigParser ( )
config . read ( CONFIG_FILE_PATH )
if fallback :
return config . get ( " Settings " , name , fallback = fallback )
return config . get ( " Settings " , name , fallback = " on " )
2023-08-25 15:08:08 -04:00
def save_config ( name , value ) :
config = configparser . ConfigParser ( )
config . read ( CONFIG_FILE_PATH )
if name and value :
config . set ( " Settings " , name , value )
with open ( CONFIG_FILE_PATH , " w " ) as config_file :
config . write ( config_file )
2023-08-23 16:27:24 -04:00
def check_custom_theme ( theme_name ) :
if os . path . exists ( os . path . join ( cwd ( ) , theme_name ) ) :
return os . path . join ( cwd ( ) , theme_name )
else :
try :
return os . path . join ( RESOURCES_DIR , theme_name )
except :
return os . path . join ( RESOURCES_DIR , " boiiiwd_theme.json " )
ctk . set_appearance_mode ( check_config ( " appearance " , " Dark " ) ) # Modes: "System" (standard), "Dark", "Light"
try :
ctk . set_default_color_theme ( check_custom_theme ( check_config ( " theme " , fallback = " boiiiwd_theme.json " ) ) )
except :
2023-08-25 15:08:08 -04:00
save_config ( " theme " , " boiiiwd_theme.json " )
2023-08-23 16:27:24 -04:00
ctk . set_default_color_theme ( os . path . join ( RESOURCES_DIR , " boiiiwd_theme.json " ) )
2023-08-03 04:13:02 -04:00
def get_latest_release_version ( ) :
try :
release_api_url = f " https://api.github.com/repos/ { GITHUB_REPO } /releases/latest "
response = requests . get ( release_api_url )
response . raise_for_status ( )
data = response . json ( )
return data [ " tag_name " ]
except requests . exceptions . RequestException as e :
show_message ( " Warning " , f " Error while checking for updates: \n { e } " )
return None
def create_update_script ( current_exe , new_exe , updater_folder , program_name ) :
script_content = f """
@echo off
echo Terminating BOIIIWD . exe . . .
taskkill / im " {program_name} " / t / f
echo Replacing BOIIIWD . exe . . .
cd " {updater_folder} "
taskkill / im " {program_name} " / t / f
move / y " {new_exe} " " ../ " { program_name } " "
echo Starting BOIIIWD . exe . . .
cd . .
start " " " {current_exe} "
echo Exiting !
exit
"""
script_path = os . path . join ( updater_folder , " boiiiwd_updater.bat " )
with open ( script_path , " w " ) as script_file :
script_file . write ( script_content )
return script_path
2023-07-31 02:02:53 -04:00
2023-08-14 20:59:35 -04:00
def check_for_updates_func ( window , ignore_up_todate = False ) :
try :
latest_version = get_latest_release_version ( )
current_version = VERSION
if latest_version and latest_version != current_version :
msg_box = CTkMessagebox ( title = " Update Available " , message = f " An update is available! Install now? \n \n Current Version: { current_version } \n Latest Version: { latest_version } " , option_1 = " View " , option_2 = " No " , option_3 = " Yes " , fade_in_duration = int ( 1 ) )
result = msg_box . get ( )
if result == " View " :
webbrowser . open ( f " https://github.com/ { GITHUB_REPO } /releases/latest " )
if result == " Yes " :
update_window = UpdateWindow ( window , LATEST_RELEASE_URL )
update_window . start_update ( )
if result == " No " :
return
elif latest_version == current_version :
if ignore_up_todate :
return
msg_box = CTkMessagebox ( title = " Up to Date! " , message = " No Updates Available! " , option_1 = " Ok " )
result = msg_box . get ( )
except Exception as e :
show_message ( " Error " , f " Error while checking for updates: \n { e } " , icon = " cancel " )
2023-08-03 04:13:02 -04:00
def extract_workshop_id ( link ) :
try :
pattern = r ' (?<=id=)( \ d+) '
match = re . search ( pattern , link )
if match :
return match . group ( 0 )
else :
return None
except :
return None
2023-07-31 16:44:31 -04:00
def check_steamcmd ( ) :
steamcmd_path = get_steamcmd_path ( )
steamcmd_exe_path = os . path . join ( steamcmd_path , " steamcmd.exe " )
if not os . path . exists ( steamcmd_exe_path ) :
return False
return True
2023-08-12 12:32:54 -04:00
def initialize_steam ( master ) :
2023-08-03 04:13:02 -04:00
try :
steamcmd_path = get_steamcmd_path ( )
steamcmd_exe_path = os . path . join ( steamcmd_path , " steamcmd.exe " )
process = subprocess . Popen ( [ steamcmd_exe_path , " +quit " ] , creationflags = subprocess . CREATE_NEW_CONSOLE )
2023-08-12 12:32:54 -04:00
master . attributes ( ' -alpha ' , 0.0 )
2023-08-03 04:13:02 -04:00
process . wait ( )
2023-08-23 16:27:24 -04:00
if is_steamcmd_initialized ( ) :
show_message ( " SteamCMD has terminated! " , " BOIIIWD is ready for action. " , icon = " info " )
else :
show_message ( " SteamCMD has terminated!! " , " SteamCMD isn ' t initialized yet " )
2023-08-03 04:13:02 -04:00
except :
2023-08-12 12:32:54 -04:00
show_message ( " Error! " , " An error occurred please check your paths and try again. " , icon = " cancel " )
master . attributes ( ' -alpha ' , 1.0 )
2023-08-03 04:13:02 -04:00
2023-07-31 21:48:40 -04:00
def valid_id ( workshop_id ) :
url = f " https://steamcommunity.com/sharedfiles/filedetails/?id= { workshop_id } "
response = requests . get ( url )
response . raise_for_status ( )
content = response . text
soup = BeautifulSoup ( content , " html.parser " )
try :
soup . find ( " div " , class_ = " rightDetailsBlock " ) . text . strip ( )
soup . find ( " div " , class_ = " workshopItemTitle " ) . text . strip ( )
soup . find ( " div " , class_ = " detailsStatRight " ) . text . strip ( )
stars_div = soup . find ( " div " , class_ = " fileRatingDetails " )
stars_div . find ( " img " ) [ " src " ]
return True
except :
return False
2023-07-31 13:48:21 -04:00
def convert_speed ( speed_bytes ) :
if speed_bytes < 1024 :
return speed_bytes , " B/s "
elif speed_bytes < 1024 * 1024 :
return speed_bytes / 1024 , " KB/s "
elif speed_bytes < 1024 * 1024 * 1024 :
return speed_bytes / ( 1024 * 1024 ) , " MB/s "
else :
return speed_bytes / ( 1024 * 1024 * 1024 ) , " GB/s "
2023-07-31 02:02:53 -04:00
def create_default_config ( ) :
config = configparser . ConfigParser ( )
config [ " Settings " ] = {
2023-07-31 10:39:56 -04:00
" SteamCMDPath " : cwd ( ) ,
2023-08-03 04:13:02 -04:00
" DestinationFolder " : " " ,
" checkforupdtes " : " on " ,
" console " : " off "
2023-07-31 02:02:53 -04:00
}
with open ( CONFIG_FILE_PATH , " w " ) as config_file :
config . write ( config_file )
2023-08-27 16:09:11 -04:00
def run_steamcmd_command ( command , self , map_folder , queue = None ) :
2023-08-25 15:08:08 -04:00
global steampid , stopped , steam_fail_counter , steam_fail_number , steamcmd_reset
2023-07-31 02:02:53 -04:00
steamcmd_path = get_steamcmd_path ( )
2023-08-03 04:13:02 -04:00
show_console = subprocess . CREATE_NO_WINDOW
if console :
show_console = subprocess . CREATE_NEW_CONSOLE
2023-07-31 16:52:22 -04:00
2023-08-27 15:37:29 -04:00
if os . path . exists ( map_folder ) :
try :
try :
os . remove ( map_folder )
except :
timestamp = datetime . datetime . now ( ) . strftime ( " % Y- % m- %d - % H- % M- % S " )
os . rename ( map_folder , os . path . join ( map_folder , os . path . join ( get_steamcmd_path ( ) , " steamapps " , " workshop " , " content " , " 311210 " , f " couldntremove_ { timestamp } " ) ) )
except Exception as e :
stopped = True
self . queue_stop_button = True
show_message ( " Error " , f " Couldn ' t remove { map_folder } , please do so manually \n { e } " , icon = " cancel " )
return
2023-08-16 05:05:59 -04:00
if continuous :
while not os . path . exists ( map_folder ) and not stopped :
process = subprocess . Popen (
[ steamcmd_path + " \ steamcmd.exe " ] + command . split ( ) ,
stdout = None if console else subprocess . PIPE ,
stderr = None if console else subprocess . PIPE ,
text = True ,
bufsize = 1 ,
universal_newlines = True ,
creationflags = show_console
)
2023-07-31 16:52:22 -04:00
2023-08-16 05:05:59 -04:00
steampid = process . pid
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
if process . poll ( ) is not None :
return process . returncode
2023-07-31 14:45:46 -04:00
2023-08-16 05:05:59 -04:00
process . communicate ( )
2023-08-25 15:08:08 -04:00
steam_fail_counter = steam_fail_counter + 1
if steam_fail_counter_toggle :
# print(steam_fail_counter)
try :
if steam_fail_counter > = int ( steam_fail_number ) :
reset_steamcmd ( no_warn = True )
steamcmd_reset = True
steam_fail_counter = 0
except :
if steam_fail_counter > = 10 :
reset_steamcmd ( no_warn = True )
steam_fail_counter = 0
2023-08-16 05:05:59 -04:00
else :
process = subprocess . Popen (
[ steamcmd_path + " \ steamcmd.exe " ] + command . split ( ) ,
stdout = None if console else subprocess . PIPE ,
stderr = None if console else subprocess . PIPE ,
text = True ,
bufsize = 1 ,
universal_newlines = True ,
creationflags = show_console
)
steampid = process . pid
if process . poll ( ) is not None :
return process . returncode
process . communicate ( )
if not os . path . exists ( map_folder ) :
show_message ( " SteamCMD has terminated " , " SteamCMD has been terminated \n And failed to download the map/mod, try again or enable continuous download in settings " )
2023-07-31 14:45:46 -04:00
2023-08-12 12:32:54 -04:00
stopped = True
2023-08-27 16:09:11 -04:00
if not queue :
self . button_download . configure ( state = " normal " )
self . button_stop . configure ( state = " disabled " )
2023-07-31 14:45:46 -04:00
return process . returncode
2023-07-31 02:02:53 -04:00
def get_steamcmd_path ( ) :
config = configparser . ConfigParser ( )
config . read ( CONFIG_FILE_PATH )
2023-07-31 10:39:56 -04:00
return config . get ( " Settings " , " SteamCMDPath " , fallback = cwd ( ) )
2023-07-31 02:02:53 -04:00
2023-08-18 20:05:04 -04:00
def extract_json_data ( json_path , key ) :
2023-07-31 02:02:53 -04:00
with open ( json_path , " r " ) as json_file :
data = json . load ( json_file )
2023-08-18 20:05:04 -04:00
return data [ key ]
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
def convert_bytes_to_readable ( size_in_bytes , no_symb = None ) :
2023-08-01 09:32:32 -04:00
for unit in [ ' B ' , ' KB ' , ' MB ' , ' GB ' , ' TB ' ] :
if size_in_bytes < 1024.0 :
2023-08-16 05:05:59 -04:00
if no_symb :
return f " { size_in_bytes : .2f } "
2023-08-01 09:32:32 -04:00
return f " { size_in_bytes : .2f } { unit } "
size_in_bytes / = 1024.0
2023-07-31 16:44:31 -04:00
def get_workshop_file_size ( workshop_id , raw = None ) :
2023-07-31 02:02:53 -04:00
url = f " https://steamcommunity.com/sharedfiles/filedetails/?id= { workshop_id } &searchtext= "
response = requests . get ( url )
soup = BeautifulSoup ( response . text , " html.parser " )
file_size_element = soup . find ( " div " , class_ = " detailsStatRight " )
2023-07-31 16:44:31 -04:00
2023-07-31 21:48:40 -04:00
try :
if raw :
file_size_text = file_size_element . get_text ( strip = True )
file_size_text = file_size_text . replace ( " , " , " " )
2023-08-01 09:32:32 -04:00
file_size_in_mb = float ( file_size_text . replace ( " MB " , " " ) )
file_size_in_bytes = int ( file_size_in_mb * 1024 * 1024 )
return convert_bytes_to_readable ( file_size_in_bytes )
2023-07-31 21:48:40 -04:00
if file_size_element :
file_size_text = file_size_element . get_text ( strip = True )
file_size_text = file_size_text . replace ( " , " , " " )
file_size_in_mb = float ( file_size_text . replace ( " MB " , " " ) )
file_size_in_bytes = int ( file_size_in_mb * 1024 * 1024 )
return file_size_in_bytes
return None
except :
return None
2023-07-31 02:02:53 -04:00
2023-08-12 12:32:54 -04:00
def show_message ( title , message , icon = " warning " , exit_on_close = False ) :
2023-08-03 04:13:02 -04:00
if exit_on_close :
2023-08-12 12:32:54 -04:00
msg = CTkMessagebox ( title = title , message = message , icon = icon , option_1 = " No " , option_2 = " Ok " )
response = msg . get ( )
if response == " No " :
return False
if response == " Ok " :
return True
else :
return False
2023-08-03 04:13:02 -04:00
else :
2023-08-12 12:32:54 -04:00
msg = CTkMessagebox ( title = title , message = message , icon = icon )
2023-08-14 20:59:35 -04:00
def launch_boiii_func ( path ) :
try :
boiii_path = os . path . join ( path , " boiii.exe " )
subprocess . Popen ( [ boiii_path ] , cwd = path )
except Exception as e :
show_message ( " Error: Failed to launch BOIII " , f " Failed to launch boiii.exe \n Make sure to put in your correct boiii path \n { e } " )
def remove_tree ( folder_path , show_error = None ) :
if show_error :
try :
shutil . rmtree ( folder_path )
except Exception as e :
show_message ( " Error! " , f " An error occurred while trying to remove files: \n { e } " , icon = " cancel " )
try :
shutil . rmtree ( folder_path )
except Exception as e :
pass
2023-08-16 05:05:59 -04:00
def convert_seconds ( seconds ) :
minutes , seconds = divmod ( seconds , 60 )
hours , minutes = divmod ( minutes , 60 )
return hours , minutes , seconds
2023-08-18 20:05:04 -04:00
def get_folder_size ( folder_path ) :
total_size = 0
2023-08-23 16:27:24 -04:00
for path , dirs , files in os . walk ( folder_path ) :
for f in files :
fp = os . path . join ( path , f )
total_size + = os . stat ( fp ) . st_size
2023-08-18 20:05:04 -04:00
return total_size
2023-08-23 16:27:24 -04:00
def is_steamcmd_initialized ( ) :
steamcmd_path = get_steamcmd_path ( )
steamcmd_exe_path = os . path . join ( steamcmd_path , " steamcmd.exe " )
steamcmd_size = os . path . getsize ( steamcmd_exe_path )
if steamcmd_size < 3 * 1024 * 1024 :
return False
return True
def get_button_state_colors ( file_path , state ) :
try :
with open ( file_path , ' r ' ) as json_file :
data = json . load ( json_file )
if ' BOIIIWD_Globals ' in data :
boiiiwd_globals = data [ ' BOIIIWD_Globals ' ]
if state in boiiiwd_globals :
return boiiiwd_globals [ state ]
else :
return None
else :
return None
except FileNotFoundError :
return None
except json . JSONDecodeError :
return None
2023-08-25 15:08:08 -04:00
def reset_steamcmd ( no_warn = None ) :
steamcmd_path = get_steamcmd_path ( )
steamcmd_steamapps = os . path . join ( steamcmd_path , " steamapps " )
if os . path . exists ( steamcmd_steamapps ) :
remove_tree ( steamcmd_steamapps , show_error = True )
if not no_warn :
show_message ( " Success! " , " SteamCMD has been reset successfully! " , icon = " info " )
else :
if not no_warn :
show_message ( " Warning! " , " steamapps folder was not found, maybe already removed? " , icon = " warning " )
2023-08-27 15:37:29 -04:00
def get_item_name ( id ) :
try :
url = f " https://steamcommunity.com/sharedfiles/filedetails/?id= { id } "
response = requests . get ( url )
response . raise_for_status ( )
content = response . text
soup = BeautifulSoup ( content , " html.parser " )
try :
map_name = soup . find ( " div " , class_ = " workshopItemTitle " ) . text . strip ( )
return map_name
except :
return True
except :
return False
2023-08-12 12:32:54 -04:00
# End helper functions
class UpdateWindow ( ctk . CTkToplevel ) :
def __init__ ( self , master , update_url ) :
global master_win
master_win = master
super ( ) . __init__ ( master )
self . title ( " BOIIIWD Self-Updater " )
self . geometry ( " 400x150 " )
2023-08-18 20:05:04 -04:00
self . after ( 250 , lambda : self . iconbitmap ( os . path . join ( RESOURCES_DIR , " ryuk.ico " ) ) )
2023-08-12 12:32:54 -04:00
self . protocol ( " WM_DELETE_WINDOW " , self . cancel_update )
self . attributes ( ' -topmost ' , ' true ' )
self . columnconfigure ( 0 , weight = 1 )
self . columnconfigure ( 1 , weight = 1 )
self . rowconfigure ( 0 , weight = 1 )
self . rowconfigure ( 1 , weight = 1 )
self . label_download = ctk . CTkLabel ( self , text = " Starting... " )
self . label_download . grid ( row = 0 , column = 0 , padx = 30 , pady = ( 10 , 0 ) , sticky = " w " )
self . label_size = ctk . CTkLabel ( self , text = " Size: 0 " )
self . label_size . grid ( row = 0 , column = 1 , padx = 30 , pady = ( 10 , 0 ) , sticky = " e " )
2023-08-25 15:18:36 -04:00
self . progress_color = get_button_state_colors ( check_custom_theme ( check_config ( " theme " , fallback = " boiiiwd_theme.json " ) ) , " progress_bar_fill_color " )
self . progress_bar = ctk . CTkProgressBar ( self , mode = " determinate " , height = 20 , corner_radius = 7 , progress_color = self . progress_color )
2023-08-12 12:32:54 -04:00
self . progress_bar . grid ( row = 1 , column = 0 , columnspan = 4 , padx = 30 , pady = 10 , sticky = " ew " )
self . progress_bar . set ( 0 )
self . progress_label = ctk . CTkLabel ( self . progress_bar , text = " 0 % " , font = ( " Helvetica " , 12 ) , fg_color = " transparent " , height = 0 , width = 0 , corner_radius = 0 )
self . progress_label . place ( relx = 0.5 , rely = 0.5 , anchor = " center " )
self . cancel_button = ctk . CTkButton ( self , text = " Cancel " , command = self . cancel_update )
self . cancel_button . grid ( row = 2 , column = 0 , padx = 30 , pady = ( 0 , 10 ) , sticky = " w " )
self . update_url = update_url
self . total_size = None
self . up_cancelled = False
def update_progress_bar ( self ) :
2023-08-03 04:13:02 -04:00
try :
update_dir = os . path . join ( os . getcwd ( ) , UPDATER_FOLDER )
response = requests . get ( LATEST_RELEASE_URL , stream = True )
response . raise_for_status ( )
current_exe = sys . argv [ 0 ]
program_name = os . path . basename ( current_exe )
new_exe = os . path . join ( update_dir , " BOIIIWD.exe " )
if not os . path . exists ( update_dir ) :
os . makedirs ( update_dir )
2023-08-12 12:32:54 -04:00
self . progress_bar . set ( 0.0 )
self . total_size = int ( response . headers . get ( ' content-length ' , 0 ) )
self . label_size . configure ( text = f " Size: { convert_bytes_to_readable ( self . total_size ) } " )
2023-08-03 04:13:02 -04:00
zip_path = os . path . join ( update_dir , " latest_version.zip " )
2023-08-12 12:32:54 -04:00
with open ( zip_path , " wb " ) as file :
downloaded_size = 0
for chunk in response . iter_content ( chunk_size = 8192 ) :
if self . up_cancelled :
2023-08-03 04:13:02 -04:00
break
if chunk :
2023-08-12 12:32:54 -04:00
file . write ( chunk )
downloaded_size + = len ( chunk )
progress = int ( ( downloaded_size / self . total_size ) * 100 )
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
self . after ( 1 , lambda p = progress : self . label_download . configure ( text = f " Downloading update... " ) )
self . after ( 1 , lambda v = progress / 100.0 : self . progress_bar . set ( v ) )
self . after ( 1 , lambda p = progress : self . progress_label . configure ( text = f " { p } % " ) )
if not self . up_cancelled :
self . progress_bar . set ( 1.0 )
2023-08-03 04:13:02 -04:00
with zipfile . ZipFile ( zip_path , " r " ) as zip_ref :
zip_ref . extractall ( update_dir )
2023-08-12 12:32:54 -04:00
self . label_download . configure ( text = " Update Downloaded successfully! " )
if not show_message ( " Success! " , " Update Downloaded successfully! \n Press ok to install it " , icon = " info " , exit_on_close = True ) :
return
2023-08-03 04:13:02 -04:00
script_path = create_update_script ( current_exe , new_exe , update_dir , program_name )
subprocess . run ( ( ' cmd ' , ' /C ' , ' start ' , ' ' , fr ' { script_path } ' ) )
sys . exit ( 0 )
else :
if os . path . exists ( zip_path ) :
os . remove ( fr " { zip_path } " )
2023-08-12 12:32:54 -04:00
self . label_download . configure ( text = " Update cancelled. " )
self . progress_bar . set ( 0.0 )
# there's a better solution ill implement it later
global master_win
2023-08-13 13:48:28 -04:00
try :
master_win . attributes ( ' -alpha ' , 1.0 )
except :
pass
2023-08-12 12:32:54 -04:00
show_message ( " Cancelled! " , " Update cancelled by user " , icon = " warning " )
2023-08-03 04:13:02 -04:00
except Exception as e :
2023-08-12 12:32:54 -04:00
self . progress_bar . set ( 0.0 )
self . label_download . configure ( text = " Update failed " )
show_message ( " Error " , f " Error installing the update \n { e } " , icon = " cancel " )
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
def start_update ( self ) :
self . thread = threading . Thread ( target = self . update_progress_bar )
self . thread . start ( )
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
def cancel_update ( self ) :
self . up_cancelled = True
self . withdraw ( )
2023-08-18 20:05:04 -04:00
class LibraryTab ( ctk . CTkScrollableFrame ) :
def __init__ ( self , master , * * kwargs ) :
2023-08-16 19:02:57 -04:00
2023-08-18 20:05:04 -04:00
super ( ) . __init__ ( master , * * kwargs )
self . added_items = set ( )
2023-08-16 19:02:57 -04:00
self . grid_columnconfigure ( 0 , weight = 1 )
2023-08-18 20:05:04 -04:00
self . radiobutton_variable = ctk . StringVar ( )
2023-08-23 16:27:24 -04:00
self . no_items_label = ctk . CTkLabel ( self , text = " " , anchor = " w " )
self . filter_entry = ctk . CTkEntry ( self , placeholder_text = " Your search query here, or type in mod or map to see only that " )
self . filter_entry . bind ( " <KeyRelease> " , self . filter_items )
self . filter_entry . grid ( row = 0 , column = 0 , padx = ( 10 , 20 ) , pady = ( 10 , 20 ) , sticky = " we " )
filter_refresh_button_image = os . path . join ( RESOURCES_DIR , " Refresh_icon.svg.png " )
self . filter_refresh_button = ctk . CTkButton ( self , image = ctk . CTkImage ( Image . open ( filter_refresh_button_image ) ) , command = self . refresh_items , width = 20 , height = 20 ,
fg_color = " transparent " , text = " " )
self . filter_refresh_button . grid ( row = 0 , column = 1 , padx = ( 10 , 20 ) , pady = ( 10 , 20 ) , sticky = " enw " )
2023-08-18 20:05:04 -04:00
self . label_list = [ ]
self . button_list = [ ]
self . button_view_list = [ ]
self . filter_type = True
def add_item ( self , item , image = None , item_type = " map " , workshop_id = None , folder = None ) :
label = ctk . CTkLabel ( self , text = item , image = image , compound = " left " , padx = 5 , anchor = " w " )
2023-08-23 16:27:24 -04:00
button = ctk . CTkButton ( self , text = " Remove " , width = 60 , height = 24 , fg_color = " #3d3f42 " )
button_view = ctk . CTkButton ( self , text = " Details " , width = 55 , height = 24 , fg_color = " #3d3f42 " )
2023-08-18 20:05:04 -04:00
button . configure ( command = lambda : self . remove_item ( item , folder ) )
button_view . configure ( command = lambda : self . show_map_info ( workshop_id ) )
button_view_tooltip = CTkToolTip ( button_view , message = " Opens up a window that shows basic details " )
button_tooltip = CTkToolTip ( button , message = " Removes the map/mod from your game " )
label . grid ( row = len ( self . label_list ) + 1 , column = 0 , pady = ( 0 , 10 ) , padx = ( 5 , 10 ) , sticky = " w " )
2023-08-23 16:27:24 -04:00
button . grid ( row = len ( self . button_list ) + 1 , column = 1 , pady = ( 0 , 10 ) , padx = ( 50 , 10 ) , sticky = " e " )
button_view . grid ( row = len ( self . button_view_list ) + 1 , column = 1 , pady = ( 0 , 10 ) , padx = ( 10 , 75 ) , sticky = " w " )
2023-08-18 20:05:04 -04:00
self . label_list . append ( label )
self . button_list . append ( button )
self . button_view_list . append ( button_view )
def filter_items ( self , event ) :
filter_text = self . filter_entry . get ( ) . lower ( )
for label , button , button_view_list in zip ( self . label_list , self . button_list , self . button_view_list ) :
item_text = label . cget ( " text " ) . lower ( )
if filter_text in item_text :
label . grid ( )
button . grid ( )
button_view_list . grid ( )
else :
label . grid_remove ( )
button_view_list . grid_remove ( )
button . grid_remove ( )
def load_items ( self , boiiiFolder ) :
# if you add this under init the whole app shrinks for some reason
global boiiiFolderGlobal
boiiiFolderGlobal = boiiiFolder
maps_folder = os . path . join ( boiiiFolder , " mods " )
mods_folder = os . path . join ( boiiiFolder , " usermaps " )
2023-08-23 16:27:24 -04:00
folders_to_process = [ mods_folder , maps_folder ]
2023-08-18 20:05:04 -04:00
for folder_path in folders_to_process :
for root , _ , _ in os . walk ( folder_path ) :
zone_path = os . path . join ( root , " zone " )
if os . path . exists ( zone_path ) :
json_path = os . path . join ( zone_path , " workshop.json " )
if os . path . exists ( json_path ) :
name = extract_json_data ( json_path , " Title " ) . replace ( " > " , " " ) . replace ( " ^ " , " " )
2023-08-23 16:27:24 -04:00
name = name [ : 35 ] + " ... " if len ( name ) > 35 else name
2023-08-18 20:05:04 -04:00
item_type = extract_json_data ( json_path , " Type " )
workshop_id = extract_json_data ( json_path , " PublisherID " )
size = convert_bytes_to_readable ( get_folder_size ( root ) )
2023-08-23 16:27:24 -04:00
text_to_add = f " { name } | Type: { item_type } | ID: { workshop_id } | Size: { size } "
2023-08-18 20:05:04 -04:00
if text_to_add not in self . added_items :
self . added_items . add ( text_to_add )
if item_type == " mod " :
2023-08-23 16:27:24 -04:00
image_path = os . path . join ( RESOURCES_DIR , " mod_image.png " )
2023-08-18 20:05:04 -04:00
else :
2023-08-23 16:27:24 -04:00
image_path = os . path . join ( RESOURCES_DIR , " map_image.png " )
2023-08-18 20:05:04 -04:00
self . add_item ( text_to_add , image = ctk . CTkImage ( Image . open ( image_path ) ) , item_type = item_type , workshop_id = workshop_id , folder = root )
if not self . added_items :
self . show_no_items_message ( )
else :
self . hide_no_items_message ( )
def remove_item ( self , item , folder ) :
for label , button , button_view_list in zip ( self . label_list , self . button_list , self . button_view_list ) :
if item == label . cget ( " text " ) :
try :
shutil . rmtree ( folder )
except Exception as e :
show_message ( " Error " , f " Error removing folder ' { folder } ' : { e } " , icon = " cancel " )
return
label . destroy ( )
button . destroy ( )
button_view_list . destroy ( )
self . label_list . remove ( label )
self . button_list . remove ( button )
self . button_view_list . remove ( button_view_list )
def refresh_items ( self ) :
for label , button , button_view_list in zip ( self . label_list , self . button_list , self . button_view_list ) :
label . destroy ( )
button . destroy ( )
button_view_list . destroy ( )
self . label_list . clear ( )
self . button_list . clear ( )
self . button_view_list . clear ( )
self . added_items . clear ( )
self . load_items ( boiiiFolderGlobal )
def view_item ( self , workshop_id ) :
url = f " https://steamcommunity.com/sharedfiles/filedetails/?id= { workshop_id } "
webbrowser . open ( url )
def show_no_items_message ( self ) :
self . no_items_label . grid ( row = 1 , column = 0 , padx = 10 , pady = ( 0 , 10 ) , sticky = " n " )
self . no_items_label . configure ( text = " No items found in the selected folder. \n Make sure you have a mod/map downloaded and or have the right boiii folder selected. " )
def hide_no_items_message ( self ) :
2023-08-23 16:27:24 -04:00
self . no_items_label . configure ( text = " " )
self . no_items_label . forget ( )
2023-08-18 20:05:04 -04:00
# i know i know ,please make a pull request i cant be bother
def show_map_info ( self , workshop ) :
for button_view in self . button_view_list :
button_view . configure ( state = " disabled " )
def show_map_thread ( ) :
workshop_id = workshop
if not workshop_id . isdigit ( ) :
try :
if extract_workshop_id ( workshop_id ) . strip ( ) . isdigit ( ) :
workshop_id = extract_workshop_id ( workshop_id ) . strip ( )
else :
show_message ( " Warning " , " Not a valid Workshop ID. " )
except :
show_message ( " Warning " , " Not a valid Workshop ID. " )
return
try :
url = f " https://steamcommunity.com/sharedfiles/filedetails/?id= { workshop_id } "
response = requests . get ( url )
response . raise_for_status ( )
content = response . text
soup = BeautifulSoup ( content , " html.parser " )
try :
map_mod_type = soup . find ( " div " , class_ = " rightDetailsBlock " ) . text . strip ( )
map_name = soup . find ( " div " , class_ = " workshopItemTitle " ) . text . strip ( )
map_size = map_size = get_workshop_file_size ( workshop_id , raw = True )
details_stats_container = soup . find ( " div " , class_ = " detailsStatsContainerRight " )
details_stat_elements = details_stats_container . find_all ( " div " , class_ = " detailsStatRight " )
date_created = details_stat_elements [ 1 ] . text . strip ( )
try :
ratings = soup . find ( ' div ' , class_ = ' numRatings ' )
ratings_text = ratings . get_text ( )
except :
ratings = " Not found "
ratings_text = " Not enough ratings "
try :
date_updated = details_stat_elements [ 2 ] . text . strip ( )
except :
date_updated = " Not updated "
stars_div = soup . find ( " div " , class_ = " fileRatingDetails " )
starts = stars_div . find ( " img " ) [ " src " ]
except :
show_message ( " Warning " , " Not a valid Workshop ID \n Couldn ' t get information. " )
for button_view in self . button_view_list :
button_view . configure ( state = " normal " )
return
try :
preview_image_element = soup . find ( " img " , id = " previewImage " )
workshop_item_image_url = preview_image_element [ " src " ]
except :
try :
preview_image_element = soup . find ( " img " , id = " previewImageMain " )
workshop_item_image_url = preview_image_element [ " src " ]
except Exception as e :
show_message ( " Warning " , f " Failed to get preview image ,probably wrong link/id if not please open an issue on github. \n { e } " )
for button_view in self . button_view_list :
button_view . configure ( state = " normal " )
return
starts_image_response = requests . get ( starts )
stars_image = Image . open ( io . BytesIO ( starts_image_response . content ) )
stars_image_size = stars_image . size
image_response = requests . get ( workshop_item_image_url )
image_response . raise_for_status ( )
image = Image . open ( io . BytesIO ( image_response . content ) )
image_size = image . size
self . toplevel_info_window ( map_name , map_mod_type , map_size , image , image_size , date_created ,
date_updated , stars_image , stars_image_size , ratings_text , url )
except requests . exceptions . RequestException as e :
show_message ( " Error " , f " Failed to fetch map information. \n Error: { e } " , icon = " cancel " )
for button_view in self . button_view_list :
button_view . configure ( state = " normal " )
return
info_thread = threading . Thread ( target = show_map_thread )
info_thread . start ( )
def toplevel_info_window ( self , map_name , map_mod_type , map_size , image , image_size ,
date_created , date_updated , stars_image , stars_image_size , ratings_text , url ) :
try :
top = ctk . CTkToplevel ( self )
top . after ( 210 , lambda : top . iconbitmap ( os . path . join ( RESOURCES_DIR , " ryuk.ico " ) ) )
top . title ( " Map/Mod Information " )
top . attributes ( ' -topmost ' , ' true ' )
def close_window ( ) :
top . destroy ( )
def view_map_mod ( ) :
webbrowser . open ( url )
# frames
stars_frame = ctk . CTkFrame ( top )
stars_frame . grid ( row = 0 , column = 0 , columnspan = 2 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nsew " )
stars_frame . columnconfigure ( 0 , weight = 0 )
stars_frame . rowconfigure ( 0 , weight = 1 )
image_frame = ctk . CTkFrame ( top )
image_frame . grid ( row = 1 , column = 0 , columnspan = 2 , padx = 20 , pady = 0 , sticky = " nsew " )
info_frame = ctk . CTkFrame ( top )
info_frame . grid ( row = 2 , column = 0 , columnspan = 2 , padx = 20 , pady = 20 , sticky = " nsew " )
buttons_frame = ctk . CTkFrame ( top )
buttons_frame . grid ( row = 3 , column = 0 , columnspan = 2 , padx = 20 , pady = ( 0 , 20 ) , sticky = " nsew " )
# fillers
name_label = ctk . CTkLabel ( info_frame , text = f " Name: { map_name } " )
name_label . grid ( row = 0 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
type_label = ctk . CTkLabel ( info_frame , text = f " Type: { map_mod_type } " )
type_label . grid ( row = 1 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
2023-08-23 16:27:24 -04:00
size_label = ctk . CTkLabel ( info_frame , text = f " Size (Workshop): { map_size } " )
2023-08-18 20:05:04 -04:00
size_label . grid ( row = 2 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
date_created_label = ctk . CTkLabel ( info_frame , text = f " Posted: { date_created } " )
date_created_label . grid ( row = 3 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
date_updated_label = ctk . CTkLabel ( info_frame , text = f " Updated: { date_updated } " )
date_updated_label . grid ( row = 4 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
stars_image_label = ctk . CTkLabel ( stars_frame )
stars_width , stars_height = stars_image_size
stars_image_widget = ctk . CTkImage ( stars_image , size = ( int ( stars_width ) , int ( stars_height ) ) )
stars_image_label . configure ( image = stars_image_widget , text = " " )
stars_image_label . pack ( side = " left " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
ratings = ctk . CTkLabel ( stars_frame )
ratings . configure ( text = ratings_text )
ratings . pack ( side = " right " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
image_label = ctk . CTkLabel ( image_frame )
width , height = image_size
image_widget = ctk . CTkImage ( image , size = ( int ( width ) , int ( height ) ) )
image_label . configure ( image = image_widget , text = " " )
image_label . pack ( expand = True , fill = " both " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
# Buttons
close_button = ctk . CTkButton ( buttons_frame , text = " View " , command = view_map_mod )
close_button . pack ( side = " left " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
view_button = ctk . CTkButton ( buttons_frame , text = " Close " , command = close_window )
view_button . pack ( side = " right " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
top . grid_rowconfigure ( 0 , weight = 0 )
top . grid_rowconfigure ( 1 , weight = 0 )
top . grid_rowconfigure ( 2 , weight = 1 )
top . grid_columnconfigure ( 0 , weight = 1 )
top . grid_columnconfigure ( 1 , weight = 1 )
finally :
for button_view in self . button_view_list :
button_view . configure ( state = " normal " )
2023-08-16 19:02:57 -04:00
2023-08-12 12:32:54 -04:00
class SettingsTab ( ctk . CTkFrame ) :
def __init__ ( self , master = None ) :
super ( ) . __init__ ( master )
2023-08-13 13:48:28 -04:00
# Left and right frames, use fg_color="transparent"
self . grid_rowconfigure ( 0 , weight = 1 )
self . grid_columnconfigure ( 1 , weight = 1 )
self . grid_columnconfigure ( 0 , weight = 1 )
left_frame = ctk . CTkFrame ( self )
left_frame . grid ( row = 0 , column = 0 , padx = ( 20 , 20 ) , pady = ( 20 , 0 ) , sticky = " nsew " )
left_frame . grid_columnconfigure ( 1 , weight = 1 )
right_frame = ctk . CTkFrame ( self )
right_frame . grid ( row = 0 , column = 1 , padx = ( 20 , 20 ) , pady = ( 20 , 0 ) , sticky = " nsew " )
right_frame . grid_columnconfigure ( 1 , weight = 1 )
self . update_idletasks ( )
2023-08-12 12:32:54 -04:00
# Check for updates checkbox
self . check_updates_var = ctk . BooleanVar ( )
self . check_updates_var . trace_add ( " write " , self . enable_save_button )
2023-08-13 13:48:28 -04:00
self . check_updates_checkbox = ctk . CTkSwitch ( left_frame , text = " Check for updates on launch " , variable = self . check_updates_var )
self . check_updates_checkbox . grid ( row = 0 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nw " )
self . check_updates_var . set ( self . load_settings ( " checkforupdates " ) )
2023-08-12 12:32:54 -04:00
# Show console checkbox
self . console_var = ctk . BooleanVar ( )
self . console_var . trace_add ( " write " , self . enable_save_button )
2023-08-13 13:48:28 -04:00
self . checkbox_show_console = ctk . CTkSwitch ( left_frame , text = " Console (On Download) " , variable = self . console_var )
self . checkbox_show_console . grid ( row = 1 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nw " )
self . checkbox_show_console_tooltip = CTkToolTip ( self . checkbox_show_console , message = " Toggle SteamCMD console \n Please don ' t close the Console If you want to stop press the Stop button " )
self . console_var . set ( self . load_settings ( " console " ) )
2023-08-12 12:32:54 -04:00
2023-08-16 05:05:59 -04:00
# Show continuous checkbox
self . continuous_var = ctk . BooleanVar ( )
self . continuous_var . trace_add ( " write " , self . enable_save_button )
self . checkbox_continuous = ctk . CTkSwitch ( left_frame , text = " Continuous Download " , variable = self . continuous_var )
self . checkbox_continuous . grid ( row = 2 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nw " )
self . checkbox_continuous_tooltip = CTkToolTip ( self . checkbox_continuous , message = " This will make sure that the download restarts and resumes! until it finishes if steamcmd crashes randomly (it will not redownload from the start) " )
self . continuous_var . set ( self . load_settings ( " continuous_download " ) )
2023-08-14 20:59:35 -04:00
# clean on finish checkbox
self . clean_checkbox_var = ctk . BooleanVar ( )
self . clean_checkbox_var . trace_add ( " write " , self . enable_save_button )
self . clean_checkbox = ctk . CTkSwitch ( left_frame , text = " Clean on finish " , variable = self . clean_checkbox_var )
2023-08-16 05:05:59 -04:00
self . clean_checkbox . grid ( row = 3 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nw " )
2023-08-14 20:59:35 -04:00
self . clean_checkbox_tooltip = CTkToolTip ( self . clean_checkbox , message = " Cleans the map that have been downloaded and installed from steamcmd ' s steamapps folder ,to save space " )
self . clean_checkbox_var . set ( self . load_settings ( " clean_on_finish " , " on " ) )
2023-08-16 05:05:59 -04:00
# Show estimated_progress checkbox
self . estimated_progress_var = ctk . BooleanVar ( )
self . estimated_progress_var . trace_add ( " write " , self . enable_save_button )
self . estimated_progress = ctk . CTkSwitch ( left_frame , text = " Estimated Progress Bar " , variable = self . estimated_progress_var )
self . estimated_progress . grid ( row = 4 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nw " )
self . estimated_progress_var_tooltip = CTkToolTip ( self . estimated_progress , message = " This will change how to progress bar works by estimating how long the download will take \
\nThis is not accurate , it ' s better than with it off which is calculating the downloaded folder size which steamcmd dumps the full size rigth mostly " )
self . estimated_progress_var . set ( self . load_settings ( " estimated_progress " , " on " ) )
2023-08-27 15:37:29 -04:00
# Show estimated_progress checkbox
self . show_fails_var = ctk . BooleanVar ( )
self . show_fails_var . trace_add ( " write " , self . enable_save_button )
self . show_fails = ctk . CTkSwitch ( left_frame , text = " Show fails (on top of progress bar): " , variable = self . show_fails_var )
self . show_fails . grid ( row = 5 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nw " )
self . show_fails_tooltip = CTkToolTip ( self . show_fails , message = " Display how many times steamcmd has failed/crashed \n If the number is getting high quickly then try pressing Reset SteamCMD and try again, otherwise its fine " )
self . estimated_progress_var . set ( self . load_settings ( " show_fails " , " on " ) )
2023-08-25 15:08:08 -04:00
# Resetr steam on many fails
self . reset_steamcmd_on_fail_var = ctk . IntVar ( )
self . reset_steamcmd_on_fail_var . trace_add ( " write " , self . enable_save_button )
self . reset_steamcmd_on_fail_text = ctk . CTkLabel ( left_frame , text = f " Reset steamcmd on % fails: (n of fails) " , anchor = " w " )
2023-08-27 15:37:29 -04:00
self . reset_steamcmd_on_fail_text . grid ( row = 6 , column = 1 , padx = 20 , pady = ( 10 , 0 ) , sticky = " nw " )
self . reset_steamcmd_on_fail = ctk . CTkOptionMenu ( left_frame , values = [ " 20 " , " 30 " , " 40 " , " Custom " , " Disable " ] , variable = self . reset_steamcmd_on_fail_var , command = self . reset_steamcmd_on_fail_func )
self . reset_steamcmd_on_fail . grid ( row = 7 , column = 1 , padx = 20 , pady = ( 0 , 0 ) , sticky = " nw " )
2023-08-25 15:08:08 -04:00
self . reset_steamcmd_on_fail_tooltip = CTkToolTip ( self . reset_steamcmd_on_fail , message = " This actually fixes steamcmd when its crashing way too much " )
2023-08-27 15:37:29 -04:00
self . reset_steamcmd_on_fail . set ( value = self . load_settings ( " reset_on_fail " , " Disable " ) )
2023-08-25 15:08:08 -04:00
2023-08-14 20:59:35 -04:00
# Check for updates button n Launch boiii
self . check_for_updates = ctk . CTkButton ( right_frame , text = " Check for updates " , command = self . settings_check_for_updates )
self . check_for_updates . grid ( row = 1 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " n " )
self . launch_boiii = ctk . CTkButton ( right_frame , text = " Launch boiii " , command = self . settings_launch_boiii )
self . launch_boiii . grid ( row = 2 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " n " )
self . reset_steamcmd = ctk . CTkButton ( right_frame , text = " Reset SteamCMD " , command = self . settings_reset_steamcmd )
self . reset_steamcmd . grid ( row = 3 , column = 1 , padx = 20 , pady = ( 20 , 0 ) , sticky = " n " )
self . reset_steamcmd_tooltip = CTkToolTip ( self . reset_steamcmd , message = " This will remove steamapps folder + all the maps that are potentioaly corrupted or not so use at ur own risk (could fix some issues as well) " )
2023-08-12 12:32:54 -04:00
2023-08-23 16:27:24 -04:00
# appearance
self . appearance_mode_label = ctk . CTkLabel ( right_frame , text = " Appearance Mode: " , anchor = " n " )
self . appearance_mode_label . grid ( row = 4 , column = 1 , padx = 20 , pady = ( 20 , 0 ) )
self . appearance_mode_optionemenu = ctk . CTkOptionMenu ( right_frame , values = [ " Light " , " Dark " , " System " ] ,
command = master . change_appearance_mode_event )
self . appearance_mode_optionemenu . grid ( row = 5 , column = 1 , padx = 20 , pady = ( 0 , 0 ) )
self . scaling_label = ctk . CTkLabel ( right_frame , text = " UI Scaling: " , anchor = " n " )
self . scaling_label . grid ( row = 6 , column = 1 , padx = 20 , pady = ( 10 , 0 ) )
self . scaling_optionemenu = ctk . CTkOptionMenu ( right_frame , values = [ " 80 % " , " 90 % " , " 100 % " , " 110 % " , " 120 % " ] ,
command = master . change_scaling_event )
self . scaling_optionemenu . grid ( row = 7 , column = 1 , padx = 20 , pady = ( 0 , 0 ) )
# self.custom_theme = ctk.CTkButton(right_frame, text="Custom theme", command=self.boiiiwd_custom_theme)
# self.custom_theme.grid(row=8, column=1, padx=20, pady=(20, 0), sticky="n")
self . theme_options_label = ctk . CTkLabel ( right_frame , text = " Themes: " , anchor = " n " )
self . theme_options_label . grid ( row = 8 , column = 1 , padx = 20 , pady = ( 10 , 0 ) )
self . theme_options = ctk . CTkOptionMenu ( right_frame , values = [ " Default " , " Blue " , " Grey " , " Custom " ] ,
command = self . theme_options_func )
self . theme_options . grid ( row = 9 , column = 1 , padx = 20 , pady = ( 0 , 0 ) )
self . theme_options . set ( value = self . load_settings ( " theme " , " Default " ) )
2023-08-12 12:32:54 -04:00
# Save button
self . save_button = ctk . CTkButton ( self , text = " Save " , command = self . save_settings , state = ' disabled ' )
2023-08-13 13:48:28 -04:00
self . save_button . grid ( row = 3 , column = 0 , padx = 20 , pady = ( 20 , 20 ) , sticky = " nw " )
2023-08-19 06:27:22 -04:00
#version
self . version_info = ctk . CTkLabel ( self , text = f " { VERSION } " )
self . version_info . grid ( row = 3 , column = 1 , padx = 20 , pady = ( 20 , 20 ) , sticky = " e " )
2023-08-12 12:32:54 -04:00
2023-08-27 15:37:29 -04:00
def reset_steamcmd_on_fail_func ( self , option : str ) :
if option == " Custom " :
try :
save_config ( " reset_on_fail " , self . reset_steamcmd_on_fail . get ( ) )
if show_message ( " config.ini " , " change reset_on_fail value to whatever you want " , exit_on_close = True ) :
os . system ( f " notepad { os . path . join ( cwd ( ) , ' config.ini ' ) } " )
except :
show_message ( " Couldn ' t open config.ini " , " you can do so by yourself and change reset_on_fail value to whatever you want " )
else :
pass
2023-08-23 16:27:24 -04:00
def theme_options_func ( self , option : str ) :
if option == " Default " :
self . boiiiwd_custom_theme ( disable_only = True )
save_config ( " theme " , " boiiiwd_theme.json " )
if option == " Blue " :
self . boiiiwd_custom_theme ( disable_only = True )
save_config ( " theme " , " boiiiwd_blue.json " )
if option == " Grey " :
self . boiiiwd_custom_theme ( disable_only = True )
save_config ( " theme " , " boiiiwd_grey.json " )
if option == " Custom " :
self . boiiiwd_custom_theme ( )
save_config ( " theme " , " boiiiwd_theme.json " )
if not option == " Custom " :
show_message ( " Restart to take effect! " , f " { option } theme has been set ,please restart to take effect " , icon = " info " )
2023-08-12 12:32:54 -04:00
def enable_save_button ( self , * args ) :
2023-08-13 13:48:28 -04:00
try :
self . save_button . configure ( state = ' normal ' )
except :
pass
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
def save_settings ( self ) :
self . save_button . configure ( state = ' disabled ' )
2023-08-27 15:37:29 -04:00
global console , clean_on_finish , continuous , estimated_progress , steam_fail_number , steam_fail_counter_toggle , show_fails
2023-08-12 12:32:54 -04:00
if self . check_updates_checkbox . get ( ) :
save_config ( " checkforupdtes " , " on " )
else :
save_config ( " checkforupdtes " , " off " )
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
if self . checkbox_show_console . get ( ) :
save_config ( " console " , " on " )
console = True
else :
save_config ( " console " , " off " )
console = False
2023-08-03 04:13:02 -04:00
2023-08-14 20:59:35 -04:00
if self . clean_checkbox . get ( ) :
save_config ( " clean_on_finish " , " on " )
clean_on_finish = True
else :
save_config ( " clean_on_finish " , " off " )
clean_on_finish = False
2023-08-16 05:05:59 -04:00
if self . checkbox_continuous . get ( ) :
save_config ( " continuous_download " , " on " )
continuous = True
else :
save_config ( " continuous_download " , " off " )
continuous = False
if self . estimated_progress . get ( ) :
save_config ( " estimated_progress " , " on " )
estimated_progress = True
else :
save_config ( " estimated_progress " , " off " )
estimated_progress = False
2023-08-27 15:37:29 -04:00
if self . show_fails . get ( ) :
save_config ( " show_fails " , " on " )
show_fails = True
else :
save_config ( " show_fails " , " off " )
show_fails = False
2023-08-25 15:08:08 -04:00
if self . reset_steamcmd_on_fail . get ( ) :
value = self . reset_steamcmd_on_fail . get ( )
if value == " Disable " :
steam_fail_counter_toggle = False
else :
steam_fail_counter_toggle = True
steam_fail_number = int ( value )
save_config ( " reset_on_fail " , value )
2023-08-14 20:59:35 -04:00
def load_settings ( self , setting , fallback = None ) :
2023-08-27 15:37:29 -04:00
global console , clean_on_finish , continuous , estimated_progress , steam_fail_counter_toggle , steam_fail_number , show_fails
2023-08-13 13:48:28 -04:00
if setting == " console " :
2023-08-14 20:59:35 -04:00
if check_config ( setting , fallback ) == " on " :
2023-08-13 13:48:28 -04:00
console = True
2023-08-12 12:32:54 -04:00
return 1
else :
2023-08-13 13:48:28 -04:00
console = False
2023-08-12 12:32:54 -04:00
return 0
2023-08-16 05:05:59 -04:00
if setting == " continuous_download " :
if check_config ( setting , " on " ) == " on " :
continuous = True
return 1
else :
continuous = False
return 0
2023-08-14 20:59:35 -04:00
if setting == " clean_on_finish " :
if check_config ( setting , fallback ) == " on " :
clean_on_finish = True
return 1
else :
clean_on_finish = False
return 0
2023-08-16 05:05:59 -04:00
if setting == " estimated_progress " :
if check_config ( setting , fallback ) == " on " :
estimated_progress = True
return 1
else :
estimated_progress = False
return 0
2023-08-23 16:27:24 -04:00
2023-08-25 15:08:08 -04:00
if setting == " reset_on_fail " :
option = check_config ( setting , fallback )
2023-08-27 15:37:29 -04:00
if option == " Disable " or option == " Custom " :
2023-08-25 15:08:08 -04:00
steam_fail_counter_toggle = False
return " Disable "
else :
try :
steam_fail_number = int ( option )
return option
except :
2023-08-27 15:37:29 -04:00
if steam_fail_counter_toggle :
steam_fail_number = 10
return " 10 "
else :
steam_fail_number = 10
return " Disable "
if setting == " show_fails " :
if check_config ( setting , fallback ) == " on " :
show_fails = True
return 1
else :
show_fails = False
return 0
2023-08-25 15:08:08 -04:00
2023-08-23 16:27:24 -04:00
if setting == " theme " :
if os . path . exists ( os . path . join ( cwd ( ) , " boiiiwd_theme.json " ) ) :
return " Custom "
if check_config ( " theme " , " boiiiwd_theme.json " ) == " boiiiwd_theme.json " :
return " Default "
if check_config ( " theme " , " boiiiwd_theme.json " ) == " boiiiwd_grey.json " :
return " Grey "
if check_config ( " theme " , " boiiiwd_theme.json " ) == " boiiiwd_blue.json " :
return " Blue "
2023-08-13 13:48:28 -04:00
else :
2023-08-14 20:59:35 -04:00
if check_config ( setting , fallback ) == " on " :
2023-08-12 12:32:54 -04:00
return 1
else :
return 0
2023-08-03 04:13:02 -04:00
2023-08-23 16:27:24 -04:00
def boiiiwd_custom_theme ( self , disable_only = None ) :
file_to_rename = os . path . join ( cwd ( ) , " boiiiwd_theme.json " )
if os . path . exists ( file_to_rename ) :
timestamp = datetime . datetime . now ( ) . strftime ( " % Y- % m- %d - % H- % M- % S " )
new_name = f " boiiiwd_theme_ { timestamp } .json "
os . rename ( file_to_rename , os . path . join ( cwd ( ) , new_name ) )
if not disable_only :
show_message ( " Preset file renamed " , " Custom preset disabled, file has been renmaed \n * Restart the app to take effect " , icon = " info " )
else :
if disable_only :
return
try :
shutil . copy ( os . path . join ( RESOURCES_DIR , check_config ( " theme " , " boiiiwd_theme.json " ) ) , os . path . join ( cwd ( ) , " boiiiwd_theme.json " ) )
except :
shutil . copy ( os . path . join ( RESOURCES_DIR , " boiiiwd_theme.json " ) , os . path . join ( cwd ( ) , " boiiiwd_theme.json " ) )
show_message ( " Preset file created " , " You can now edit boiiiwd_theme.json in the current directory to your liking \n * Edits will apply next time you open boiiiwd \n * Program will always take boiiiwd_theme.json as the first theme option if found \n * Click on this button again to disable your custom theme or just rename boiiiwd_theme.json " , icon = " info " )
2023-08-14 20:59:35 -04:00
def settings_check_for_updates ( self ) :
check_for_updates_func ( self , ignore_up_todate = False )
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
def load_on_switch_screen ( self ) :
2023-08-13 13:48:28 -04:00
self . check_updates_var . set ( self . load_settings ( " checkforupdtes " ) )
self . console_var . set ( self . load_settings ( " console " ) )
2023-08-27 15:37:29 -04:00
self . reset_steamcmd_on_fail . set ( value = self . load_settings ( " reset_on_fail " , " Disable " ) )
2023-08-25 15:08:08 -04:00
self . estimated_progress_var . set ( self . load_settings ( " estimated_progress " , " on " ) )
self . clean_checkbox_var . set ( self . load_settings ( " clean_on_finish " , " on " ) )
self . continuous_var . set ( self . load_settings ( " continuous_download " ) )
2023-08-27 15:37:29 -04:00
self . show_fails_var . set ( self . load_settings ( " show_fails " , " on " ) )
2023-07-31 02:02:53 -04:00
2023-08-12 12:32:54 -04:00
# keep last cuz of trace_add()
self . save_button . configure ( state = ' disabled ' )
2023-07-31 02:02:53 -04:00
2023-08-14 20:59:35 -04:00
def settings_launch_boiii ( self ) :
launch_boiii_func ( check_config ( " destinationfolder " ) )
def settings_reset_steamcmd ( self ) :
2023-08-25 15:08:08 -04:00
reset_steamcmd ( )
2023-08-14 20:59:35 -04:00
2023-08-12 12:32:54 -04:00
class BOIIIWD ( ctk . CTk ) :
def __init__ ( self ) :
2023-07-31 02:02:53 -04:00
super ( ) . __init__ ( )
2023-08-12 12:32:54 -04:00
# self.app_instance = BOIIIWD()
# configure window
self . title ( " BOIII Workshop Downloader - Main " )
2023-08-23 16:27:24 -04:00
try :
geometry_file = os . path . join ( cwd ( ) , " boiiiwd_dont_touch.conf " )
if os . path . isfile ( geometry_file ) :
with open ( geometry_file , " r " ) as conf :
self . geometry ( conf . read ( ) )
else :
self . geometry ( f " { 910 } x { 560 } " )
except :
self . geometry ( f " { 910 } x { 560 } " )
2023-08-18 20:05:04 -04:00
self . wm_iconbitmap ( os . path . join ( RESOURCES_DIR , " ryuk.ico " ) )
2023-08-16 05:05:59 -04:00
self . protocol ( " WM_DELETE_WINDOW " , self . on_closing )
2023-08-12 12:32:54 -04:00
2023-08-27 15:37:29 -04:00
# Qeue frame/tab, keep here or app will start shrinked eveytime
self . qeueuframe = ctk . CTkFrame ( self )
self . qeueuframe . columnconfigure ( 1 , weight = 1 )
self . qeueuframe . columnconfigure ( 2 , weight = 1 )
self . qeueuframe . columnconfigure ( 3 , weight = 1 )
self . qeueuframe . rowconfigure ( 1 , weight = 1 )
self . qeueuframe . rowconfigure ( 2 , weight = 1 )
self . qeueuframe . rowconfigure ( 3 , weight = 1 )
self . qeueuframe . rowconfigure ( 4 , weight = 1 )
self . workshop_queue_label = ctk . CTkLabel ( self . qeueuframe , text = " Workshop IDs/Links -> press help to see examples: " )
self . workshop_queue_label . grid ( row = 0 , column = 0 , padx = ( 20 , 20 ) , pady = ( 20 , 20 ) , sticky = " wns " )
self . help_button = ctk . CTkButton ( master = self . qeueuframe , text = " help " , command = self . help_queue_text_func , width = 10 , height = 10 , fg_color = " #585858 " )
self . help_button . grid ( row = 0 , column = 1 , padx = ( 0 , 20 ) , pady = ( 20 , 20 ) , sticky = " wns " )
self . help_button_tooltip = CTkToolTip ( self . help_button , message = " This only works if the text area is empty (press twice if you had something in it) " )
self . queuetextarea = ctk . CTkTextbox ( master = self . qeueuframe , font = ( " " , 15 ) )
self . queuetextarea . grid ( row = 1 , column = 0 , columnspan = 2 , rowspan = 2 , padx = ( 20 , 20 ) , pady = ( 0 , 20 ) , sticky = " nwse " )
self . status_text = ctk . CTkLabel ( self . qeueuframe , text = " Status: Not Downloading " )
self . status_text . grid ( row = 3 , column = 0 , padx = ( 20 , 20 ) , pady = ( 0 , 20 ) , sticky = " ws " )
self . qeueuframe . grid_remove ( )
2023-08-12 12:32:54 -04:00
# configure grid layout (4x4)
self . grid_columnconfigure ( 1 , weight = 1 )
self . grid_columnconfigure ( ( 2 , 3 ) , weight = 0 )
self . grid_rowconfigure ( ( 0 , 1 , 2 ) , weight = 1 )
self . settings_tab = SettingsTab ( self )
2023-08-18 20:05:04 -04:00
self . library_tab = LibraryTab ( self , corner_radius = 3 )
2023-08-12 12:32:54 -04:00
# create sidebar frame with widgets
2023-08-23 16:27:24 -04:00
font = " Comic Sans MS "
2023-08-18 20:05:04 -04:00
ryuks_icon = os . path . join ( RESOURCES_DIR , " ryuk.png " )
self . sidebar_icon = ctk . CTkImage ( light_image = Image . open ( ryuks_icon ) , dark_image = Image . open ( ryuks_icon ) , size = ( 40 , 40 ) )
2023-08-23 16:27:24 -04:00
self . sidebar_frame = ctk . CTkFrame ( self , width = 100 , corner_radius = 10 )
self . sidebar_frame . grid ( row = 0 , column = 0 , rowspan = 3 , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) , sticky = " nsew " )
2023-08-12 12:32:54 -04:00
self . sidebar_frame . grid_rowconfigure ( 4 , weight = 1 )
self . logo_label = ctk . CTkLabel ( self . sidebar_frame , text = ' ' , image = self . sidebar_icon )
self . logo_label . grid ( row = 0 , column = 0 , padx = 20 , pady = ( 20 , 10 ) )
2023-08-23 16:27:24 -04:00
self . txt_label = ctk . CTkLabel ( self . sidebar_frame , text = " - Sidebar - " , font = ( font , 17 ) )
2023-08-12 12:32:54 -04:00
self . txt_label . grid ( row = 1 , column = 0 , padx = 20 , pady = ( 20 , 10 ) )
2023-08-14 20:59:35 -04:00
self . sidebar_main = ctk . CTkButton ( self . sidebar_frame )
self . sidebar_main . grid ( row = 2 , column = 0 , padx = 20 , pady = 10 )
self . sidebar_queue = ctk . CTkButton ( self . sidebar_frame )
2023-08-27 15:37:29 -04:00
self . sidebar_queue . grid ( row = 3 , column = 0 , padx = 20 , pady = 10 )
self . sidebar_library = ctk . CTkButton ( self . sidebar_frame )
self . sidebar_library . grid ( row = 4 , column = 0 , padx = 20 , pady = 10 , sticky = " n " )
2023-08-14 20:59:35 -04:00
self . sidebar_settings = ctk . CTkButton ( self . sidebar_frame )
2023-08-23 16:27:24 -04:00
self . sidebar_settings . grid ( row = 5 , column = 0 , padx = 20 , pady = 10 , sticky = " n " )
2023-08-12 12:32:54 -04:00
# create optionsframe
self . optionsframe = ctk . CTkFrame ( self )
2023-08-23 16:27:24 -04:00
self . optionsframe . grid ( row = 0 , column = 1 , rowspan = 2 , padx = ( 0 , 20 ) , pady = ( 20 , 0 ) , sticky = " nsew " )
self . txt_main = ctk . CTkLabel ( self . optionsframe , text = " 💎 BOIIIWD 💎 " , font = ( font , 20 ) )
self . txt_main . grid ( row = 0 , column = 1 , columnspan = 5 , padx = 0 , pady = ( 20 , 20 ) , sticky = " n " )
2023-08-12 12:32:54 -04:00
# create slider and progressbar frame
self . slider_progressbar_frame = ctk . CTkFrame ( self )
2023-08-23 16:27:24 -04:00
self . slider_progressbar_frame . grid ( row = 2 , column = 1 , rowspan = 1 , padx = ( 0 , 20 ) , pady = ( 20 , 20 ) , sticky = " nsew " )
2023-08-12 12:32:54 -04:00
self . slider_progressbar_frame . columnconfigure ( 0 , weight = 0 )
self . slider_progressbar_frame . columnconfigure ( 1 , weight = 1 )
self . slider_progressbar_frame . columnconfigure ( 2 , weight = 0 )
self . slider_progressbar_frame . rowconfigure ( 0 , weight = 1 )
self . slider_progressbar_frame . rowconfigure ( 1 , weight = 1 )
self . slider_progressbar_frame . rowconfigure ( 2 , weight = 1 )
self . slider_progressbar_frame . rowconfigure ( 3 , weight = 1 )
2023-08-16 19:02:57 -04:00
# self.spacer = ctk.CTkLabel(master=self.slider_progressbar_frame, text="")
# self.spacer.grid(row=0, column=0, columnspan=1)
2023-08-12 12:32:54 -04:00
self . label_speed = ctk . CTkLabel ( master = self . slider_progressbar_frame , text = " Network Speed: 0 KB/s " )
self . label_speed . grid ( row = 1 , column = 0 , padx = 20 , pady = ( 0 , 10 ) , sticky = " w " )
2023-08-16 05:05:59 -04:00
self . elapsed_time = ctk . CTkLabel ( master = self . slider_progressbar_frame , text = " " )
2023-08-16 19:02:57 -04:00
self . elapsed_time . grid ( row = 1 , column = 1 , padx = 20 , pady = ( 0 , 10 ) , sticky = " nsew " , columnspan = 1 )
2023-08-16 05:05:59 -04:00
2023-08-12 12:32:54 -04:00
self . label_file_size = ctk . CTkLabel ( master = self . slider_progressbar_frame , text = " File size: 0KB " )
self . label_file_size . grid ( row = 1 , column = 2 , padx = ( 0 , 20 ) , pady = ( 0 , 10 ) , sticky = " e " )
self . progress_bar = ctk . CTkProgressBar ( master = self . slider_progressbar_frame , mode = " determinate " , height = 20 , corner_radius = 7 )
self . progress_bar . grid ( row = 2 , column = 0 , padx = 20 , pady = ( 0 , 10 ) , columnspan = 3 , sticky = " ew " )
self . progress_text = ctk . CTkLabel ( self . progress_bar , text = " 0 % " , font = ( " Helvetica " , 12 ) , fg_color = " transparent " , height = 0 , width = 0 , corner_radius = 0 )
self . progress_text . place ( relx = 0.5 , rely = 0.5 , anchor = " center " )
self . button_download = ctk . CTkButton ( master = self . slider_progressbar_frame , text = " Download " , command = self . download_map )
2023-08-16 19:02:57 -04:00
self . button_download . grid ( row = 4 , column = 0 , padx = 20 , pady = ( 5 , 20 ) , columnspan = 2 , sticky = " ew " )
2023-08-12 12:32:54 -04:00
self . button_stop = ctk . CTkButton ( master = self . slider_progressbar_frame , text = " Stop " , command = self . stop_download )
2023-08-27 15:37:29 -04:00
self . button_stop . grid ( row = 4 , column = 2 , padx = ( 0 , 20 ) , pady = ( 5 , 20 ) , columnspan = 1 , sticky = " w " )
2023-08-12 12:32:54 -04:00
# options frame
self . optionsframe . columnconfigure ( 1 , weight = 1 )
self . optionsframe . columnconfigure ( 2 , weight = 1 )
self . optionsframe . columnconfigure ( 3 , weight = 1 )
2023-08-23 16:27:24 -04:00
self . optionsframe . rowconfigure ( 1 , weight = 1 )
self . optionsframe . rowconfigure ( 2 , weight = 1 )
self . optionsframe . rowconfigure ( 3 , weight = 1 )
self . optionsframe . rowconfigure ( 4 , weight = 1 )
2023-08-12 12:32:54 -04:00
2023-08-16 19:02:57 -04:00
self . label_workshop_id = ctk . CTkLabel ( master = self . optionsframe , text = " Enter the Workshop ID or Link of the map/mod you want to download: \n " )
2023-08-23 16:27:24 -04:00
self . label_workshop_id . grid ( row = 1 , column = 1 , padx = 20 , pady = ( 10 , 0 ) , columnspan = 4 , sticky = " ws " )
2023-08-12 12:32:54 -04:00
self . check_if_changed = ctk . StringVar ( )
self . check_if_changed . trace_add ( " write " , self . id_chnaged_handler )
self . edit_workshop_id = ctk . CTkEntry ( master = self . optionsframe , textvariable = self . check_if_changed )
2023-08-23 16:27:24 -04:00
self . edit_workshop_id . grid ( row = 2 , column = 1 , padx = 20 , pady = ( 0 , 10 ) , columnspan = 4 , sticky = " ewn " )
2023-08-12 12:32:54 -04:00
2023-08-16 19:02:57 -04:00
self . button_browse = ctk . CTkButton ( master = self . optionsframe , text = " Workshop " , command = self . open_browser , width = 10 )
2023-08-23 16:27:24 -04:00
self . button_browse . grid ( row = 2 , column = 5 , padx = ( 0 , 20 ) , pady = ( 0 , 10 ) , sticky = " en " )
2023-08-16 19:02:57 -04:00
self . button_browse_tooltip = CTkToolTip ( self . button_browse , message = " Will open steam workshop for boiii in your browser " )
2023-08-12 12:32:54 -04:00
2023-08-16 19:02:57 -04:00
self . info_button = ctk . CTkButton ( master = self . optionsframe , text = " Details " , command = self . show_map_info , width = 10 )
2023-08-23 16:27:24 -04:00
self . info_button . grid ( row = 2 , column = 5 , padx = ( 0 , 20 ) , pady = ( 0 , 10 ) , sticky = " wn " )
2023-08-12 12:32:54 -04:00
self . label_destination_folder = ctk . CTkLabel ( master = self . optionsframe , text = " Enter Your BOIII folder: " )
2023-08-23 16:27:24 -04:00
self . label_destination_folder . grid ( row = 3 , column = 1 , padx = 20 , pady = ( 0 , 0 ) , columnspan = 4 , sticky = " ws " )
2023-08-12 12:32:54 -04:00
self . edit_destination_folder = ctk . CTkEntry ( master = self . optionsframe , placeholder_text = " Your BOIII Instalation folder " )
2023-08-23 16:27:24 -04:00
self . edit_destination_folder . grid ( row = 4 , column = 1 , padx = 20 , pady = ( 0 , 25 ) , columnspan = 4 , sticky = " ewn " )
2023-08-12 12:32:54 -04:00
self . button_BOIII_browse = ctk . CTkButton ( master = self . optionsframe , text = " Select " , command = self . open_BOIII_browser )
2023-08-23 16:27:24 -04:00
self . button_BOIII_browse . grid ( row = 4 , column = 5 , padx = ( 0 , 20 ) , pady = ( 0 , 10 ) , sticky = " ewn " )
2023-08-12 12:32:54 -04:00
self . label_steamcmd_path = ctk . CTkLabel ( master = self . optionsframe , text = " Enter SteamCMD path: " )
2023-08-23 16:27:24 -04:00
self . label_steamcmd_path . grid ( row = 5 , column = 1 , padx = 20 , pady = ( 0 , 0 ) , columnspan = 3 , sticky = " wn " )
2023-08-12 12:32:54 -04:00
self . edit_steamcmd_path = ctk . CTkEntry ( master = self . optionsframe , placeholder_text = " Enter your SteamCMD path " )
2023-08-23 16:27:24 -04:00
self . edit_steamcmd_path . grid ( row = 6 , column = 1 , padx = 20 , pady = ( 0 , 30 ) , columnspan = 4 , sticky = " ewn " )
2023-08-12 12:32:54 -04:00
self . button_steamcmd_browse = ctk . CTkButton ( master = self . optionsframe , text = " Select " , command = self . open_steamcmd_path_browser )
2023-08-23 16:27:24 -04:00
self . button_steamcmd_browse . grid ( row = 6 , column = 5 , padx = ( 0 , 20 ) , pady = ( 0 , 30 ) , sticky = " ewn " )
2023-08-12 12:32:54 -04:00
2023-08-27 15:37:29 -04:00
2023-08-12 12:32:54 -04:00
# set default values
2023-08-23 16:27:24 -04:00
self . active_color = get_button_state_colors ( check_custom_theme ( check_config ( " theme " , fallback = " boiiiwd_theme.json " ) ) , " button_active_state_color " )
self . normal_color = get_button_state_colors ( check_custom_theme ( check_config ( " theme " , fallback = " boiiiwd_theme.json " ) ) , " button_normal_state_color " )
self . progress_color = get_button_state_colors ( check_custom_theme ( check_config ( " theme " , fallback = " boiiiwd_theme.json " ) ) , " progress_bar_fill_color " )
self . settings_tab . appearance_mode_optionemenu . set ( " Dark " )
self . settings_tab . scaling_optionemenu . set ( " 100 % " )
2023-08-12 13:03:45 -04:00
self . progress_bar . set ( 0.0 )
2023-08-23 16:27:24 -04:00
self . progress_bar . configure ( progress_color = self . progress_color )
2023-08-12 12:32:54 -04:00
self . hide_settings_widgets ( )
self . button_stop . configure ( state = " disabled " )
2023-08-27 15:37:29 -04:00
self . is_pressed = False
self . queue_enabled = False
self . queue_stop_button = False
2023-08-12 12:32:54 -04:00
# sidebar windows bouttons
2023-08-23 16:27:24 -04:00
self . sidebar_main . configure ( command = self . main_button_event , text = " Main ⬇️ " , fg_color = ( self . active_color ) , state = " active " )
self . sidebar_library . configure ( text = " Library 📙 " , command = self . library_button_event )
2023-08-27 15:37:29 -04:00
self . sidebar_queue . configure ( text = " Queue 🚧 " , command = self . queue_button_event )
2023-08-23 16:27:24 -04:00
sidebar_settings_button_image = os . path . join ( RESOURCES_DIR , " sett10.png " )
self . sidebar_settings . configure ( command = self . settings_button_event , text = " " , image = ctk . CTkImage ( Image . open ( sidebar_settings_button_image ) , size = ( int ( 35 ) , int ( 35 ) ) ) , fg_color = " transparent " , width = 45 , height = 45 )
self . sidebar_settings_tooltip = CTkToolTip ( self . sidebar_settings , message = " Settings " )
2023-08-18 20:05:04 -04:00
self . sidebar_library_tooltip = CTkToolTip ( self . sidebar_library , message = " Experimental " )
2023-08-27 15:37:29 -04:00
self . sidebar_queue_tooltip = CTkToolTip ( self . sidebar_queue , message = " Experimental " )
2023-08-23 16:27:24 -04:00
self . bind ( " <Configure> " , self . save_window_size )
2023-08-12 12:32:54 -04:00
# load ui configs
self . load_configs ( )
2023-07-31 02:02:53 -04:00
2023-08-12 12:32:54 -04:00
if check_config ( " checkforupdtes " ) == " on " :
self . withdraw ( )
2023-08-14 20:59:35 -04:00
check_for_updates_func ( self , ignore_up_todate = True )
2023-08-12 12:32:54 -04:00
self . update ( )
self . deiconify ( )
2023-07-31 02:02:53 -04:00
2023-08-12 12:32:54 -04:00
try :
2023-08-16 05:05:59 -04:00
self . settings_tab . load_settings ( " clean_on_finish " , " on " )
self . settings_tab . load_settings ( " continuous_download " , " on " )
self . settings_tab . load_settings ( " console " , " off " )
self . settings_tab . load_settings ( " estimated_progress " , " on " )
2023-08-27 15:37:29 -04:00
self . settings_tab . load_settings ( " reset_on_fail " , " Disable " )
self . settings_tab . load_settings ( " show_fails " , " on " )
2023-08-12 12:32:54 -04:00
except :
pass
2023-07-31 16:44:31 -04:00
if not check_steamcmd ( ) :
self . show_warning_message ( )
2023-08-25 15:08:08 -04:00
def save_window_size ( self , event ) :
2023-08-23 16:27:24 -04:00
with open ( " boiiiwd_dont_touch.conf " , " w " ) as conf :
conf . write ( self . geometry ( ) )
2023-08-16 05:05:59 -04:00
def on_closing ( self ) :
save_config ( " DestinationFolder " , self . edit_destination_folder . get ( ) )
save_config ( " SteamCMDPath " , self . edit_steamcmd_path . get ( ) )
self . stop_download ( on_close = True )
os . _exit ( 0 )
2023-08-12 12:32:54 -04:00
def id_chnaged_handler ( self , some = None , other = None , shit = None ) :
self . after ( 1 , self . label_file_size . configure ( text = f " File size: 0KB " ) )
2023-08-14 20:59:35 -04:00
def check_for_updates ( self ) :
check_for_updates_func ( self , ignore_up_todate = False )
2023-08-12 12:32:54 -04:00
def change_appearance_mode_event ( self , new_appearance_mode : str ) :
2023-08-23 16:27:24 -04:00
ctk . set_appearance_mode ( new_appearance_mode )
save_config ( " appearance " , new_appearance_mode )
2023-08-12 12:32:54 -04:00
def change_scaling_event ( self , new_scaling : str ) :
new_scaling_float = int ( new_scaling . replace ( " % " , " " ) ) / 100
ctk . set_widget_scaling ( new_scaling_float )
save_config ( " scaling " , str ( new_scaling_float ) )
def hide_main_widgets ( self ) :
2023-08-13 13:48:28 -04:00
self . optionsframe . grid_forget ( )
self . slider_progressbar_frame . grid_forget ( )
2023-08-12 12:32:54 -04:00
def show_main_widgets ( self ) :
self . title ( " BOIII Workshop Downloader - Main " )
2023-08-23 16:27:24 -04:00
self . slider_progressbar_frame . grid ( row = 2 , column = 1 , rowspan = 1 , padx = ( 0 , 20 ) , pady = ( 20 , 20 ) , sticky = " nsew " )
self . optionsframe . grid ( row = 0 , column = 1 , rowspan = 2 , padx = ( 0 , 20 ) , pady = ( 20 , 0 ) , sticky = " nsew " )
2023-08-12 12:32:54 -04:00
def hide_settings_widgets ( self ) :
2023-08-13 13:48:28 -04:00
self . settings_tab . grid_forget ( )
2023-08-12 12:32:54 -04:00
def show_settings_widgets ( self ) :
self . title ( " BOIII Workshop Downloader - Settings " )
2023-08-23 16:27:24 -04:00
self . settings_tab . grid ( row = 0 , rowspan = 3 , column = 1 , padx = ( 0 , 20 ) , pady = ( 20 , 20 ) , sticky = " nsew " )
2023-08-12 12:32:54 -04:00
self . settings_tab . load_on_switch_screen ( )
2023-08-16 19:02:57 -04:00
def hide_library_widgets ( self ) :
2023-08-23 16:27:24 -04:00
self . library_tab . grid_remove ( )
2023-08-16 19:02:57 -04:00
def show_library_widgets ( self ) :
self . title ( " BOIII Workshop Downloader - Library " )
2023-08-18 20:05:04 -04:00
self . library_tab . load_items ( self . edit_destination_folder . get ( ) )
2023-08-23 16:27:24 -04:00
self . library_tab . grid ( row = 0 , rowspan = 3 , column = 1 , padx = ( 0 , 20 ) , pady = ( 20 , 20 ) , sticky = " nsew " )
2023-08-16 19:02:57 -04:00
2023-08-27 15:37:29 -04:00
def show_queue_widgets ( self ) :
self . title ( " BOIII Workshop Downloader - Queue " )
self . optionsframe . grid_forget ( )
self . queue_enabled = True
self . slider_progressbar_frame . grid ( row = 2 , column = 1 , rowspan = 1 , padx = ( 0 , 20 ) , pady = ( 20 , 20 ) , sticky = " nsew " )
self . qeueuframe . grid ( row = 0 , column = 1 , rowspan = 2 , padx = ( 0 , 20 ) , pady = ( 20 , 0 ) , sticky = " nsew " )
def hide_queue_widgets ( self ) :
self . queue_enabled = False
self . qeueuframe . grid_forget ( )
2023-08-12 12:32:54 -04:00
def main_button_event ( self ) :
2023-08-23 16:27:24 -04:00
self . sidebar_main . configure ( state = " active " , fg_color = ( self . active_color ) )
self . sidebar_settings . configure ( state = " normal " , fg_color = " transparent " )
self . sidebar_library . configure ( state = " normal " , fg_color = ( self . normal_color ) )
2023-08-27 15:37:29 -04:00
self . sidebar_queue . configure ( state = " normal " , fg_color = ( self . normal_color ) )
2023-08-12 12:32:54 -04:00
self . hide_settings_widgets ( )
2023-08-16 19:02:57 -04:00
self . hide_library_widgets ( )
2023-08-27 15:37:29 -04:00
self . hide_queue_widgets ( )
2023-08-12 12:32:54 -04:00
self . show_main_widgets ( )
def settings_button_event ( self ) :
2023-08-23 16:27:24 -04:00
self . sidebar_main . configure ( state = " normal " , fg_color = ( self . normal_color ) )
self . sidebar_library . configure ( state = " normal " , fg_color = ( self . normal_color ) )
2023-08-27 15:37:29 -04:00
self . sidebar_queue . configure ( state = " normal " , fg_color = ( self . normal_color ) )
2023-08-23 16:27:24 -04:00
self . sidebar_settings . configure ( state = " active " , fg_color = ( self . active_color ) )
2023-08-12 12:32:54 -04:00
self . hide_main_widgets ( )
2023-08-16 19:02:57 -04:00
self . hide_library_widgets ( )
2023-08-27 15:37:29 -04:00
self . hide_queue_widgets ( )
2023-08-12 12:32:54 -04:00
self . show_settings_widgets ( )
2023-08-16 19:02:57 -04:00
def library_button_event ( self ) :
2023-08-23 16:27:24 -04:00
self . sidebar_main . configure ( state = " normal " , fg_color = ( self . normal_color ) )
self . sidebar_settings . configure ( state = " normal " , fg_color = " transparent " )
2023-08-27 15:37:29 -04:00
self . sidebar_queue . configure ( state = " normal " , fg_color = ( self . normal_color ) )
2023-08-23 16:27:24 -04:00
self . sidebar_library . configure ( state = " active " , fg_color = ( self . active_color ) )
2023-08-16 19:02:57 -04:00
self . hide_main_widgets ( )
self . hide_settings_widgets ( )
2023-08-27 15:37:29 -04:00
self . hide_queue_widgets ( )
2023-08-16 19:02:57 -04:00
self . show_library_widgets ( )
2023-08-27 15:37:29 -04:00
def queue_button_event ( self ) :
self . sidebar_main . configure ( state = " normal " , fg_color = ( self . normal_color ) )
self . sidebar_settings . configure ( state = " normal " , fg_color = " transparent " )
self . sidebar_library . configure ( state = " normal " , fg_color = ( self . normal_color ) )
self . sidebar_queue . configure ( state = " active " , fg_color = ( self . active_color ) )
self . hide_settings_widgets ( )
self . hide_library_widgets ( )
self . show_queue_widgets ( )
2023-08-12 12:32:54 -04:00
def load_configs ( self ) :
if os . path . exists ( CONFIG_FILE_PATH ) :
destination_folder = check_config ( " DestinationFolder " , " " )
steamcmd_path = check_config ( " SteamCMDPath " , os . getcwd ( ) )
new_appearance_mode = check_config ( " appearance " , " Dark " )
new_scaling = check_config ( " scaling " , 1.0 )
self . edit_destination_folder . delete ( 0 , " end " )
self . edit_destination_folder . insert ( 0 , destination_folder )
self . edit_steamcmd_path . delete ( 0 , " end " )
self . edit_steamcmd_path . insert ( 0 , steamcmd_path )
ctk . set_appearance_mode ( new_appearance_mode )
ctk . set_widget_scaling ( float ( new_scaling ) )
2023-08-23 16:27:24 -04:00
self . settings_tab . appearance_mode_optionemenu . set ( new_appearance_mode )
2023-08-12 12:32:54 -04:00
scaling_float = float ( new_scaling ) * 100
scaling_int = math . trunc ( scaling_float )
2023-08-23 16:27:24 -04:00
self . settings_tab . scaling_optionemenu . set ( f " { scaling_int } % " )
2023-08-12 12:32:54 -04:00
else :
new_appearance_mode = check_config ( " appearance " , " Dark " )
new_scaling = check_config ( " scaling " , 1.0 )
ctk . set_appearance_mode ( new_appearance_mode )
ctk . set_widget_scaling ( float ( new_scaling ) )
2023-08-23 16:27:24 -04:00
self . settings_tab . appearance_mode_optionemenu . set ( new_appearance_mode )
2023-08-12 12:32:54 -04:00
scaling_float = float ( new_scaling ) * 100
scaling_int = math . trunc ( scaling_float )
2023-08-23 16:27:24 -04:00
self . settings_tab . scaling_optionemenu . set ( f " { scaling_int } % " )
2023-08-18 20:05:04 -04:00
self . edit_steamcmd_path . delete ( 0 , " end " )
self . edit_steamcmd_path . insert ( 0 , cwd ( ) )
2023-08-12 12:32:54 -04:00
create_default_config ( )
2023-08-27 15:37:29 -04:00
def help_queue_text_func ( self ) :
if any ( char . isalpha ( ) for char in self . queuetextarea . get ( " 1.0 " , " end " ) ) :
self . workshop_queue_label . configure ( text = " Workshop IDs/Links => press help to see examples: " )
self . queuetextarea . configure ( state = " normal " )
self . queuetextarea . delete ( 1.0 , " end " )
self . queuetextarea . insert ( 1.0 , " " )
else :
self . workshop_queue_label . configure ( text = " Workshop IDs/Links => press help again to remove examples: " )
self . queuetextarea . insert ( 1.0 , " 3010399939,2976006537,2118338989,... \n or: \n 3010399939 \n 2976006537 \n 2113146805 \n ... " )
self . queuetextarea . configure ( state = " disabled " )
2023-08-12 12:32:54 -04:00
def open_BOIII_browser ( self ) :
selected_folder = ctk . filedialog . askdirectory ( title = " Select BOIII Folder " )
if selected_folder :
self . edit_destination_folder . delete ( 0 , " end " )
self . edit_destination_folder . insert ( 0 , selected_folder )
save_config ( " DestinationFolder " , self . edit_destination_folder . get ( ) )
save_config ( " SteamCMDPath " , self . edit_steamcmd_path . get ( ) )
def open_steamcmd_path_browser ( self ) :
selected_folder = ctk . filedialog . askdirectory ( title = " Select SteamCMD Folder " )
if selected_folder :
self . edit_steamcmd_path . delete ( 0 , " end " )
self . edit_steamcmd_path . insert ( 0 , selected_folder )
save_config ( " DestinationFolder " , self . edit_destination_folder . get ( ) )
save_config ( " SteamCMDPath " , self . edit_steamcmd_path . get ( ) )
2023-07-31 02:02:53 -04:00
2023-07-31 16:44:31 -04:00
def show_warning_message ( self ) :
2023-08-12 12:32:54 -04:00
msg = CTkMessagebox ( title = " Warning " , message = " steamcmd.exe was not found in the specified directory. \n Press Download to get it or Press Cancel and select it from there!. " ,
icon = " warning " , option_1 = " Cancel " , option_2 = " Download " )
2023-07-31 16:44:31 -04:00
2023-08-12 12:32:54 -04:00
response = msg . get ( )
if response == " Cancel " :
return
elif response == " Download " :
self . download_steamcmd ( )
2023-07-31 16:44:31 -04:00
2023-08-12 12:32:54 -04:00
def open_browser ( self ) :
link = " https://steamcommunity.com/app/311210/workshop/ "
webbrowser . open ( link )
2023-07-31 16:44:31 -04:00
def download_steamcmd ( self ) :
2023-08-12 12:32:54 -04:00
self . edit_steamcmd_path . delete ( 0 , " end " )
self . edit_steamcmd_path . insert ( 0 , cwd ( ) )
save_config ( " DestinationFolder " , self . edit_destination_folder . get ( ) )
save_config ( " SteamCMDPath " , self . edit_steamcmd_path . get ( ) )
2023-07-31 16:44:31 -04:00
steamcmd_url = " https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip "
steamcmd_zip_path = os . path . join ( cwd ( ) , " steamcmd.zip " )
try :
response = requests . get ( steamcmd_url )
response . raise_for_status ( )
with open ( steamcmd_zip_path , " wb " ) as zip_file :
zip_file . write ( response . content )
with zipfile . ZipFile ( steamcmd_zip_path , " r " ) as zip_ref :
zip_ref . extractall ( cwd ( ) )
if check_steamcmd ( ) :
2023-08-03 04:13:02 -04:00
os . remove ( fr " { steamcmd_zip_path } " )
2023-08-12 12:32:54 -04:00
if not show_message ( " Success " , " SteamCMD has been downloaded ,Press ok to initialize it. " , icon = " info " , exit_on_close = True ) :
pass
else :
initialize_steam_thread = threading . Thread ( target = lambda : initialize_steam ( self ) )
initialize_steam_thread . start ( )
2023-07-31 16:44:31 -04:00
else :
2023-08-12 12:32:54 -04:00
show_message ( " Error " , " Failed to find steamcmd.exe after extraction. \n Make you sure to select the correct SteamCMD path (by default current BOIIIWD path) " , icon = " cancel " )
2023-08-03 04:13:02 -04:00
os . remove ( fr " { steamcmd_zip_path } " )
2023-07-31 16:44:31 -04:00
except requests . exceptions . RequestException as e :
2023-08-12 12:32:54 -04:00
show_message ( " Error " , f " Failed to download SteamCMD: { e } " , icon = " cancel " )
2023-08-03 04:13:02 -04:00
os . remove ( fr " { steamcmd_zip_path } " )
2023-07-31 16:44:31 -04:00
except zipfile . BadZipFile :
2023-08-12 12:32:54 -04:00
show_message ( " Error " , " Failed to extract SteamCMD. The downloaded file might be corrupted. " , icon = " cancel " )
2023-08-03 04:13:02 -04:00
os . remove ( fr " { steamcmd_zip_path } " )
2023-08-12 12:32:54 -04:00
def show_map_info ( self ) :
def show_map_thread ( ) :
workshop_id = self . edit_workshop_id . get ( ) . strip ( )
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
if not workshop_id :
2023-08-23 16:27:24 -04:00
show_message ( " Warning " , " Please enter a Workshop ID/Link first. " )
2023-08-12 12:32:54 -04:00
return
2023-08-03 04:13:02 -04:00
2023-08-12 12:32:54 -04:00
if not workshop_id . isdigit ( ) :
try :
if extract_workshop_id ( workshop_id ) . strip ( ) . isdigit ( ) :
workshop_id = extract_workshop_id ( workshop_id ) . strip ( )
else :
2023-08-23 16:27:24 -04:00
show_message ( " Warning " , " Please enter a valid Workshop ID/Link. " )
2023-08-12 12:32:54 -04:00
except :
2023-08-23 16:27:24 -04:00
show_message ( " Warning " , " Please enter a valid Workshop ID/Link. " )
2023-08-03 04:13:02 -04:00
return
2023-08-16 19:02:57 -04:00
if self . button_download . _state == " normal " :
self . after ( 1 , lambda mid = workshop_id : self . label_file_size . configure ( text = f " File size: { get_workshop_file_size ( mid , raw = True ) } " ) )
2023-07-31 16:44:31 -04:00
2023-08-12 12:32:54 -04:00
try :
url = f " https://steamcommunity.com/sharedfiles/filedetails/?id= { workshop_id } "
response = requests . get ( url )
response . raise_for_status ( )
content = response . text
soup = BeautifulSoup ( content , " html.parser " )
try :
map_mod_type = soup . find ( " div " , class_ = " rightDetailsBlock " ) . text . strip ( )
map_name = soup . find ( " div " , class_ = " workshopItemTitle " ) . text . strip ( )
2023-08-16 19:02:57 -04:00
map_size = map_size = get_workshop_file_size ( workshop_id , raw = True )
details_stats_container = soup . find ( " div " , class_ = " detailsStatsContainerRight " )
details_stat_elements = details_stats_container . find_all ( " div " , class_ = " detailsStatRight " )
date_created = details_stat_elements [ 1 ] . text . strip ( )
try :
ratings = soup . find ( ' div ' , class_ = ' numRatings ' )
ratings_text = ratings . get_text ( )
except :
ratings = " Not found "
ratings_text = " Not enough ratings "
try :
date_updated = details_stat_elements [ 2 ] . text . strip ( )
except :
date_updated = " Not updated "
stars_div = soup . find ( " div " , class_ = " fileRatingDetails " )
starts = stars_div . find ( " img " ) [ " src " ]
2023-08-12 12:32:54 -04:00
except :
2023-08-23 16:27:24 -04:00
show_message ( " Warning " , " Please enter a valid Workshop ID/Link \n Couldn ' t get information. " )
2023-08-12 12:32:54 -04:00
return
2023-07-31 02:02:53 -04:00
2023-08-12 12:32:54 -04:00
try :
preview_image_element = soup . find ( " img " , id = " previewImage " )
workshop_item_image_url = preview_image_element [ " src " ]
except :
try :
preview_image_element = soup . find ( " img " , id = " previewImageMain " )
workshop_item_image_url = preview_image_element [ " src " ]
except Exception as e :
show_message ( " Warning " , f " Failed to get preview image ,probably wrong link/id if not please open an issue on github. \n { e } " )
return
2023-08-16 19:02:57 -04:00
starts_image_response = requests . get ( starts )
stars_image = Image . open ( io . BytesIO ( starts_image_response . content ) )
stars_image_size = stars_image . size
2023-08-12 12:32:54 -04:00
image_response = requests . get ( workshop_item_image_url )
image_response . raise_for_status ( )
image = Image . open ( io . BytesIO ( image_response . content ) )
2023-08-16 19:02:57 -04:00
image_size = image . size
2023-08-12 12:32:54 -04:00
2023-08-16 19:02:57 -04:00
self . toplevel_info_window ( map_name , map_mod_type , map_size , image , image_size , date_created ,
date_updated , stars_image , stars_image_size , ratings_text , url )
2023-08-12 12:32:54 -04:00
except requests . exceptions . RequestException as e :
show_message ( " Error " , f " Failed to fetch map information. \n Error: { e } " , icon = " cancel " )
2023-08-18 20:05:04 -04:00
return
2023-08-12 12:32:54 -04:00
info_thread = threading . Thread ( target = show_map_thread )
info_thread . start ( )
2023-08-16 19:02:57 -04:00
def toplevel_info_window ( self , map_name , map_mod_type , map_size , image , image_size ,
date_created , date_updated , stars_image , stars_image_size , ratings_text , url ) :
2023-08-12 12:32:54 -04:00
top = ctk . CTkToplevel ( self )
2023-08-18 20:05:04 -04:00
top . after ( 210 , lambda : top . iconbitmap ( os . path . join ( RESOURCES_DIR , " ryuk.ico " ) ) )
2023-08-12 12:32:54 -04:00
top . title ( " Map/Mod Information " )
top . attributes ( ' -topmost ' , ' true ' )
2023-08-16 19:02:57 -04:00
def close_window ( ) :
top . destroy ( )
2023-08-12 12:32:54 -04:00
2023-08-16 19:02:57 -04:00
def view_map_mod ( ) :
webbrowser . open ( url )
# frames
stars_frame = ctk . CTkFrame ( top )
stars_frame . grid ( row = 0 , column = 0 , columnspan = 2 , padx = 20 , pady = ( 20 , 0 ) , sticky = " nsew " )
stars_frame . columnconfigure ( 0 , weight = 0 )
stars_frame . rowconfigure ( 0 , weight = 1 )
image_frame = ctk . CTkFrame ( top )
image_frame . grid ( row = 1 , column = 0 , columnspan = 2 , padx = 20 , pady = 0 , sticky = " nsew " )
info_frame = ctk . CTkFrame ( top )
info_frame . grid ( row = 2 , column = 0 , columnspan = 2 , padx = 20 , pady = 20 , sticky = " nsew " )
buttons_frame = ctk . CTkFrame ( top )
buttons_frame . grid ( row = 3 , column = 0 , columnspan = 2 , padx = 20 , pady = ( 0 , 20 ) , sticky = " nsew " )
# fillers
name_label = ctk . CTkLabel ( info_frame , text = f " Name: { map_name } " )
name_label . grid ( row = 0 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
type_label = ctk . CTkLabel ( info_frame , text = f " Type: { map_mod_type } " )
type_label . grid ( row = 1 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
size_label = ctk . CTkLabel ( info_frame , text = f " Size: { map_size } " )
size_label . grid ( row = 2 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
date_created_label = ctk . CTkLabel ( info_frame , text = f " Posted: { date_created } " )
date_created_label . grid ( row = 3 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
date_updated_label = ctk . CTkLabel ( info_frame , text = f " Updated: { date_updated } " )
date_updated_label . grid ( row = 4 , column = 0 , columnspan = 2 , sticky = " w " , padx = 20 , pady = 5 )
stars_image_label = ctk . CTkLabel ( stars_frame )
stars_width , stars_height = stars_image_size
stars_image_widget = ctk . CTkImage ( stars_image , size = ( int ( stars_width ) , int ( stars_height ) ) )
stars_image_label . configure ( image = stars_image_widget , text = " " )
stars_image_label . pack ( side = " left " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
ratings = ctk . CTkLabel ( stars_frame )
ratings . configure ( text = ratings_text )
ratings . pack ( side = " right " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
image_label = ctk . CTkLabel ( image_frame )
width , height = image_size
image_widget = ctk . CTkImage ( image , size = ( int ( width ) , int ( height ) ) )
image_label . configure ( image = image_widget , text = " " )
image_label . pack ( expand = True , fill = " both " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
# Buttons
close_button = ctk . CTkButton ( buttons_frame , text = " View " , command = view_map_mod )
close_button . pack ( side = " left " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
view_button = ctk . CTkButton ( buttons_frame , text = " Close " , command = close_window )
view_button . pack ( side = " right " , padx = ( 10 , 20 ) , pady = ( 10 , 10 ) )
2023-08-12 12:32:54 -04:00
2023-08-16 19:02:57 -04:00
top . grid_rowconfigure ( 0 , weight = 0 )
top . grid_rowconfigure ( 1 , weight = 0 )
top . grid_rowconfigure ( 2 , weight = 1 )
top . grid_columnconfigure ( 0 , weight = 1 )
top . grid_columnconfigure ( 1 , weight = 1 )
2023-08-03 04:13:02 -04:00
2023-07-31 02:02:53 -04:00
def download_map ( self ) :
2023-08-27 15:37:29 -04:00
if not self . is_pressed :
self . is_pressed = True
if self . queue_enabled :
start_down_thread = threading . Thread ( target = self . queue_download_thread )
start_down_thread . start ( )
else :
start_down_thread = threading . Thread ( target = self . download_thread )
start_down_thread . start ( )
2023-08-16 05:05:59 -04:00
else :
2023-08-25 15:08:08 -04:00
show_message ( " Warning " , " Already pressed, Please wait. " )
2023-07-31 02:02:53 -04:00
2023-08-27 15:37:29 -04:00
def queue_download_thread ( self ) :
global stopped
stopped = False
self . queue_stop_button = False
try :
save_config ( " DestinationFolder " , self . edit_destination_folder . get ( ) )
save_config ( " SteamCMDPath " , self . edit_steamcmd_path . get ( ) )
if not check_steamcmd ( ) :
self . show_warning_message ( )
return
steamcmd_path = get_steamcmd_path ( )
if not is_steamcmd_initialized ( ) :
if not show_message ( " Warning " , " SteamCMD is not initialized, Press OK to do so! \n Program may go unresponsive until SteamCMD is finished downloading. " ,
icon = " warning " , exit_on_close = True ) :
pass
else :
initialize_steam_thread = threading . Thread ( target = lambda : initialize_steam ( self ) )
initialize_steam_thread . start ( )
return
text = self . queuetextarea . get ( " 1.0 " , " end " )
items = [ ]
if " , " in text :
items = [ n . strip ( ) for n in text . split ( " , " ) ]
else :
items = [ n . strip ( ) for n in text . split ( " \n " ) if n . strip ( ) ]
if not items :
show_message ( " Warning " , " Please enter valid Workshop IDs/Links. " , icon = " warning " )
self . stop_download
return
destination_folder = self . edit_destination_folder . get ( ) . strip ( )
if not destination_folder or not os . path . exists ( destination_folder ) :
show_message ( " Error " , " Please select a valid destination folder => in the main tab!. " )
self . stop_download
return
if not steamcmd_path or not os . path . exists ( steamcmd_path ) :
show_message ( " Error " , " Please enter a valid SteamCMD path => in the main tab!. " )
self . stop_download
return
self . total_queue_size = 0
for item in items :
item . strip ( )
workshop_id = item
if not workshop_id . isdigit ( ) :
try :
if extract_workshop_id ( workshop_id ) . strip ( ) . isdigit ( ) :
workshop_id = extract_workshop_id ( workshop_id ) . strip ( )
else :
show_message ( " Warning " , " Please enter valid Workshop IDs/Links. " , icon = " warning " )
self . stop_download
return
except :
show_message ( " Warning " , " Please enter valid Workshop IDs/Links. " , icon = " warning " )
self . stop_download
return
if not valid_id ( workshop_id ) :
show_message ( " Warning " , " Please enter valid Workshop IDs/Links. " , icon = " warning " )
self . stop_download
return
ws_file_size = get_workshop_file_size ( workshop_id )
file_size = ws_file_size
self . total_queue_size + = ws_file_size
if file_size is None :
show_message ( " Error " , " Failed to retrieve file size. " , icon = " cancel " )
self . stop_download
return
self . after ( 1 , self . status_text . configure ( text = f " Status: Total size: ~ { convert_bytes_to_readable ( self . total_queue_size ) } " ) )
start_time = time . time ( )
for index , item in enumerate ( items ) :
2023-08-27 16:09:11 -04:00
current_number = index + 1
total_items = len ( items )
2023-08-27 15:37:29 -04:00
if self . queue_stop_button :
self . stop_download
break
item . strip ( )
stopped = False
workshop_id = item
if not workshop_id . isdigit ( ) :
try :
if extract_workshop_id ( workshop_id ) . strip ( ) . isdigit ( ) :
workshop_id = extract_workshop_id ( workshop_id ) . strip ( )
else :
show_message ( " Warning " , " Please enter valid Workshop IDs/Links. " , icon = " warning " )
self . stop_download
return
except :
show_message ( " Warning " , " Please enter valid Workshop IDs/Links. " , icon = " warning " )
self . stop_download
return
ws_file_size = get_workshop_file_size ( workshop_id )
file_size = ws_file_size
self . after ( 1 , lambda mid = workshop_id : self . label_file_size . configure ( text = f " File size: { get_workshop_file_size ( mid , raw = True ) } " ) )
download_folder = os . path . join ( get_steamcmd_path ( ) , " steamapps " , " workshop " , " downloads " , " 311210 " , workshop_id )
map_folder = os . path . join ( get_steamcmd_path ( ) , " steamapps " , " workshop " , " content " , " 311210 " , workshop_id )
if not os . path . exists ( download_folder ) :
os . makedirs ( download_folder )
def check_and_update_progress ( ) :
# delay untill steam boots up and starts downloading (ive a better idea ill implement it later)
time . sleep ( 8 )
global stopped , steamcmd_reset
previous_net_speed = 0
est_downloaded_bytes = 0
file_size = ws_file_size
item_name = get_item_name ( workshop_id ) if get_item_name ( workshop_id ) else " Error getting name "
while not stopped :
if steamcmd_reset :
steamcmd_reset = False
previous_net_speed = 0
est_downloaded_bytes = 0
try :
current_size = sum ( os . path . getsize ( os . path . join ( download_folder , f ) ) for f in os . listdir ( download_folder ) )
except :
try :
current_size = sum ( os . path . getsize ( os . path . join ( map_folder , f ) ) for f in os . listdir ( map_folder ) )
except :
continue
progress = int ( current_size / file_size * 100 )
if progress > 100 :
progress = int ( current_size / current_size * 100 )
self . total_queue_size - = file_size
file_size = current_size
self . total_queue_size + = file_size
2023-08-27 16:09:11 -04:00
self . after ( 1 , self . status_text . configure (
text = f " Status: Total size: ~ { convert_bytes_to_readable ( self . total_queue_size ) } | Current Item: { workshop_id } | Name: { item_name } | Downloading { current_number } / { total_items } " ) )
2023-08-27 15:37:29 -04:00
self . after ( 1 , lambda p = progress : self . label_file_size . configure ( text = f " Wrong size reported \n File size: ~ { convert_bytes_to_readable ( current_size ) } " ) )
if estimated_progress :
time_elapsed = time . time ( ) - start_time
raw_net_speed = psutil . net_io_counters ( ) . bytes_recv
current_net_speed_text = raw_net_speed
net_speed_bytes = current_net_speed_text - previous_net_speed
previous_net_speed = current_net_speed_text
current_net_speed = net_speed_bytes
down_cap = 150000000
if current_net_speed > = down_cap :
current_net_speed = 10
est_downloaded_bytes + = current_net_speed
percentage_complete = ( est_downloaded_bytes / file_size ) * 100
progress = min ( percentage_complete / 100 , 0.99 )
net_speed , speed_unit = convert_speed ( net_speed_bytes )
elapsed_hours , elapsed_minutes , elapsed_seconds = convert_seconds ( time_elapsed )
# print(f"raw_net {raw_net_speed}\ncurrent_net_speed: {current_net_speed}\nest_downloaded_bytes {est_downloaded_bytes}\npercentage_complete {percentage_complete}\nprogress {progress}")
2023-08-27 16:09:11 -04:00
self . after ( 1 , self . status_text . configure (
text = f " Status: Total size: ~ { convert_bytes_to_readable ( self . total_queue_size ) } | Current Item: { workshop_id } | Name: { item_name } | Downloading { current_number } / { total_items } " ) )
2023-08-27 15:37:29 -04:00
self . after ( 1 , self . progress_bar . set ( progress ) )
self . after ( 1 , lambda v = net_speed : self . label_speed . configure ( text = f " Network Speed: { v : .2f } { speed_unit } " ) )
self . after ( 1 , lambda p = min ( percentage_complete , 99 ) : self . progress_text . configure ( text = f " { p : .2f } % " ) )
if show_fails :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } - Fails: { steam_fail_counter } " ) )
else :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } " ) )
time . sleep ( 1 )
else :
time_elapsed = time . time ( ) - start_time
progress = int ( current_size / file_size * 100 )
self . after ( 1 , lambda v = progress / 100.0 : self . progress_bar . set ( v ) )
current_net_speed = psutil . net_io_counters ( ) . bytes_recv
net_speed_bytes = current_net_speed - previous_net_speed
previous_net_speed = current_net_speed
net_speed , speed_unit = convert_speed ( net_speed_bytes )
elapsed_hours , elapsed_minutes , elapsed_seconds = convert_seconds ( time_elapsed )
2023-08-27 16:09:11 -04:00
self . after ( 1 , self . status_text . configure (
text = f " Status: Total size: ~ { convert_bytes_to_readable ( self . total_queue_size ) } | Current Item: { workshop_id } | Name: { item_name } | Downloading { current_number } / { total_items } " ) )
2023-08-27 15:37:29 -04:00
self . after ( 1 , lambda v = net_speed : self . label_speed . configure ( text = f " Network Speed: { v : .2f } { speed_unit } " ) )
self . after ( 1 , lambda p = progress : self . progress_text . configure ( text = f " { p } % " ) )
if show_fails :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } - Fails: { steam_fail_counter } " ) )
else :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } " ) )
time . sleep ( 1 )
command = f " +login anonymous +@sSteamCmdForcePlatformBitness 64 +app_info_update 1 +app_info_print 311210 app_update 311210 +workshop_download_item 311210 { workshop_id } validate +quit "
2023-08-27 16:09:11 -04:00
steamcmd_thread = threading . Thread ( target = lambda : run_steamcmd_command ( command , self , map_folder , queue = True ) )
2023-08-27 15:37:29 -04:00
steamcmd_thread . start ( )
def wait_for_threads ( ) :
update_ui_thread = threading . Thread ( target = check_and_update_progress )
update_ui_thread . daemon = True
update_ui_thread . start ( )
update_ui_thread . join ( )
self . label_speed . configure ( text = " Network Speed: 0 KB/s " )
self . progress_text . configure ( text = " 0 % " )
self . progress_bar . set ( 0.0 )
map_folder = os . path . join ( get_steamcmd_path ( ) , " steamapps " , " workshop " , " content " , " 311210 " , workshop_id )
json_file_path = os . path . join ( map_folder , " workshop.json " )
if os . path . exists ( json_file_path ) :
mod_type = extract_json_data ( json_file_path , " Type " )
folder_name = extract_json_data ( json_file_path , " FolderName " )
if mod_type == " mod " :
mods_folder = os . path . join ( destination_folder , " mods " )
folder_name_path = os . path . join ( mods_folder , folder_name , " zone " )
elif mod_type == " map " :
usermaps_folder = os . path . join ( destination_folder , " usermaps " )
folder_name_path = os . path . join ( usermaps_folder , folder_name , " zone " )
else :
show_message ( " Error " , " Invalid workshop type in workshop.json, are you sure this is a map or a mod?. " , icon = " cancel " )
self . stop_download
return
os . makedirs ( folder_name_path , exist_ok = True )
try :
shutil . copytree ( map_folder , folder_name_path , dirs_exist_ok = True )
except Exception as E :
show_message ( " Error " , f " Error copying files: { E } " , icon = " cancel " )
if clean_on_finish :
remove_tree ( map_folder )
remove_tree ( download_folder )
if index == len ( items ) - 1 :
msg = CTkMessagebox ( title = " Downloads Complete " , message = f " All files were downloaded \n You can run the game now! " , icon = " info " , option_1 = " Launch " , option_2 = " Ok " )
response = msg . get ( )
if response == " Launch " :
launch_boiii_func ( self . edit_destination_folder . get ( ) . strip ( ) )
if response == " Ok " :
pass
self . button_download . configure ( state = " disabled " )
self . button_stop . configure ( state = " normal " )
update_wait_thread = threading . Thread ( target = wait_for_threads )
update_wait_thread . start ( )
steamcmd_thread . join ( )
update_wait_thread . join ( )
if index == len ( items ) - 1 :
self . button_download . configure ( state = " normal " )
self . button_stop . configure ( state = " disabled " )
self . after ( 1 , self . status_text . configure ( text = f " Status: Done " ) )
self . after ( 1 , self . label_file_size . configure ( text = f " File size: 0KB " ) )
stopped = True
self . stop_download
return
finally :
global steam_fail_counter
steam_fail_counter = 0
self . after ( 1 , self . label_file_size . configure ( text = f " File size: 0KB " ) )
self . stop_download
self . is_pressed = False
2023-08-16 05:05:59 -04:00
def download_thread ( self ) :
try :
global stopped
stopped = False
2023-07-31 16:44:31 -04:00
2023-08-16 05:05:59 -04:00
save_config ( " DestinationFolder " , self . edit_destination_folder . get ( ) )
save_config ( " SteamCMDPath " , self . edit_steamcmd_path . get ( ) )
2023-07-31 21:48:40 -04:00
2023-08-16 05:05:59 -04:00
if not check_steamcmd ( ) :
self . show_warning_message ( )
return
2023-08-12 12:32:54 -04:00
2023-08-16 05:05:59 -04:00
steamcmd_path = get_steamcmd_path ( )
2023-08-23 16:27:24 -04:00
if not is_steamcmd_initialized ( ) :
2023-08-16 05:05:59 -04:00
if not show_message ( " Warning " , " SteamCMD is not initialized, Press OK to do so! \n Program may go unresponsive until SteamCMD is finished downloading. " ,
icon = " warning " , exit_on_close = True ) :
pass
2023-08-03 04:13:02 -04:00
else :
2023-08-16 05:05:59 -04:00
initialize_steam_thread = threading . Thread ( target = lambda : initialize_steam ( self ) )
initialize_steam_thread . start ( )
return
workshop_id = self . edit_workshop_id . get ( ) . strip ( )
2023-08-27 15:37:29 -04:00
2023-08-16 05:05:59 -04:00
destination_folder = self . edit_destination_folder . get ( ) . strip ( )
2023-08-18 20:05:04 -04:00
if not destination_folder or not os . path . exists ( destination_folder ) :
show_message ( " Error " , " Please select a valid destination folder. " )
self . stop_download
return
if not steamcmd_path or not os . path . exists ( steamcmd_path ) :
show_message ( " Error " , " Please enter a valid SteamCMD path. " )
self . stop_download
return
2023-08-16 05:05:59 -04:00
if not workshop_id . isdigit ( ) :
try :
if extract_workshop_id ( workshop_id ) . strip ( ) . isdigit ( ) :
workshop_id = extract_workshop_id ( workshop_id ) . strip ( )
else :
2023-08-23 16:27:24 -04:00
show_message ( " Warning " , " Please enter a valid Workshop ID/Link. " , icon = " warning " )
2023-08-16 19:02:57 -04:00
self . stop_download
2023-08-16 05:05:59 -04:00
return
except :
2023-08-23 16:27:24 -04:00
show_message ( " Warning " , " Please enter a valid Workshop ID/Link. " , icon = " warning " )
2023-08-16 19:02:57 -04:00
self . stop_download
2023-08-03 04:13:02 -04:00
return
2023-08-16 05:05:59 -04:00
2023-08-19 17:10:05 -04:00
ws_file_size = get_workshop_file_size ( workshop_id )
2023-08-16 05:05:59 -04:00
file_size = ws_file_size
if not valid_id ( workshop_id ) :
2023-08-23 16:27:24 -04:00
show_message ( " Warning " , " Please enter a valid Workshop ID/Link. " , icon = " warning " )
2023-08-16 19:02:57 -04:00
self . stop_download
2023-08-03 04:13:02 -04:00
return
2023-08-16 05:05:59 -04:00
if file_size is None :
show_message ( " Error " , " Failed to retrieve file size. " , icon = " cancel " )
2023-08-16 19:02:57 -04:00
self . stop_download
2023-08-16 05:05:59 -04:00
return
2023-08-12 12:32:54 -04:00
2023-08-16 05:05:59 -04:00
self . after ( 1 , lambda mid = workshop_id : self . label_file_size . configure ( text = f " File size: { get_workshop_file_size ( mid , raw = True ) } " ) )
download_folder = os . path . join ( get_steamcmd_path ( ) , " steamapps " , " workshop " , " downloads " , " 311210 " , workshop_id )
map_folder = os . path . join ( get_steamcmd_path ( ) , " steamapps " , " workshop " , " content " , " 311210 " , workshop_id )
if not os . path . exists ( download_folder ) :
os . makedirs ( download_folder )
def check_and_update_progress ( ) :
# delay untill steam boots up and starts downloading (ive a better idea ill implement it later)
time . sleep ( 8 )
2023-08-25 15:08:08 -04:00
global stopped , steamcmd_reset
2023-08-16 05:05:59 -04:00
previous_net_speed = 0
est_downloaded_bytes = 0
start_time = time . time ( )
file_size = ws_file_size
while not stopped :
2023-08-25 15:08:08 -04:00
if steamcmd_reset :
steamcmd_reset = False
previous_net_speed = 0
est_downloaded_bytes = 0
2023-08-16 05:05:59 -04:00
try :
current_size = sum ( os . path . getsize ( os . path . join ( download_folder , f ) ) for f in os . listdir ( download_folder ) )
except :
2023-08-25 15:08:08 -04:00
try :
current_size = sum ( os . path . getsize ( os . path . join ( map_folder , f ) ) for f in os . listdir ( map_folder ) )
except :
continue
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
progress = int ( current_size / file_size * 100 )
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
if progress > 100 :
progress = int ( current_size / current_size * 100 )
file_size = current_size
self . after ( 1 , lambda p = progress : self . label_file_size . configure ( text = f " Wrong size reported \n Actual size: ~ { convert_bytes_to_readable ( current_size ) } " ) )
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
if estimated_progress :
time_elapsed = time . time ( ) - start_time
raw_net_speed = psutil . net_io_counters ( ) . bytes_recv
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
current_net_speed_text = raw_net_speed
net_speed_bytes = current_net_speed_text - previous_net_speed
previous_net_speed = current_net_speed_text
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
current_net_speed = net_speed_bytes
down_cap = 150000000
if current_net_speed > = down_cap :
2023-08-16 19:02:57 -04:00
current_net_speed = 10
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
est_downloaded_bytes + = current_net_speed
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
percentage_complete = ( est_downloaded_bytes / file_size ) * 100
2023-07-31 16:44:31 -04:00
2023-08-16 05:05:59 -04:00
progress = min ( percentage_complete / 100 , 0.99 )
2023-07-31 16:44:31 -04:00
2023-08-16 05:05:59 -04:00
net_speed , speed_unit = convert_speed ( net_speed_bytes )
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
elapsed_hours , elapsed_minutes , elapsed_seconds = convert_seconds ( time_elapsed )
2023-07-31 02:02:53 -04:00
2023-08-18 20:05:04 -04:00
# print(f"raw_net {raw_net_speed}\ncurrent_net_speed: {current_net_speed}\nest_downloaded_bytes {est_downloaded_bytes}\npercentage_complete {percentage_complete}\nprogress {progress}")
2023-07-31 09:29:45 -04:00
2023-08-16 05:05:59 -04:00
self . after ( 1 , self . progress_bar . set ( progress ) )
self . after ( 1 , lambda v = net_speed : self . label_speed . configure ( text = f " Network Speed: { v : .2f } { speed_unit } " ) )
2023-08-18 20:05:04 -04:00
self . after ( 1 , lambda p = min ( percentage_complete , 99 ) : self . progress_text . configure ( text = f " { p : .2f } % " ) )
2023-08-27 15:37:29 -04:00
if show_fails :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } - Fails: { steam_fail_counter } " ) )
else :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } " ) )
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
time . sleep ( 1 )
else :
time_elapsed = time . time ( ) - start_time
progress = int ( current_size / file_size * 100 )
self . after ( 1 , lambda v = progress / 100.0 : self . progress_bar . set ( v ) )
2023-07-31 02:02:53 -04:00
2023-08-16 05:05:59 -04:00
current_net_speed = psutil . net_io_counters ( ) . bytes_recv
2023-08-01 11:07:44 -04:00
2023-08-16 05:05:59 -04:00
net_speed_bytes = current_net_speed - previous_net_speed
previous_net_speed = current_net_speed
2023-07-31 21:48:40 -04:00
2023-08-16 05:05:59 -04:00
net_speed , speed_unit = convert_speed ( net_speed_bytes )
elapsed_hours , elapsed_minutes , elapsed_seconds = convert_seconds ( time_elapsed )
2023-07-31 16:44:31 -04:00
2023-08-16 05:05:59 -04:00
self . after ( 1 , lambda v = net_speed : self . label_speed . configure ( text = f " Network Speed: { v : .2f } { speed_unit } " ) )
self . after ( 1 , lambda p = progress : self . progress_text . configure ( text = f " { p } % " ) )
2023-08-27 15:37:29 -04:00
if show_fails :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } - Fails: { steam_fail_counter } " ) )
else :
self . after ( 1 , lambda h = elapsed_hours , m = elapsed_minutes , s = elapsed_seconds : self . elapsed_time . configure ( text = f " Elapsed Time: { int ( h ) : 02d } : { int ( m ) : 02d } : { int ( s ) : 02d } " ) )
2023-08-16 05:05:59 -04:00
time . sleep ( 1 )
2023-07-31 21:48:40 -04:00
2023-08-16 05:05:59 -04:00
command = f " +login anonymous +@sSteamCmdForcePlatformBitness 64 +app_info_update 1 +app_info_print 311210 app_update 311210 +workshop_download_item 311210 { workshop_id } validate +quit "
steamcmd_thread = threading . Thread ( target = lambda : run_steamcmd_command ( command , self , map_folder ) )
steamcmd_thread . start ( )
2023-07-31 16:44:31 -04:00
2023-08-16 05:05:59 -04:00
def wait_for_threads ( ) :
update_ui_thread = threading . Thread ( target = check_and_update_progress )
update_ui_thread . daemon = True
update_ui_thread . start ( )
update_ui_thread . join ( )
global stopped
stopped = True
self . label_speed . configure ( text = " Network Speed: 0 KB/s " )
self . progress_text . configure ( text = " 0 % " )
self . progress_bar . set ( 0.0 )
map_folder = os . path . join ( get_steamcmd_path ( ) , " steamapps " , " workshop " , " content " , " 311210 " , workshop_id )
json_file_path = os . path . join ( map_folder , " workshop.json " )
2023-08-12 13:03:45 -04:00
2023-08-16 05:05:59 -04:00
if os . path . exists ( json_file_path ) :
2023-08-18 20:05:04 -04:00
mod_type = extract_json_data ( json_file_path , " Type " )
folder_name = extract_json_data ( json_file_path , " FolderName " )
2023-08-03 04:13:02 -04:00
2023-08-16 05:05:59 -04:00
if mod_type == " mod " :
mods_folder = os . path . join ( destination_folder , " mods " )
folder_name_path = os . path . join ( mods_folder , folder_name , " zone " )
elif mod_type == " map " :
usermaps_folder = os . path . join ( destination_folder , " usermaps " )
folder_name_path = os . path . join ( usermaps_folder , folder_name , " zone " )
else :
2023-08-18 20:05:04 -04:00
show_message ( " Error " , " Invalid workshop type in workshop.json, are you sure this is a map or a mod?. " , icon = " cancel " )
2023-08-16 19:02:57 -04:00
self . stop_download
2023-08-16 05:05:59 -04:00
return
os . makedirs ( folder_name_path , exist_ok = True )
try :
shutil . copytree ( map_folder , folder_name_path , dirs_exist_ok = True )
except Exception as E :
show_message ( " Error " , f " Error copying files: { E } " , icon = " cancel " )
if clean_on_finish :
remove_tree ( map_folder )
remove_tree ( download_folder )
msg = CTkMessagebox ( title = " Download Complete " , message = f " { mod_type . capitalize ( ) } files were downloaded \n You can run the game now! " , icon = " info " , option_1 = " Launch " , option_2 = " Ok " )
response = msg . get ( )
if response == " Launch " :
launch_boiii_func ( self . edit_destination_folder . get ( ) . strip ( ) )
if response == " Ok " :
pass
2023-08-03 04:13:02 -04:00
2023-08-16 05:05:59 -04:00
self . button_download . configure ( state = " normal " )
self . button_stop . configure ( state = " disabled " )
2023-08-03 04:13:02 -04:00
2023-08-16 05:05:59 -04:00
update_wait_thread = threading . Thread ( target = wait_for_threads )
update_wait_thread . start ( )
self . button_download . configure ( state = " disabled " )
self . button_stop . configure ( state = " normal " )
finally :
2023-08-27 15:37:29 -04:00
global steam_fail_counter
steam_fail_counter = 0
2023-08-16 05:05:59 -04:00
self . stop_download
2023-08-27 15:37:29 -04:00
self . is_pressed = False
2023-08-16 05:05:59 -04:00
def stop_download ( self , on_close = None ) :
2023-08-27 15:37:29 -04:00
global stopped , steam_fail_counter
2023-08-12 12:32:54 -04:00
stopped = True
2023-08-27 15:37:29 -04:00
self . queue_stop_button = True
steam_fail_counter = 0
self . is_pressed = False
self . after ( 1 , self . label_file_size . configure ( text = f " File size: 0KB " ) )
2023-08-03 04:13:02 -04:00
2023-08-16 05:05:59 -04:00
if on_close :
subprocess . run ( [ ' taskkill ' , ' /F ' , ' /IM ' , ' steamcmd.exe ' ] , stdout = subprocess . PIPE , stderr = subprocess . PIPE ,
creationflags = subprocess . CREATE_NO_WINDOW )
return
2023-08-12 12:32:54 -04:00
subprocess . run ( [ ' taskkill ' , ' /F ' , ' /IM ' , ' steamcmd.exe ' ] , stdout = subprocess . PIPE , stderr = subprocess . PIPE ,
creationflags = subprocess . CREATE_NO_WINDOW )
2023-08-03 04:13:02 -04:00
2023-08-27 15:37:29 -04:00
self . status_text . configure ( text = f " Status: Not Downloading " )
2023-08-12 12:32:54 -04:00
self . button_download . configure ( state = " normal " )
self . button_stop . configure ( state = " disabled " )
self . label_speed . configure ( text = " Network Speed: 0 KB/s " )
self . progress_text . configure ( text = " 0 % " )
2023-08-16 05:05:59 -04:00
self . elapsed_time . configure ( text = f " " )
2023-08-12 12:32:54 -04:00
self . progress_bar . set ( 0.0 )
2023-08-03 04:13:02 -04:00
2023-07-31 02:02:53 -04:00
if __name__ == " __main__ " :
2023-08-12 12:32:54 -04:00
app = BOIIIWD ( )
app . mainloop ( )