Skip to content

Commit e1bd437

Browse files
committed
- add update_screenshots.py utility script for automated screenshot generation (created with Claude 3.7)
- implement git and timestamp-based detection of modified files - add support for headless browser automation with selenium - add customizable screenshot parameters (size, URL, max entries) - add `logo` class to navigation links for screenshot processing - update screenshots of recently updated games
1 parent c57417e commit e1bd437

File tree

10 files changed

+230
-0
lines changed

10 files changed

+230
-0
lines changed

logo.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ window.addEventListener('load', function () {
55
const l = document.createElement('a');
66
l.href = 'https://gptgames.dev';
77
l.target = '_blank';
8+
l.classList.add('logo')
89
l.style.position = 'fixed';
910
l.style.zIndex = '9999';
1011
l.style.transition = 'transform 0.2s ease';

screenshots/screenshot_100.png

5.8 KB
Loading

screenshots/screenshot_122.png

-11.6 KB
Loading

screenshots/screenshot_138.png

13.9 KB
Loading

screenshots/screenshot_26.png

21.7 KB
Loading

screenshots/screenshot_44.png

27.9 KB
Loading

screenshots/screenshot_64.png

-2.64 KB
Loading

screenshots/screenshot_76.png

17.3 KB
Loading

screenshots/screenshot_91.png

-5.12 KB
Loading

util/update_screenshots.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import os
2+
import json
3+
import subprocess
4+
from pathlib import Path
5+
from PIL import Image
6+
from datetime import datetime
7+
from selenium import webdriver
8+
from selenium.webdriver.chrome.options import Options
9+
import time
10+
11+
def get_recently_modified_paths_git(project_root, num_entries=10):
12+
"""Get recently modified files using git"""
13+
try:
14+
# Get the list of files modified in the most recent commits
15+
result = subprocess.run(
16+
["git", "-C", str(project_root), "log", "--name-only", "--pretty=format:", "-n", "50"],
17+
capture_output=True, text=True, check=True
18+
)
19+
20+
# Parse the output to get unique file paths
21+
modified_files = [line.strip() for line in result.stdout.split('\n') if line.strip()]
22+
23+
# Filter to include only relevant paths (HTML, JS, or other web content)
24+
relevant_files = []
25+
for file_path in modified_files:
26+
if file_path.endswith(('.html', '.htm', '.js', '.css', '.php')) and os.path.exists(project_root / file_path):
27+
relevant_files.append(file_path)
28+
29+
# Remove duplicates and limit to requested number
30+
unique_files = list(dict.fromkeys(relevant_files))
31+
return unique_files[:num_entries]
32+
except Exception as e:
33+
print(f"Error using git to find modified files: {str(e)}")
34+
return []
35+
36+
def get_recently_modified_paths_timestamp(project_root, num_entries=10):
37+
"""Get recently modified files using file timestamps"""
38+
try:
39+
# Get all HTML, JS, and other web content files
40+
all_files = []
41+
for ext in ['.html', '.htm', '.js', '.css', '.php']:
42+
all_files.extend(list(project_root.glob(f"**/*{ext}")))
43+
44+
# Sort by modification time (newest first)
45+
modified_files = sorted(all_files, key=lambda x: os.path.getmtime(x), reverse=True)
46+
47+
# Convert to relative paths
48+
relative_paths = [str(path.relative_to(project_root)) for path in modified_files]
49+
50+
return relative_paths[:num_entries]
51+
except Exception as e:
52+
print(f"Error finding modified files by timestamp: {str(e)}")
53+
return []
54+
55+
def update_screenshots(entries_file, modified_paths=None, max_entries=10, base_url="http://localhost:80"):
56+
"""
57+
Update screenshots for recently modified entries in a JSON file
58+
59+
Args:
60+
entries_file: Path to the JSON file with entries
61+
modified_paths: List of recently modified file paths
62+
max_entries: Maximum number of entries to update
63+
base_url: Base URL for the local server
64+
"""
65+
# Get absolute project root path (2 levels up from the script location)
66+
script_dir = Path(__file__).resolve().parent
67+
project_root = script_dir.parent
68+
69+
# Convert relative path to absolute
70+
entries_path = project_root / entries_file
71+
72+
print(f"Processing file: {entries_path}")
73+
74+
# Load JSON data with UTF-8 encoding
75+
with open(entries_path, 'r', encoding='utf-8') as f:
76+
try:
77+
data = json.load(f)
78+
except json.JSONDecodeError:
79+
print(f"Error: Could not parse JSON file {entries_path}")
80+
return
81+
82+
# Handle different JSON structures
83+
if isinstance(data, dict) and "entries" in data:
84+
entries = data["entries"]
85+
elif isinstance(data, list):
86+
entries = data
87+
else:
88+
entries = [data]
89+
90+
# Filter entries to only those recently modified
91+
if modified_paths:
92+
filtered_entries = []
93+
for entry in entries:
94+
if "url" in entry:
95+
# Check if this entry's URL matches any of the modified paths
96+
for path in modified_paths:
97+
if entry["url"] in path or path in entry["url"]:
98+
filtered_entries.append(entry)
99+
break
100+
101+
# If no matching entries found, take the most recent ones by date field
102+
if not filtered_entries and entries and "date" in entries[0]:
103+
sorted_entries = sorted(entries, key=lambda x: x.get("date", ""), reverse=True)
104+
filtered_entries = sorted_entries[:max_entries]
105+
print(f"No matching files found. Using {len(filtered_entries)} most recent entries by date.")
106+
107+
entries = filtered_entries[:max_entries]
108+
109+
if not entries:
110+
print("No entries to update.")
111+
return
112+
113+
print(f"Updating screenshots for {len(entries)} entries...")
114+
115+
# Set up headless browser with CSS to hide scrollbars
116+
chrome_options = Options()
117+
chrome_options.add_argument("--headless")
118+
chrome_options.add_argument("--hide-scrollbars") # Hide scrollbars in Chrome
119+
driver = webdriver.Chrome(options=chrome_options)
120+
driver.set_window_size(800, 800)
121+
122+
# Update screenshots for each entry
123+
for i, entry in enumerate(entries):
124+
if "url" not in entry or "screenshot" not in entry:
125+
print(f"Skipping entry {i+1}: Missing url or screenshot")
126+
continue
127+
128+
url = entry["url"]
129+
screenshot_path = entry["screenshot"]
130+
131+
# Convert screenshot path to absolute path
132+
abs_screenshot_path = project_root / screenshot_path
133+
134+
print(f"[{i+1}/{len(entries)}] Updating: {entry.get('title', url)}")
135+
136+
try:
137+
# Ensure screenshot directory exists
138+
abs_screenshot_path.parent.mkdir(parents=True, exist_ok=True)
139+
140+
# Take screenshot
141+
full_url = f"{base_url}/{url}" if not url.startswith("http") else url
142+
print(f" Loading: {full_url}")
143+
driver.get(full_url)
144+
145+
# Wait for page to load
146+
time.sleep(1)
147+
148+
# Hide scrollbars, #sidebar-toggle and a.logo elements before taking screenshot
149+
driver.execute_script("""
150+
// Hide scrollbars
151+
document.documentElement.style.overflow = 'hidden';
152+
document.body.style.overflow = 'hidden';
153+
document.documentElement.style.msOverflowStyle = 'none';
154+
document.documentElement.style.scrollbarWidth = 'none';
155+
156+
// Hide sidebar-toggle
157+
const sidebarToggle = document.getElementById('sidebar-toggle');
158+
if (sidebarToggle) {
159+
sidebarToggle.style.display = 'none';
160+
}
161+
162+
// Hide logo links
163+
const logoLinks = document.querySelectorAll('a.logo');
164+
logoLinks.forEach(link => {
165+
link.style.display = 'none';
166+
});
167+
""")
168+
169+
# Take screenshot
170+
driver.save_screenshot(str(abs_screenshot_path))
171+
172+
# Resize screenshot
173+
image = Image.open(abs_screenshot_path)
174+
resized_image = image.resize((260, 260), Image.LANCZOS)
175+
resized_image.save(abs_screenshot_path)
176+
177+
print(f" ✓ Screenshot updated: {screenshot_path}")
178+
except Exception as e:
179+
print(f" ✗ Error updating screenshot: {str(e)}")
180+
181+
driver.quit()
182+
print(f"Screenshot update completed for {entries_path}")
183+
184+
if __name__ == "__main__":
185+
print("Screenshot Update Tool")
186+
print("---------------------")
187+
188+
# Get absolute project root path
189+
script_dir = Path(__file__).resolve().parent
190+
project_root = script_dir.parent
191+
192+
# Ask for method to determine recent changes
193+
print("\nHow would you like to identify recently changed files?")
194+
print("1: Use git history (recommended)")
195+
print("2: Use file timestamps")
196+
print("3: Update all entries")
197+
198+
choice = input("Enter choice (default: 1): ") or "1"
199+
200+
# Default base URL
201+
base_url = input("\nEnter the base URL (default: http://localhost:80): ") or "http://localhost:80"
202+
203+
# Get number of entries to update
204+
max_entries = int(input("\nEnter maximum number of entries to update (default: 10): ") or "10")
205+
206+
# Get modified paths based on chosen method
207+
modified_paths = None
208+
if choice == "1":
209+
print("\nFinding recently modified files using git...")
210+
modified_paths = get_recently_modified_paths_git(project_root, max_entries)
211+
print(f"Found {len(modified_paths)} recently modified files")
212+
elif choice == "2":
213+
print("\nFinding recently modified files using timestamps...")
214+
modified_paths = get_recently_modified_paths_timestamp(project_root, max_entries)
215+
print(f"Found {len(modified_paths)} recently modified files")
216+
else:
217+
print("\nUpdating all entries...")
218+
219+
# Show modified paths
220+
if modified_paths:
221+
print("\nRecently modified files:")
222+
for path in modified_paths:
223+
print(f" - {path}")
224+
225+
# Process games and tools
226+
update_screenshots("data/games.json", modified_paths, max_entries, base_url)
227+
update_screenshots("data/tools.json", modified_paths, max_entries, base_url)
228+
229+
print("\nScreenshot update process completed!")

0 commit comments

Comments
 (0)