Created
January 29, 2026 01:42
-
-
Save macim/c8a143863b534de2b591d38463902a8f to your computer and use it in GitHub Desktop.
modsgrapperdebug.py - From Reddit Post
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import tkinter as tk | |
| import re | |
| import webbrowser | |
| from tkinter import messagebox | |
| from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError | |
| # ------------------------------------------------- | |
| # Extract Steam ID | |
| # ------------------------------------------------- | |
| def extract_steam_id(text: str): | |
| text = text.strip() | |
| if not text: | |
| return None | |
| if text.isdigit(): | |
| return text | |
| m = re.search(r"[?&]id=(\d+)", text) | |
| if m: | |
| return m.group(1) | |
| return None | |
| # ------------------------------------------------- | |
| # Build smods URLs | |
| # ------------------------------------------------- | |
| def build_urls(steam_id: str): | |
| return [ | |
| f"https://stellaris.smods.ru/?s={steam_id}", | |
| f"https://hearts-of-iron-4.smods.ru/?s={steam_id}", | |
| f"https://smods.ru/?s={steam_id}", | |
| f"https://catalogue.smods.ru/?s={steam_id}&app=1158310", | |
| f"https://catalogue.smods.ru/?s={steam_id}&app=236850", | |
| ] | |
| # ------------------------------------------------- | |
| # Playwright: find modsbase link on a page | |
| # (robust: DOM query first, then HTML regex, with debug output) | |
| # ------------------------------------------------- | |
| def find_modsbase_link_on_page(page, url: str, timeout_ms=25000) -> str | None: | |
| print(f"[CHECK] {url}") | |
| try: | |
| page.goto(url, timeout=timeout_ms, wait_until="domcontentloaded") | |
| # Short wait to allow JS, redirects, cookies to settle | |
| page.wait_for_timeout(2500) | |
| # Debug: what did Playwright actually load? | |
| title = page.title() | |
| current_url = page.url | |
| print(f"[DEBUG] page.url = {current_url}") | |
| print(f"[DEBUG] title = {title}") | |
| # 1) DOM search: tolerant selector (href contains modsbase.com) | |
| # Not only ^https:// because sometimes http:// or // is used | |
| locator = page.locator('a.skymods-excerpt-btn[href*="modsbase.com"]') | |
| count = locator.count() | |
| print(f"[DEBUG] DOM matches (a.skymods-excerpt-btn[href*='modsbase.com']) = {count}") | |
| if count > 0: | |
| href = locator.first.get_attribute("href") | |
| if href: | |
| print(f"[FOUND] href = {href}") | |
| return href | |
| # 2) Fallback: search rendered HTML via regex (even more tolerant) | |
| html = page.content() | |
| # Common Cloudflare / block detection (diagnostic only) | |
| if "Just a moment" in html or "cf-challenge" in html or "Cloudflare" in html: | |
| print("[DEBUG] Looks like a Cloudflare challenge page (Playwright may be blocked).") | |
| # Regex: also matches http:// or //modsbase.com | |
| m = re.search( | |
| r'href=["\'](https?:)?//modsbase\.com/[^"\']+["\']', | |
| html, | |
| flags=re.IGNORECASE | |
| ) | |
| if m: | |
| url_match = re.search( | |
| r'(https?:)?//modsbase\.com/[^"\']+', | |
| m.group(0), | |
| flags=re.IGNORECASE | |
| ) | |
| if url_match: | |
| href = url_match.group(0) | |
| # Normalize //modsbase.com/... to https://modsbase.com/... | |
| if href.startswith("//"): | |
| href = "https:" + href | |
| print(f"[FOUND via REGEX] href = {href}") | |
| return href | |
| print("[NO MOD] No modsbase link found") | |
| return None | |
| except PlaywrightTimeoutError: | |
| print("[ERROR] Timeout while loading or waiting") | |
| return None | |
| except Exception as e: | |
| print(f"[ERROR] Playwright exception: {e}") | |
| return None | |
| # ------------------------------------------------- | |
| # For a Steam ID: find the first URL that yields a modsbase link | |
| # ------------------------------------------------- | |
| def find_modsbase_for_id(page, steam_id: str) -> str | None: | |
| for url in build_urls(steam_id): | |
| href = find_modsbase_link_on_page(page, url) | |
| if href: | |
| return href | |
| return None | |
| # ------------------------------------------------- | |
| # GUI: submit handler | |
| # ------------------------------------------------- | |
| def on_submit(): | |
| raw_inputs = [] | |
| for _, entry in entry_widgets: | |
| v = entry.get().strip() | |
| if v: | |
| raw_inputs.append(v) | |
| if not raw_inputs: | |
| messagebox.showinfo("Info", "Please enter at least one Steam ID or Steam Workshop link.") | |
| return | |
| not_found = [] | |
| opened = 0 | |
| # Start Playwright once (faster) | |
| with sync_playwright() as p: | |
| browser = p.chromium.launch(headless=True) | |
| context = browser.new_context() | |
| page = context.new_page() | |
| for raw in raw_inputs: | |
| steam_id = extract_steam_id(raw) | |
| if not steam_id: | |
| not_found.append(raw) | |
| continue | |
| print("\n---------------------------") | |
| print(f"[DEBUG] Steam ID: {steam_id}") | |
| modsbase_url = find_modsbase_for_id(page, steam_id) | |
| if modsbase_url: | |
| webbrowser.open(modsbase_url) | |
| opened += 1 | |
| else: | |
| not_found.append(raw) | |
| browser.close() | |
| if not_found: | |
| result_label.config( | |
| text=f"{opened} opened, {len(not_found)} not found.", | |
| fg="red" | |
| ) | |
| not_found_text.delete(1.0, tk.END) | |
| not_found_text.insert(tk.END, "\n".join(not_found)) | |
| else: | |
| result_label.config( | |
| text=f"All found! ({opened} opened)", | |
| fg="green" | |
| ) | |
| not_found_text.delete(1.0, tk.END) | |
| # ------------------------------------------------- | |
| # GUI | |
| # ------------------------------------------------- | |
| root = tk.Tk() | |
| root.title("Steam Workshop → modsbase Finder (Playwright)") | |
| root.geometry("700x600") | |
| root.configure(bg="#171a21") | |
| tk.Label( | |
| root, | |
| text="Steam → modsbase (Playwright)", | |
| font=("Arial", 18, "bold"), | |
| bg="#171a21", | |
| fg="#ecf0f1" | |
| ).pack(pady=10) | |
| content_frame = tk.Frame(root, bg="#34495e") | |
| content_frame.pack(fill="both", expand=True, padx=10, pady=10) | |
| canvas = tk.Canvas(content_frame, bg="#34495e", highlightthickness=0) | |
| canvas.pack(side="left", fill="both", expand=True) | |
| scrollbar = tk.Scrollbar(content_frame, orient="vertical", command=canvas.yview) | |
| scrollbar.pack(side="right", fill="y") | |
| canvas.configure(yscrollcommand=scrollbar.set) | |
| inner_frame = tk.Frame(canvas, bg="#34495e") | |
| canvas.create_window((0, 0), window=inner_frame, anchor="nw") | |
| def update_scroll_region(event=None): | |
| canvas.configure(scrollregion=canvas.bbox("all")) | |
| inner_frame.bind("<Configure>", update_scroll_region) | |
| entry_widgets = [] | |
| def delete_entry(entry_frame): | |
| entry_frame.destroy() | |
| global entry_widgets | |
| entry_widgets[:] = [w for w in entry_widgets if w[0] != entry_frame] | |
| def add_entry(): | |
| entry_frame = tk.Frame(inner_frame, bg="#34495e") | |
| entry_frame.pack(pady=5, padx=10, fill="x") | |
| entry = tk.Entry(entry_frame, font=("Arial", 12), bg="#c7d5e0", fg="#171a21") | |
| entry.pack(side="left", fill="x", expand=True, padx=(0, 5)) | |
| if len(entry_widgets) > 0: | |
| tk.Button( | |
| entry_frame, | |
| text="X", | |
| font=("Arial", 12, "bold"), | |
| bg="#e74c3c", | |
| fg="#ffffff", | |
| command=lambda: delete_entry(entry_frame) | |
| ).pack(side="right") | |
| entry_widgets.append((entry_frame, entry)) | |
| entry.focus_set() | |
| def auto_add_entry(event=None): | |
| for _, entry in entry_widgets: | |
| if entry.get().strip(): | |
| add_entry() | |
| break | |
| root.bind("<Return>", auto_add_entry) | |
| tk.Button( | |
| root, | |
| text="Find & open modsbase link", | |
| command=on_submit, | |
| font=("Arial", 14), | |
| bg="#1b2838", | |
| fg="#FFFFFF", | |
| width=30 | |
| ).pack(pady=10) | |
| result_label = tk.Label(root, text="", font=("Arial", 12), bg="#171a21", fg="#ecf0f1") | |
| result_label.pack(pady=5) | |
| tk.Label( | |
| root, | |
| text="Not found / invalid:", | |
| font=("Arial", 12), | |
| bg="#171a21", | |
| fg="#ecf0f1" | |
| ).pack(pady=5) | |
| not_found_text = tk.Text( | |
| root, | |
| height=10, | |
| font=("Arial", 12), | |
| bg="#1b2838", | |
| fg="#FFFFFF", | |
| wrap="word" | |
| ) | |
| not_found_text.pack(pady=5, padx=10, fill="both") | |
| add_entry() | |
| root.mainloop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment