Add collection scraper and local mod detection
This commit is contained in:
29
About.xml
Normal file
29
About.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ModMetaData>
|
||||
<name>RT Fuse</name>
|
||||
<author>Ratys</author>
|
||||
<packageId>ratys.rtfuse</packageId>
|
||||
<url>https://ludeon.com/forums/index.php?topic=11272</url>
|
||||
<supportedVersions>
|
||||
<li>1.0</li>
|
||||
<li>1.1</li>
|
||||
<li>1.2</li>
|
||||
<li>1.3</li>
|
||||
<li>1.4</li>
|
||||
<li>1.5</li>
|
||||
<li>1.6</li>
|
||||
</supportedVersions>
|
||||
<modDependencies>
|
||||
<li>
|
||||
<packageId>brrainz.harmony</packageId>
|
||||
<displayName>Harmony</displayName>
|
||||
<steamWorkshopUrl>steam://url/CommunityFilePage/2009463077</steamWorkshopUrl>
|
||||
<downloadUrl>https://github.com/pardeike/HarmonyRimWorld/releases/latest</downloadUrl>
|
||||
</li>
|
||||
</modDependencies>
|
||||
<description>Researchable (RT Mods research tab) electric fuses to mitigate short circuits. When placed anywhere on a power network, each fuse will safely discharge up to three of network's batteries, mitigating or preventing the explosion.
|
||||
|
||||
Does not require a new colony to add or remove (might throw a one-time error).
|
||||
|
||||
Refer to forum thread for additional information.</description>
|
||||
</ModMetaData>
|
||||
48
README.md
48
README.md
@@ -1,2 +1,48 @@
|
||||
# ProgressionMods
|
||||
# Steam Workshop Collection Manager
|
||||
|
||||
A Python GUI application for extracting Steam Workshop mod IDs from RimWorld mod collections.
|
||||
|
||||
## Features
|
||||
|
||||
- Dark themed Windows-style GUI
|
||||
- Three input fields for different mod collection types (Core, Content, Cosmetics)
|
||||
- Automatic extraction of Workshop IDs from Steam collection URLs
|
||||
- Real-time output display with scrollable text area
|
||||
- Pre-populated with example RimWorld mod collections
|
||||
|
||||
## Installation
|
||||
|
||||
1. Make sure you have Python 3.6+ installed
|
||||
2. Install required dependencies:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Run the application:
|
||||
```
|
||||
python steam_workshop_gui.py
|
||||
```
|
||||
|
||||
2. The application comes pre-populated with example collection URLs:
|
||||
- **Core**: https://steamcommunity.com/workshop/filedetails/?id=3521297585
|
||||
- **Content**: steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3521319712
|
||||
- **Cosmetics**: steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3637541646
|
||||
|
||||
3. You can modify these URLs or enter your own Steam Workshop collection URLs
|
||||
|
||||
4. Click "Extract Workshop IDs" to process the collections
|
||||
|
||||
5. The right panel will display all the Workshop IDs found in each collection
|
||||
|
||||
## URL Formats Supported
|
||||
|
||||
- Standard Steam Workshop URLs: `https://steamcommunity.com/workshop/filedetails/?id=XXXXXXXXX`
|
||||
- Steam protocol URLs: `steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=XXXXXXXXX`
|
||||
|
||||
## Notes
|
||||
|
||||
- The application fetches collection data directly from Steam Workshop
|
||||
- Processing may take a few seconds depending on collection size
|
||||
- Workshop IDs are extracted from the HTML content of collection pages
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
requests>=2.25.1
|
||||
502
steam_workshop_gui.py
Normal file
502
steam_workshop_gui.py
Normal file
@@ -0,0 +1,502 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, scrolledtext, filedialog, messagebox
|
||||
import requests
|
||||
import re
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
import threading
|
||||
import os
|
||||
import winreg
|
||||
from pathlib import Path
|
||||
|
||||
class SteamWorkshopGUI:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("Steam Workshop Collection Manager")
|
||||
self.root.geometry("1200x800")
|
||||
self.root.configure(bg='#2b2b2b')
|
||||
|
||||
# Configure dark theme
|
||||
self.setup_dark_theme()
|
||||
|
||||
# Initialize workshop folder
|
||||
self.workshop_folder = None
|
||||
|
||||
# Create main frame
|
||||
main_frame = tk.Frame(root, bg='#2b2b2b')
|
||||
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Left panel for inputs
|
||||
left_frame = tk.Frame(main_frame, bg='#2b2b2b', width=400)
|
||||
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
|
||||
left_frame.pack_propagate(False)
|
||||
|
||||
# Right panel for output
|
||||
right_frame = tk.Frame(main_frame, bg='#2b2b2b')
|
||||
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
|
||||
|
||||
self.create_input_section(left_frame)
|
||||
self.create_output_section(right_frame)
|
||||
|
||||
def setup_dark_theme(self):
|
||||
"""Configure dark theme colors"""
|
||||
style = ttk.Style()
|
||||
style.theme_use('clam')
|
||||
|
||||
# Configure colors for dark theme
|
||||
style.configure('TLabel', background='#2b2b2b', foreground='#ffffff')
|
||||
style.configure('TButton', background='#404040', foreground='#ffffff')
|
||||
style.map('TButton', background=[('active', '#505050')])
|
||||
style.configure('TEntry', background='#404040', foreground='#ffffff', fieldbackground='#404040')
|
||||
|
||||
def create_input_section(self, parent):
|
||||
"""Create the left input section"""
|
||||
title_label = tk.Label(parent, text="Steam Workshop Collections",
|
||||
font=('Arial', 14, 'bold'), bg='#2b2b2b', fg='#ffffff')
|
||||
title_label.pack(pady=(0, 10))
|
||||
|
||||
# RimWorld game folder input
|
||||
rimworld_frame = tk.Frame(parent, bg='#2b2b2b')
|
||||
rimworld_frame.pack(fill='x', pady=(0, 10))
|
||||
|
||||
rimworld_label = tk.Label(rimworld_frame, text="RimWorld Game Folder:",
|
||||
font=('Arial', 9, 'bold'), bg='#2b2b2b', fg='#ffffff')
|
||||
rimworld_label.pack(anchor='w')
|
||||
|
||||
rimworld_help = tk.Label(rimworld_frame,
|
||||
text="Right-click RimWorld in Steam > Manage > Browse local files, copy that path",
|
||||
font=('Arial', 8), bg='#2b2b2b', fg='#888888', wraplength=380)
|
||||
rimworld_help.pack(anchor='w', pady=(2, 5))
|
||||
|
||||
rimworld_input_frame = tk.Frame(rimworld_frame, bg='#2b2b2b')
|
||||
rimworld_input_frame.pack(fill='x')
|
||||
|
||||
self.rimworld_var = tk.StringVar()
|
||||
self.rimworld_entry = tk.Entry(rimworld_input_frame, textvariable=self.rimworld_var,
|
||||
font=('Arial', 9), bg='#404040', fg='#ffffff',
|
||||
insertbackground='#ffffff', relief='flat', bd=5)
|
||||
self.rimworld_entry.pack(side='left', fill='x', expand=True)
|
||||
self.rimworld_entry.bind('<KeyRelease>', self.on_rimworld_path_change)
|
||||
|
||||
browse_game_btn = tk.Button(rimworld_input_frame, text="Browse",
|
||||
command=self.browse_rimworld_folder,
|
||||
bg='#505050', fg='white', font=('Arial', 8),
|
||||
relief='flat', padx=10)
|
||||
browse_game_btn.pack(side='right', padx=(5, 0))
|
||||
|
||||
# Workshop folder display (derived from RimWorld path)
|
||||
workshop_frame = tk.Frame(parent, bg='#2b2b2b')
|
||||
workshop_frame.pack(fill='x', pady=(0, 15))
|
||||
|
||||
workshop_label = tk.Label(workshop_frame, text="Workshop Folder (Auto-derived):",
|
||||
font=('Arial', 9, 'bold'), bg='#2b2b2b', fg='#ffffff')
|
||||
workshop_label.pack(anchor='w')
|
||||
|
||||
self.workshop_var = tk.StringVar(value="Enter RimWorld path above")
|
||||
self.workshop_display = tk.Entry(workshop_frame, textvariable=self.workshop_var,
|
||||
font=('Arial', 8), bg='#404040', fg='#ffffff',
|
||||
insertbackground='#ffffff', relief='flat', bd=5, state='readonly')
|
||||
self.workshop_display.pack(fill='x', pady=(5, 0))
|
||||
|
||||
# Core collection
|
||||
self.create_url_input(parent, "Core Collection:",
|
||||
"https://steamcommunity.com/workshop/filedetails/?id=3521297585")
|
||||
|
||||
# Content collection
|
||||
self.create_url_input(parent, "Content Collection:",
|
||||
"steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3521319712")
|
||||
|
||||
# Cosmetics collection
|
||||
self.create_url_input(parent, "Cosmetics Collection:",
|
||||
"steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3637541646")
|
||||
|
||||
# Process button
|
||||
process_btn = tk.Button(parent, text="Extract Workshop IDs",
|
||||
command=self.process_collections,
|
||||
bg='#0078d4', fg='white', font=('Arial', 10, 'bold'),
|
||||
relief='flat', padx=20, pady=8)
|
||||
process_btn.pack(pady=10)
|
||||
|
||||
# Check local mods button
|
||||
local_btn = tk.Button(parent, text="List Local Mod Folders",
|
||||
command=self.list_local_mod_folders,
|
||||
bg='#28a745', fg='white', font=('Arial', 10, 'bold'),
|
||||
relief='flat', padx=20, pady=8)
|
||||
local_btn.pack(pady=5)
|
||||
|
||||
def create_url_input(self, parent, label_text, default_url):
|
||||
"""Create a labeled URL input field"""
|
||||
label = tk.Label(parent, text=label_text, font=('Arial', 10, 'bold'),
|
||||
bg='#2b2b2b', fg='#ffffff')
|
||||
label.pack(anchor='w', pady=(10, 5))
|
||||
|
||||
entry = tk.Entry(parent, font=('Arial', 9), bg='#404040', fg='#ffffff',
|
||||
insertbackground='#ffffff', relief='flat', bd=5)
|
||||
entry.pack(fill='x', pady=(0, 10))
|
||||
entry.insert(0, default_url)
|
||||
|
||||
# Store reference to entry widgets
|
||||
if not hasattr(self, 'url_entries'):
|
||||
self.url_entries = {}
|
||||
|
||||
collection_name = label_text.replace(" Collection:", "").lower()
|
||||
self.url_entries[collection_name] = entry
|
||||
|
||||
def create_output_section(self, parent):
|
||||
"""Create the right output section"""
|
||||
# Main output area
|
||||
main_output_frame = tk.Frame(parent, bg='#2b2b2b')
|
||||
main_output_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
title_label = tk.Label(main_output_frame, text="Workshop IDs Output",
|
||||
font=('Arial', 14, 'bold'), bg='#2b2b2b', fg='#ffffff')
|
||||
title_label.pack(pady=(0, 10))
|
||||
|
||||
# Output text area with scrollbar
|
||||
self.output_text = scrolledtext.ScrolledText(main_output_frame,
|
||||
font=('Consolas', 10),
|
||||
bg='#1e1e1e', fg='#ffffff',
|
||||
insertbackground='#ffffff',
|
||||
selectbackground='#404040',
|
||||
relief='flat', bd=5, height=20)
|
||||
self.output_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Local mod folders section
|
||||
local_mods_frame = tk.Frame(parent, bg='#2b2b2b')
|
||||
local_mods_frame.pack(fill=tk.X, pady=(10, 0))
|
||||
|
||||
local_title_label = tk.Label(local_mods_frame, text="Local Mod Folders",
|
||||
font=('Arial', 12, 'bold'), bg='#2b2b2b', fg='#ffffff')
|
||||
local_title_label.pack(pady=(0, 5))
|
||||
|
||||
# Local mods text area
|
||||
self.local_mods_text = scrolledtext.ScrolledText(local_mods_frame,
|
||||
font=('Consolas', 9),
|
||||
bg='#1e1e1e', fg='#ffffff',
|
||||
insertbackground='#ffffff',
|
||||
selectbackground='#404040',
|
||||
relief='flat', bd=5, height=8)
|
||||
self.local_mods_text.pack(fill=tk.X)
|
||||
|
||||
def extract_workshop_id(self, url):
|
||||
"""Extract workshop ID from Steam URL"""
|
||||
# Clean up steam:// protocol URLs
|
||||
if url.startswith('steam://openurl/'):
|
||||
url = url.replace('steam://openurl/', '')
|
||||
|
||||
# Extract ID from URL parameters
|
||||
try:
|
||||
parsed_url = urlparse(url)
|
||||
if 'id=' in parsed_url.query:
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
return query_params.get('id', [None])[0]
|
||||
elif '/filedetails/?id=' in url:
|
||||
# Direct extraction for simple cases
|
||||
match = re.search(r'id=(\d+)', url)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except Exception as e:
|
||||
print(f"Error extracting ID from {url}: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def process_collections(self):
|
||||
"""Process all collection URLs and extract workshop IDs"""
|
||||
self.output_text.delete(1.0, tk.END)
|
||||
self.output_text.insert(tk.END, "Processing collections...\n\n")
|
||||
self.root.update()
|
||||
|
||||
# Process each collection in a separate thread to avoid blocking UI
|
||||
threading.Thread(target=self._process_collections_thread, daemon=True).start()
|
||||
|
||||
def _process_collections_thread(self):
|
||||
"""Thread function to process collections"""
|
||||
try:
|
||||
for collection_name, entry in self.url_entries.items():
|
||||
url = entry.get().strip()
|
||||
if url:
|
||||
self._safe_update_output(f"=== {collection_name.upper()} COLLECTION ===\n")
|
||||
|
||||
workshop_id = self.extract_workshop_id(url)
|
||||
if workshop_id:
|
||||
self._safe_update_output(f"Collection ID: {workshop_id}\n")
|
||||
|
||||
# Fetch collection contents
|
||||
workshop_ids = self.fetch_collection_items(workshop_id)
|
||||
if workshop_ids:
|
||||
self._safe_update_output(f"Found {len(workshop_ids)} items:\n")
|
||||
|
||||
# Check which mods are installed locally if workshop folder is available
|
||||
local_mods = set()
|
||||
if self.workshop_folder and os.path.exists(self.workshop_folder):
|
||||
try:
|
||||
local_mods = {item for item in os.listdir(self.workshop_folder)
|
||||
if os.path.isdir(os.path.join(self.workshop_folder, item)) and item.isdigit()}
|
||||
except:
|
||||
pass
|
||||
|
||||
missing_count = 0
|
||||
for item_id in workshop_ids:
|
||||
status = " [INSTALLED]" if item_id in local_mods else " [MISSING]"
|
||||
if item_id not in local_mods:
|
||||
missing_count += 1
|
||||
self._safe_update_output(f"{item_id}{status}\n")
|
||||
|
||||
if local_mods:
|
||||
self._safe_update_output(f"\nSummary: {len(workshop_ids) - missing_count} installed, {missing_count} missing\n")
|
||||
else:
|
||||
self._safe_update_output("Could not fetch collection items\n")
|
||||
else:
|
||||
self._safe_update_output("Could not extract workshop ID from URL\n")
|
||||
|
||||
self._safe_update_output("\n")
|
||||
|
||||
self._safe_update_output("Processing complete!")
|
||||
except Exception as e:
|
||||
self._safe_update_output(f"Error in processing: {str(e)}\n")
|
||||
|
||||
def find_steam_workshop_folder(self):
|
||||
"""Automatically find the Steam Workshop folder for RimWorld"""
|
||||
try:
|
||||
# Method 1: Try to find Steam installation path from registry
|
||||
steam_paths = []
|
||||
|
||||
try:
|
||||
# Check HKEY_LOCAL_MACHINE first
|
||||
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Valve\Steam") as key:
|
||||
install_path = winreg.QueryValueEx(key, "InstallPath")[0]
|
||||
steam_paths.append(install_path)
|
||||
except (FileNotFoundError, OSError):
|
||||
pass
|
||||
|
||||
try:
|
||||
# Check HKEY_CURRENT_USER
|
||||
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Valve\Steam") as key:
|
||||
install_path = winreg.QueryValueEx(key, "SteamPath")[0]
|
||||
steam_paths.append(install_path)
|
||||
except (FileNotFoundError, OSError):
|
||||
pass
|
||||
|
||||
# Method 2: Check common Steam installation locations
|
||||
common_paths = [
|
||||
r"C:\Program Files (x86)\Steam",
|
||||
r"C:\Program Files\Steam",
|
||||
r"D:\Steam",
|
||||
r"E:\Steam",
|
||||
os.path.expanduser(r"~\Steam")
|
||||
]
|
||||
steam_paths.extend(common_paths)
|
||||
|
||||
# Method 3: Check if Steam is in PATH
|
||||
try:
|
||||
import shutil
|
||||
steam_exe = shutil.which("steam.exe")
|
||||
if steam_exe:
|
||||
steam_paths.append(os.path.dirname(steam_exe))
|
||||
except:
|
||||
pass
|
||||
|
||||
# Look for the workshop folder in each potential Steam path
|
||||
for steam_path in steam_paths:
|
||||
if not steam_path:
|
||||
continue
|
||||
|
||||
workshop_path = os.path.join(steam_path, "steamapps", "workshop", "content", "294100")
|
||||
if os.path.exists(workshop_path):
|
||||
return workshop_path
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error finding Steam workshop folder: {e}")
|
||||
return None
|
||||
|
||||
def browse_workshop_folder(self):
|
||||
"""Allow user to manually select the workshop folder"""
|
||||
folder = filedialog.askdirectory(
|
||||
title="Select RimWorld Workshop Folder (steamapps/workshop/content/294100)",
|
||||
initialdir=self.workshop_folder or "C:\\"
|
||||
)
|
||||
if folder:
|
||||
# Validate that this looks like a workshop folder
|
||||
if "294100" in folder or messagebox.askyesno(
|
||||
"Confirm Folder",
|
||||
f"Are you sure this is the RimWorld workshop folder?\n\n{folder}"
|
||||
):
|
||||
self._safe_update_output(f"Workshop folder updated: {folder}\n")
|
||||
|
||||
def on_rimworld_path_change(self, event=None):
|
||||
"""Called when RimWorld path is changed"""
|
||||
rimworld_path = self.rimworld_var.get().strip()
|
||||
if rimworld_path:
|
||||
# Derive workshop path from RimWorld path
|
||||
# From: D:\SteamLibrary\steamapps\common\RimWorld
|
||||
# To: D:\SteamLibrary\steamapps\workshop\content\294100
|
||||
|
||||
if "steamapps" in rimworld_path.lower():
|
||||
# Find the steamapps part and replace common\RimWorld with workshop\content\294100
|
||||
parts = rimworld_path.split(os.sep)
|
||||
try:
|
||||
steamapps_index = next(i for i, part in enumerate(parts) if part.lower() == 'steamapps')
|
||||
# Build new path: everything up to steamapps + steamapps + workshop + content + 294100
|
||||
workshop_parts = parts[:steamapps_index + 1] + ['workshop', 'content', '294100']
|
||||
workshop_path = os.sep.join(workshop_parts)
|
||||
self.workshop_folder = workshop_path
|
||||
self.workshop_var.set(workshop_path)
|
||||
except (StopIteration, IndexError):
|
||||
self.workshop_var.set("Invalid RimWorld path - should contain 'steamapps'")
|
||||
else:
|
||||
self.workshop_var.set("Invalid RimWorld path - should contain 'steamapps'")
|
||||
else:
|
||||
self.workshop_var.set("Enter RimWorld path above")
|
||||
|
||||
def browse_rimworld_folder(self):
|
||||
"""Allow user to browse for RimWorld game folder"""
|
||||
folder = filedialog.askdirectory(
|
||||
title="Select RimWorld Game Folder (Right-click RimWorld in Steam > Manage > Browse local files)",
|
||||
initialdir="C:\\"
|
||||
)
|
||||
if folder:
|
||||
self.rimworld_var.set(folder)
|
||||
self.on_rimworld_path_change() # Update workshop path
|
||||
|
||||
def list_local_mod_folders(self):
|
||||
"""List all local mod folder names in the separate text box"""
|
||||
if not self.workshop_folder or not os.path.exists(self.workshop_folder):
|
||||
self.local_mods_text.delete(1.0, tk.END)
|
||||
self.local_mods_text.insert(tk.END, "Workshop folder not found or invalid.\nPlease enter correct RimWorld path above.")
|
||||
return
|
||||
|
||||
threading.Thread(target=self._list_local_mod_folders_thread, daemon=True).start()
|
||||
|
||||
def _list_local_mod_folders_thread(self):
|
||||
"""Thread function to list local mod folders"""
|
||||
try:
|
||||
# Clear the local mods text box
|
||||
self.local_mods_text.delete(1.0, tk.END)
|
||||
|
||||
if not os.path.exists(self.workshop_folder):
|
||||
self.local_mods_text.insert(tk.END, f"Workshop folder does not exist:\n{self.workshop_folder}")
|
||||
return
|
||||
|
||||
# Get all subdirectories (each represents a workshop mod)
|
||||
local_mods = []
|
||||
for item in os.listdir(self.workshop_folder):
|
||||
item_path = os.path.join(self.workshop_folder, item)
|
||||
if os.path.isdir(item_path) and item.isdigit():
|
||||
local_mods.append(item)
|
||||
|
||||
local_mods.sort()
|
||||
|
||||
# Display in the local mods text box
|
||||
self.local_mods_text.insert(tk.END, f"Found {len(local_mods)} local workshop mod folders:\n\n")
|
||||
for mod_id in local_mods:
|
||||
self.local_mods_text.insert(tk.END, f"{mod_id}\n")
|
||||
|
||||
except Exception as e:
|
||||
self.local_mods_text.delete(1.0, tk.END)
|
||||
self.local_mods_text.insert(tk.END, f"Error listing local mods: {str(e)}")
|
||||
|
||||
def check_local_mods(self):
|
||||
"""Check which mods are installed locally in the workshop folder (for backward compatibility)"""
|
||||
self.list_local_mod_folders()
|
||||
|
||||
def _safe_update_output(self, text):
|
||||
"""Safely update output text without recursion"""
|
||||
try:
|
||||
self.output_text.insert(tk.END, text)
|
||||
self.output_text.see(tk.END)
|
||||
except Exception:
|
||||
pass # Ignore update errors to prevent recursion
|
||||
|
||||
def fetch_collection_items(self, collection_id):
|
||||
"""Fetch workshop IDs from a Steam Workshop collection"""
|
||||
try:
|
||||
# Steam Workshop collection URL
|
||||
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={collection_id}"
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
}
|
||||
|
||||
self._safe_update_output(f"Fetching collection page...\n")
|
||||
|
||||
response = requests.get(url, headers=headers, timeout=15)
|
||||
response.raise_for_status()
|
||||
|
||||
# Extract workshop IDs from collection items only
|
||||
html_content = response.text
|
||||
workshop_ids = []
|
||||
|
||||
# Find the collectionChildren section
|
||||
collection_start = html_content.find('<div class="collectionChildren">')
|
||||
if collection_start != -1:
|
||||
# Find the proper end of the collectionChildren section
|
||||
# Look for the closing </div> that matches the opening collectionChildren div
|
||||
# We need to count div tags to find the matching closing tag
|
||||
|
||||
search_pos = collection_start + len('<div class="collectionChildren">')
|
||||
div_count = 1 # We've opened one div
|
||||
collection_end = -1
|
||||
|
||||
while search_pos < len(html_content) and div_count > 0:
|
||||
next_open = html_content.find('<div', search_pos)
|
||||
next_close = html_content.find('</div>', search_pos)
|
||||
|
||||
if next_close == -1:
|
||||
break
|
||||
|
||||
if next_open != -1 and next_open < next_close:
|
||||
div_count += 1
|
||||
search_pos = next_open + 4
|
||||
else:
|
||||
div_count -= 1
|
||||
if div_count == 0:
|
||||
collection_end = next_close + 6
|
||||
break
|
||||
search_pos = next_close + 6
|
||||
|
||||
if collection_end == -1:
|
||||
collection_end = len(html_content)
|
||||
|
||||
collection_section = html_content[collection_start:collection_end]
|
||||
|
||||
# Extract IDs from sharedfile_ elements (these are the actual collection items)
|
||||
sharedfile_ids = re.findall(r'id="sharedfile_(\d+)"', collection_section)
|
||||
workshop_ids.extend(sharedfile_ids)
|
||||
|
||||
self._safe_update_output(f"Found {len(sharedfile_ids)} collection items\n")
|
||||
else:
|
||||
# Fallback: search the entire page but be more selective
|
||||
self._safe_update_output("Collection section not found, using fallback method\n")
|
||||
sharedfile_ids = re.findall(r'id="sharedfile_(\d+)"', html_content)
|
||||
workshop_ids.extend(sharedfile_ids)
|
||||
|
||||
# Remove duplicates and the collection ID itself
|
||||
unique_ids = list(set(workshop_ids))
|
||||
if collection_id in unique_ids:
|
||||
unique_ids.remove(collection_id)
|
||||
|
||||
return sorted(unique_ids) # Sort for consistent output
|
||||
|
||||
except requests.RequestException as e:
|
||||
self._safe_update_output(f"Network error: {str(e)}\n")
|
||||
return []
|
||||
except Exception as e:
|
||||
self._safe_update_output(f"Error fetching collection: {str(e)}\n")
|
||||
return []
|
||||
|
||||
def _update_output(self, text):
|
||||
"""Update output text (called from main thread)"""
|
||||
try:
|
||||
self.output_text.insert(tk.END, text)
|
||||
self.output_text.see(tk.END)
|
||||
except Exception:
|
||||
pass # Prevent recursion errors
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = SteamWorkshopGUI(root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user