9 Commits

Author SHA1 Message Date
44fe6245b9 Add update system
All checks were successful
Build and Upload Release (Windows EXE) / Build Windows EXE (release) Successful in 37s
2026-01-24 01:13:19 -05:00
ea6eb1fd5f update workflow to for this project
Some checks failed
Build and Upload Release (Windows EXE) / Build Windows EXE (release) Failing after 2m35s
2026-01-24 00:50:42 -05:00
24271abcd8 Remove ICO drop shadow 2026-01-24 00:37:59 -05:00
f6addb9710 Merge branch 'main' of https://git.hudsonriggs.systems/HRiggs/ProgressionMods 2026-01-24 00:35:12 -05:00
3a5f071786 Finished 2026-01-24 00:35:11 -05:00
303c7d8d6e Remove .env
No info was exposed, as its just the example however its bad practice.
2026-01-24 04:30:47 +00:00
634543eea6 pre animation 2026-01-23 22:13:07 -05:00
e4c0a0c05a art whoops! and also font whoops! and also failed at fonts and removing demo files. 2026-01-23 22:00:11 -05:00
7f293558b4 Add collection scraper and local mod detection 2026-01-23 21:13:37 -05:00
20 changed files with 3602 additions and 1 deletions

7
.env.example Normal file
View File

@@ -0,0 +1,7 @@
# Default Collection URLs
CORE_COLLECTION_URL=https://steamcommunity.com/workshop/filedetails/?id=3521297585
CONTENT_COLLECTION_URL=steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3521319712
COSMETICS_COLLECTION_URL=steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3637541646
# ModsConfig.xml Path Template
MODSCONFIG_PATH_TEMPLATE=%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Config\ModsConfig.xml

View File

@@ -0,0 +1,89 @@
name: Build and Upload Release (Windows EXE)
on:
release:
types: [published]
workflow_dispatch: {}
jobs:
build-windows-exe:
name: Build Windows EXE
runs-on: windows
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Create .env file from secrets
shell: powershell
run: |
$envContent = @"
# Default Collection URLs
CORE_COLLECTION_URL=${{ secrets.CORE_COLLECTION_URL || 'https://steamcommunity.com/workshop/filedetails/?id=3521297585' }}
CONTENT_COLLECTION_URL=${{ secrets.CONTENT_COLLECTION_URL || 'steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3521319712' }}
COSMETICS_COLLECTION_URL=${{ secrets.COSMETICS_COLLECTION_URL || 'steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3637541646' }}
# ModsConfig.xml Path Template
MODSCONFIG_PATH_TEMPLATE=${{ secrets.MODSCONFIG_PATH_TEMPLATE || '%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Config\ModsConfig.xml' }}
"@
$envContent | Out-File -FilePath .env -Encoding UTF8 -Force
Write-Host "Created .env file with configuration"
- name: Install dependencies
shell: powershell
run: |
python -m pip install --upgrade pip
if (Test-Path requirements.txt) { pip install -r requirements.txt }
pip install pyinstaller
- name: Build EXE with PyInstaller
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
# Build standalone executable with all assets bundled
pyinstaller --clean --onefile --windowed --icon=art/Progression.ico --add-data "art;art" --add-data ".env;." --name ProgressionLoader steam_workshop_gui.py
- name: Prepare artifact
shell: powershell
run: |
# Just upload the standalone .exe file
Copy-Item dist\ProgressionLoader.exe ProgressionLoader.exe
- name: Upload asset to Release
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
shell: powershell
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
RELEASE_ID: ${{ github.event.release.id }}
SERVER_URL: ${{ github.server_url }}
run: |
$ErrorActionPreference = 'Stop'
# Gitea API endpoint for uploading release assets
$uploadUrl = "$env:SERVER_URL/api/v1/repos/$env:REPO/releases/$env:RELEASE_ID/assets"
Write-Host "Uploading ProgressionLoader.exe to $uploadUrl"
# Use multipart form data for Gitea
$boundary = [System.Guid]::NewGuid().ToString()
$LF = "`r`n"
$fileBytes = [System.IO.File]::ReadAllBytes("ProgressionLoader.exe")
$fileEnc = [System.Text.Encoding]::GetEncoding('iso-8859-1').GetString($fileBytes)
$bodyLines = (
"--$boundary",
"Content-Disposition: form-data; name=`"attachment`"; filename=`"ProgressionLoader.exe`"",
"Content-Type: application/octet-stream$LF",
$fileEnc,
"--$boundary--$LF"
) -join $LF
Invoke-RestMethod -Method Post -Uri $uploadUrl -Headers @{
Authorization = "token $env:TOKEN"
"Content-Type" = "multipart/form-data; boundary=$boundary"
} -Body $bodyLines

203
.gitignore vendored Normal file
View File

@@ -0,0 +1,203 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
# project, it is recommended to ignore the entire .idea directory.
.idea/
# VS Code
.vscode/
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Windows
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
*.tmp
*.temp
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msix
*.msm
*.msp
*.lnk
# Linux
*~
# Temporary files
*.swp
*.swo
*~
# Logs
*.log
# Application specific
# Keep art assets but ignore any generated thumbnails or cache
art/cache/
art/thumbnails/
# Steam Workshop related (if any cache files are generated)
workshop_cache/
*.cache

View File

@@ -1,2 +1,14 @@
# ProgressionMods # Progression: Loader
This project pulls from the three steam workshop collections of Progression, then it searches through your installed mods, in the workshop folder and matches the steam id to a package id, and Formal name, then it takes those names, package ids and steam ids and creates the modloader modslist, thats a mouth full, and save it to your modslist folder so all you have to do is launch the game. The merge button will take your currently ENABLED mods and merge them with the latest progression pack to give you a new modslist called progresisonhomebrew which you can sort and load.
DLC detection is based on NotOwned_x.png if the user doesnt the same package NAME as the not owned png it doesnt proceed.
.envs populate pre loaded data, only change if their is an update to collection url
Doesnt sort so the current guidence to autosort from base game mod manager will still apply.
Art by ferny
to bump the version
python version_manager.py 1.0.1

BIN
art/GameTitle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
art/NotOwned_Anomaly.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
art/NotOwned_Biotech.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
art/NotOwned_Ideology.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
art/NotOwned_Royalty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
art/Progression.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
art/ProgressionICO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
art/ProgressionICO.psd Normal file

Binary file not shown.

BIN
art/ProgressionLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
art/RimWordFont4.ttf Normal file

Binary file not shown.

BIN
art/hudsonriggssystems.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
requests>=2.25.1
python-dotenv>=0.19.0
pyglet>=1.5.0
Pillow>=8.0.0
packaging>=21.0

2647
steam_workshop_gui.py Normal file

File diff suppressed because it is too large Load Diff

487
update_checker.py Normal file
View File

@@ -0,0 +1,487 @@
"""
Update Checker Module for Progression Loader
Checks for new releases from https://git.hudsonriggs.systems/HRiggs/ProgressionMods/releases
"""
import requests
import json
import re
from packaging import version
import tkinter as tk
from tkinter import messagebox
import webbrowser
import threading
import time
from datetime import datetime, timedelta
import os
from pathlib import Path
from update_config import get_update_config
class UpdateChecker:
def __init__(self, current_version=None):
# Load configuration
self.config = get_update_config()
# Set version
self.current_version = current_version or self.config["current_version"]
# API configuration
self.api_base_url = self.config["api_base_url"]
self.repo_owner = self.config["repo_owner"]
self.repo_name = self.config["repo_name"]
self.releases_url = f"{self.api_base_url}/repos/{self.repo_owner}/{self.repo_name}/releases"
# Check configuration
self.last_check_file = Path(self.config["last_check_file"]) if self.config["last_check_file"] else None
self.check_interval_hours = self.config["check_interval_hours"]
self.include_prereleases = self.config["include_prereleases"]
# Request configuration
self.user_agent = self.config["user_agent"]
self.request_timeout = self.config["request_timeout"]
def get_current_version(self):
"""Get the current version of the application"""
return self.current_version
def set_current_version(self, version_string):
"""Set the current version of the application"""
self.current_version = version_string
def should_check_for_updates(self):
"""Check if enough time has passed since last update check"""
# If no persistent file or check interval is 0, always check
if not self.last_check_file or self.check_interval_hours == 0:
return True
if not self.last_check_file.exists():
return True
try:
with open(self.last_check_file, 'r') as f:
data = json.load(f)
last_check = datetime.fromisoformat(data.get('last_check', '2000-01-01'))
return datetime.now() - last_check > timedelta(hours=self.check_interval_hours)
except (json.JSONDecodeError, ValueError, KeyError):
return True
def save_last_check_time(self):
"""Save the current time as the last check time"""
# Skip saving if no persistent file is configured
if not self.last_check_file:
return
try:
data = {'last_check': datetime.now().isoformat()}
with open(self.last_check_file, 'w') as f:
json.dump(data, f)
except Exception as e:
print(f"Could not save last check time: {e}")
def fetch_latest_release(self):
"""Fetch the latest release information from the API"""
try:
headers = {
'Accept': 'application/json',
'User-Agent': self.user_agent
}
response = requests.get(self.releases_url, headers=headers, timeout=self.request_timeout)
response.raise_for_status()
releases = response.json()
if not releases:
return None
# Filter releases based on prerelease setting
if not self.include_prereleases:
releases = [r for r in releases if not r.get('prerelease', False)]
# Filter out draft releases
releases = [r for r in releases if not r.get('draft', False)]
if not releases:
return None
# Get the latest release (first in the filtered list)
latest_release = releases[0]
return {
'version': latest_release.get('tag_name', '').lstrip('v'),
'name': latest_release.get('name', ''),
'body': latest_release.get('body', ''),
'html_url': latest_release.get('html_url', ''),
'published_at': latest_release.get('published_at', ''),
'assets': latest_release.get('assets', []),
'prerelease': latest_release.get('prerelease', False),
'draft': latest_release.get('draft', False)
}
except requests.exceptions.RequestException as e:
print(f"Network error checking for updates: {e}")
return None
except json.JSONDecodeError as e:
print(f"Error parsing release data: {e}")
return None
except Exception as e:
print(f"Unexpected error checking for updates: {e}")
return None
def is_newer_version(self, latest_version):
"""Compare versions to see if the latest is newer than current"""
try:
current = version.parse(self.current_version)
latest = version.parse(latest_version)
return latest > current
except Exception as e:
print(f"Error comparing versions: {e}")
# Fallback to string comparison
return latest_version != self.current_version
def check_for_updates_async(self, callback=None):
"""Check for updates in a background thread"""
def check_thread():
try:
if not self.should_check_for_updates():
if callback:
callback(None, "No check needed")
return
latest_release = self.fetch_latest_release()
self.save_last_check_time()
if callback:
callback(latest_release, None)
except Exception as e:
if callback:
callback(None, str(e))
thread = threading.Thread(target=check_thread, daemon=True)
thread.start()
def check_for_updates_sync(self):
"""Check for updates synchronously"""
if not self.should_check_for_updates():
return None, "No check needed"
latest_release = self.fetch_latest_release()
self.save_last_check_time()
return latest_release, None
def show_update_dialog(self, parent_window, release_info):
"""Show an update notification dialog"""
if not release_info:
return
latest_version = release_info['version']
if not self.is_newer_version(latest_version):
return # No update needed
# Create update dialog
dialog = tk.Toplevel(parent_window)
dialog.title("Update Available - Progression Loader")
dialog.configure(bg='#2b2b2b')
dialog.resizable(False, False)
dialog.attributes('-topmost', True)
# Set window icon if available
try:
if hasattr(parent_window, 'iconbitmap'):
dialog.iconbitmap(parent_window.iconbitmap())
except:
pass
# Calculate dialog size
dialog_width = 500
dialog_height = 400
# Center the dialog
x = parent_window.winfo_x() + (parent_window.winfo_width() // 2) - (dialog_width // 2)
y = parent_window.winfo_y() + (parent_window.winfo_height() // 2) - (dialog_height // 2)
dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")
# Title
title_label = tk.Label(dialog,
text="🔄 Update Available!",
fg='#00ff00', bg='#2b2b2b',
font=('Arial', 16, 'bold'))
title_label.pack(pady=20)
# Version info
version_frame = tk.Frame(dialog, bg='#2b2b2b')
version_frame.pack(pady=10)
current_label = tk.Label(version_frame,
text=f"Current Version: {self.current_version}",
fg='#cccccc', bg='#2b2b2b',
font=('Arial', 12))
current_label.pack()
latest_label = tk.Label(version_frame,
text=f"Latest Version: {latest_version}",
fg='#00ff00', bg='#2b2b2b',
font=('Arial', 12, 'bold'))
latest_label.pack()
# Release notes
if release_info.get('body'):
notes_label = tk.Label(dialog,
text="Release Notes:",
fg='#cccccc', bg='#2b2b2b',
font=('Arial', 12, 'bold'))
notes_label.pack(pady=(20, 5))
# Create scrollable text widget for release notes
notes_frame = tk.Frame(dialog, bg='#2b2b2b')
notes_frame.pack(fill='both', expand=True, padx=20, pady=(0, 20))
notes_text = tk.Text(notes_frame,
height=8,
bg='#404040', fg='#ffffff',
font=('Arial', 10),
wrap=tk.WORD,
state='disabled')
scrollbar = tk.Scrollbar(notes_frame)
scrollbar.pack(side='right', fill='y')
notes_text.pack(side='left', fill='both', expand=True)
notes_text.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=notes_text.yview)
# Insert release notes
notes_text.config(state='normal')
notes_text.insert('1.0', release_info['body'])
notes_text.config(state='disabled')
# Buttons
button_frame = tk.Frame(dialog, bg='#2b2b2b')
button_frame.pack(pady=20)
def download_update():
webbrowser.open(release_info['html_url'])
dialog.destroy()
def remind_later():
# If no persistent file, just close the dialog
if not self.last_check_file:
dialog.destroy()
return
# Reset last check time to check again sooner
try:
data = {'last_check': (datetime.now() - timedelta(hours=self.check_interval_hours - 4)).isoformat()}
with open(self.last_check_file, 'w') as f:
json.dump(data, f)
except:
pass
dialog.destroy()
def skip_version():
# If no persistent file, just close the dialog
if not self.last_check_file:
dialog.destroy()
return
# Save this version as skipped
try:
data = {
'last_check': datetime.now().isoformat(),
'skipped_version': latest_version
}
with open(self.last_check_file, 'w') as f:
json.dump(data, f)
except:
pass
dialog.destroy()
download_btn = tk.Button(button_frame,
text="Download Update",
command=download_update,
bg='#00aa00', fg='white',
font=('Arial', 12, 'bold'),
padx=20, pady=5)
download_btn.pack(side='left', padx=5)
# Only show remind/skip buttons if persistence is enabled
if self.last_check_file:
later_btn = tk.Button(button_frame,
text="Remind Later",
command=remind_later,
bg='#0078d4', fg='white',
font=('Arial', 12),
padx=20, pady=5)
later_btn.pack(side='left', padx=5)
skip_btn = tk.Button(button_frame,
text="Skip Version",
command=skip_version,
bg='#666666', fg='white',
font=('Arial', 12),
padx=20, pady=5)
skip_btn.pack(side='left', padx=5)
else:
close_btn = tk.Button(button_frame,
text="Close",
command=dialog.destroy,
bg='#666666', fg='white',
font=('Arial', 12),
padx=20, pady=5)
close_btn.pack(side='left', padx=5)
# Handle window close
dialog.protocol("WM_DELETE_WINDOW", dialog.destroy)
return dialog
def should_skip_version(self, version_string):
"""Check if this version should be skipped"""
# Skip version checking if no persistent file is configured
if not self.last_check_file:
return False
try:
with open(self.last_check_file, 'r') as f:
data = json.load(f)
return data.get('skipped_version') == version_string
except:
return False
def manual_check_for_updates(self, parent_window):
"""Manually check for updates and show result"""
def check_complete(release_info, error):
if error:
messagebox.showerror("Update Check Failed",
f"Could not check for updates:\n{error}",
parent=parent_window)
return
if not release_info:
messagebox.showinfo("No Updates",
"Could not retrieve release information.",
parent=parent_window)
return
latest_version = release_info['version']
if self.is_newer_version(latest_version) and not self.should_skip_version(latest_version):
self.show_update_dialog(parent_window, release_info)
else:
messagebox.showinfo("Up to Date",
f"You are running the latest version ({self.current_version}).",
parent=parent_window)
# Show checking message
checking_dialog = tk.Toplevel(parent_window)
checking_dialog.title("Checking for Updates")
checking_dialog.configure(bg='#2b2b2b')
checking_dialog.resizable(False, False)
checking_dialog.attributes('-topmost', True)
# Center the dialog
checking_dialog.geometry("300x100")
x = parent_window.winfo_x() + (parent_window.winfo_width() // 2) - 150
y = parent_window.winfo_y() + (parent_window.winfo_height() // 2) - 50
checking_dialog.geometry(f"300x100+{x}+{y}")
label = tk.Label(checking_dialog,
text="Checking for updates...",
fg='white', bg='#2b2b2b',
font=('Arial', 12))
label.pack(expand=True)
def check_and_close():
release_info, error = self.check_for_updates_sync()
checking_dialog.destroy()
check_complete(release_info, error)
# Start check in background
threading.Thread(target=check_and_close, daemon=True).start()
def integrate_update_checker_with_gui(gui_class):
"""
Decorator to integrate update checker with the main GUI class
"""
original_init = gui_class.__init__
def new_init(self, *args, **kwargs):
# Call original init
original_init(self, *args, **kwargs)
# Initialize update checker
self.update_checker = UpdateChecker()
# Add update check to menu if it exists
self.add_update_menu()
# Check for updates on startup (after a short delay)
self.root.after(5000, self.check_for_updates_on_startup)
def add_update_menu(self):
"""Add update check option to the application"""
try:
# Try to add to existing menu bar
if hasattr(self, 'menubar'):
help_menu = tk.Menu(self.menubar, tearoff=0)
help_menu.add_command(label="Check for Updates", command=self.manual_update_check)
self.menubar.add_cascade(label="Help", menu=help_menu)
else:
# Create a simple button in the GUI
self.add_update_button()
except Exception as e:
print(f"Could not add update menu: {e}")
def add_update_button(self):
"""Add an update check button to the GUI"""
try:
# Find a suitable parent frame (try footer first, then main frame)
parent_frame = None
if hasattr(self, 'footer_frame'):
parent_frame = self.footer_frame
elif hasattr(self, 'main_frame'):
parent_frame = self.main_frame
elif hasattr(self, 'root'):
parent_frame = self.root
if parent_frame:
update_btn = tk.Button(parent_frame,
text="Check Updates",
command=self.manual_update_check,
bg='#404040', fg='white',
font=('Arial', 10),
padx=10, pady=2)
update_btn.pack(side='right', padx=5, pady=5)
except Exception as e:
print(f"Could not add update button: {e}")
def check_for_updates_on_startup(self):
"""Check for updates when the application starts"""
def update_callback(release_info, error):
if error or not release_info:
return # Silently fail on startup
latest_version = release_info['version']
if (self.update_checker.is_newer_version(latest_version) and
not self.update_checker.should_skip_version(latest_version)):
# Show update dialog
self.update_checker.show_update_dialog(self.root, release_info)
self.update_checker.check_for_updates_async(update_callback)
def manual_update_check(self):
"""Manually triggered update check"""
self.update_checker.manual_check_for_updates(self.root)
# Add methods to the class
gui_class.add_update_menu = add_update_menu
gui_class.add_update_button = add_update_button
gui_class.check_for_updates_on_startup = check_for_updates_on_startup
gui_class.manual_update_check = manual_update_check
gui_class.__init__ = new_init
return gui_class

47
update_config.py Normal file
View File

@@ -0,0 +1,47 @@
"""
Configuration settings for the update checker
"""
# Update checker configuration
UPDATE_CONFIG = {
# Current version of the application
"current_version": "0.0.1",
# Repository information
"repo_owner": "HRiggs",
"repo_name": "ProgressionMods",
"api_base_url": "https://git.hudsonriggs.systems/api/v1",
# Update check frequency (in hours) - set to 0 to check every startup
"check_interval_hours": 0,
# Whether to check for updates on startup
"check_on_startup": True,
# Delay before checking for updates on startup (in milliseconds)
"startup_check_delay": 5000,
# Whether to include pre-releases in update checks
"include_prereleases": True,
# User agent string for API requests
"user_agent": "ProgressionLoader-UpdateChecker/1.0",
# Request timeout in seconds
"request_timeout": 10,
# File to store last check time and skipped versions (set to None to disable persistence)
"last_check_file": None
}
def get_update_config():
"""Get the update configuration"""
return UPDATE_CONFIG.copy()
def set_current_version(version):
"""Set the current version"""
UPDATE_CONFIG["current_version"] = version
def get_current_version():
"""Get the current version"""
return UPDATE_CONFIG["current_version"]

104
version_manager.py Normal file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""
Version Manager for Progression Loader
"""
import re
import sys
def get_current_version():
"""Get the current version from update_config.py"""
try:
with open('update_config.py', 'r') as f:
content = f.read()
match = re.search(r'"current_version":\s*"([^"]+)"', content)
if match:
return match.group(1)
except FileNotFoundError:
pass
return None
def set_version(new_version):
"""Set the version in update_config.py"""
try:
with open('update_config.py', 'r') as f:
content = f.read()
updated_content = re.sub(
r'"current_version":\s*"[^"]+"',
f'"current_version": "{new_version}"',
content
)
with open('update_config.py', 'w') as f:
f.write(updated_content)
print(f"✅ Updated version to {new_version}")
return True
except Exception as e:
print(f"❌ Error updating version: {e}")
return False
def validate_version(version_string):
"""Validate semantic versioning format"""
pattern = r'^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9\-\.]+))?$'
return re.match(pattern, version_string) is not None
def suggest_next_version(current_version):
"""Suggest next version numbers"""
if not current_version:
return ["1.0.0"]
try:
parts = current_version.split('.')
if len(parts) >= 3:
major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
return [
f"{major}.{minor}.{patch + 1}", # Patch
f"{major}.{minor + 1}.0", # Minor
f"{major + 1}.0.0" # Major
]
except ValueError:
pass
return ["1.0.0"]
def main():
"""Main interface"""
if len(sys.argv) < 2:
current = get_current_version()
print(f"Current Version: {current or 'Not found'}")
if current:
suggestions = suggest_next_version(current)
print("Suggested versions:", ", ".join(suggestions))
print("\nUsage:")
print(" python version_manager.py <version> # Set version")
print(" python version_manager.py current # Show current")
print("\nExample: python version_manager.py 1.0.1")
return
command = sys.argv[1]
if command == "current":
current = get_current_version()
print(f"Current version: {current or 'Not found'}")
return
# Set version
new_version = command
if not validate_version(new_version):
print(f"❌ Invalid version format: {new_version}")
print("Use format: MAJOR.MINOR.PATCH (e.g., 1.0.1)")
return
if set_version(new_version):
print("Next steps:")
print(f" git add update_config.py")
print(f" git commit -m 'Bump version to {new_version}'")
print(f" git tag v{new_version} && git push --tags")
if __name__ == "__main__":
main()