Skip to content

Instantly share code, notes, and snippets.

@macim
Created January 29, 2026 01:42
Show Gist options
  • Select an option

  • Save macim/c8a143863b534de2b591d38463902a8f to your computer and use it in GitHub Desktop.

Select an option

Save macim/c8a143863b534de2b591d38463902a8f to your computer and use it in GitHub Desktop.
modsgrapperdebug.py - From Reddit Post
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