feat: Prioritize Babepedia/Adult databases for image scraping and add full-name filter (ignore ambiguous single names)

This commit is contained in:
david
2026-08-17 11:06:02 -04:00
parent 846ca298c0
commit dfb267c7e3
2 changed files with 82 additions and 11 deletions
+9 -1
View File
@@ -1504,6 +1504,11 @@
<span>Female Only</span>
</label>
<label style="font-size: 0.75rem; color: var(--text-main); display: inline-flex; align-items: center; gap: 0.35rem; cursor: pointer; user-select: none;">
<input type="checkbox" id="toggle-stash-full-name" checked />
<span>Full Names (First + Last)</span>
</label>
<label style="font-size: 0.75rem; color: var(--text-main); display: inline-flex; align-items: center; gap: 0.35rem; cursor: pointer; user-select: none;">
<input type="checkbox" id="toggle-stash-has-scenes" checked />
<span>With Scenes Only (&gt;= 1)</span>
@@ -4901,6 +4906,7 @@ Return ONLY a valid JSON object with an array named "results":
const btnStashStopImages = document.getElementById('btn-stash-stop-images');
const btnCommitStashImages = document.getElementById('btn-commit-stash-images');
const toggleStashFemaleOnly = document.getElementById('toggle-stash-female-only');
const toggleStashFullName = document.getElementById('toggle-stash-full-name');
const toggleStashHasScenes = document.getElementById('toggle-stash-has-scenes');
const stashImagesSearchInput = document.getElementById('stash-images-search-input');
const thStashImgSelectAll = document.getElementById('th-stash-img-select-all');
@@ -4953,8 +4959,9 @@ Return ONLY a valid JSON object with an array named "results":
const stashUrl = stashEndpointInput ? stashEndpointInput.value.trim() : '';
const gender = toggleStashFemaleOnly && toggleStashFemaleOnly.checked ? 'FEMALE' : 'ALL';
const hasScenes = toggleStashHasScenes && toggleStashHasScenes.checked ? 'true' : 'false';
const fullNamesOnly = toggleStashFullName && toggleStashFullName.checked ? 'true' : 'false';
const res = await fetch(`/api/stash/performers/missing-images?url=${encodeURIComponent(stashUrl)}&gender=${gender}&has_scenes=${hasScenes}`);
const res = await fetch(`/api/stash/performers/missing-images?url=${encodeURIComponent(stashUrl)}&gender=${gender}&has_scenes=${hasScenes}&full_names_only=${fullNamesOnly}`);
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Failed to scan Stash performers');
@@ -5390,6 +5397,7 @@ Return ONLY a valid JSON object with an array named "results":
}
if (toggleStashFemaleOnly) toggleStashFemaleOnly.addEventListener('change', scanStashForMissingImages);
if (toggleStashFullName) toggleStashFullName.addEventListener('change', scanStashForMissingImages);
if (toggleStashHasScenes) toggleStashHasScenes.addEventListener('change', scanStashForMissingImages);
if (stashImagesSearchInput) {
+73 -10
View File
@@ -384,19 +384,74 @@ def query_stash_batch(items, stash_url=None):
return results
def scrape_babepedia_images(name, limit=4):
slug = name.strip().replace(' ', '_')
url = f"https://www.babepedia.com/babe/{urllib.parse.quote(slug)}"
req = urllib.request.Request(
url,
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept-Language': 'en-US,en;q=0.9',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}
)
results = []
seen = set()
try:
with urllib.request.urlopen(req, timeout=6) as res:
html = res.read().decode('utf-8', errors='ignore')
m = re.search(r'id=\"profimg\"[^\>]*href=\"([^\"]+)\"', html)
if not m:
m = re.search(r'<img[^>]+src=[\"\'](/babeimg/[^\"\']+)[\"\']', html)
if m:
u = m.group(1)
if not u.startswith('http'):
u = 'https://www.babepedia.com' + u
seen.add(u)
results.append({'url': u, 'thumbnail': u, 'source': 'Babepedia'})
gallery = re.findall(r'href=[\"\'](/pics/[^\"\']+)[\"\']', html)
for g in gallery:
if not g.startswith('http'):
g = 'https://www.babepedia.com' + g
if g not in seen:
seen.add(g)
results.append({'url': g, 'thumbnail': g, 'source': 'Babepedia'})
if len(results) >= limit:
break
except Exception:
pass
return results
def search_performer_candidate_images(name, aliases=None, limit=6):
results = []
seen = set()
# 1. Primary: Direct Babepedia Adult Database Scrape
babe_imgs = scrape_babepedia_images(name, limit=4)
for it in babe_imgs:
if it['url'] not in seen:
seen.add(it['url'])
results.append(it)
if len(results) >= limit:
return results
# 2. Secondary: Adult Database Targeted Queries on Bing
queries = [
f"{name} actress portrait",
f"{name} model photoshoot",
f"{name} portrait"
f'"{name}" site:babepedia.com',
f'"{name}" site:freeones.com',
f'"{name}" site:iafd.com',
f'"{name}" site:boobpedia.com',
f'"{name}" adult star headshot portrait',
f'"{name}" adult photoshoot portrait'
]
if aliases and isinstance(aliases, list):
for a in aliases[:2]:
if a and a.lower() != name.lower():
queries.append(f"{a} actress portrait")
queries.append(f'"{a}" site:babepedia.com')
queries.append(f'"{a}" adult star portrait')
results = []
seen = set()
for q in queries:
url = f"https://www.bing.com/images/search?q={urllib.parse.quote(q)}&FORM=HDRSC2"
req = urllib.request.Request(
@@ -413,11 +468,11 @@ def search_performer_candidate_images(name, aliases=None, limit=6):
murls = re.findall(r'&quot;murl&quot;:&quot;(http[^&]+)&quot;', html)
turls = re.findall(r'&quot;turl&quot;:&quot;(http[^&]+)&quot;', html)
for i, u in enumerate(murls):
clean_u = u.replace('\\/', '/')
if clean_u not in seen:
clean_u = u.replace(r'\/', '/')
if clean_u not in seen and not any(bad in clean_u.lower() for bad in ['youtube.com', 'wikimedia.org', 'wikipedia.org', 'disney', 'marvel', 'cartoon', 'anime']):
seen.add(clean_u)
t = turls[i].replace('\\/', '/') if i < len(turls) else clean_u
results.append({'url': clean_u, 'thumbnail': t})
t = turls[i].replace(r'\/', '/') if i < len(turls) else clean_u
results.append({'url': clean_u, 'thumbnail': t, 'source': 'Adult Web'})
if len(results) >= limit:
break
except Exception:
@@ -1178,6 +1233,7 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
stash_url = params.get('url', [''])[0] or DEFAULT_STASH_URL
gender_filter = params.get('gender', ['FEMALE'])[0].upper()
only_scenes = params.get('has_scenes', ['true'])[0].lower() in ['true', '1']
full_names_only = params.get('full_names_only', ['true'])[0].lower() in ['true', '1']
query = '''
query {
@@ -1205,6 +1261,13 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
if not is_missing:
continue
# Filter out ambiguous single-word names (only first names)
p_name = (p.get('name') or '').strip()
if full_names_only:
name_parts = [pt for pt in re.split(r'[\s\.\-_]+', p_name) if len(pt) > 1]
if len(name_parts) < 2:
continue
p_gender = (p.get('gender') or '').upper()
if gender_filter != 'ALL':
if gender_filter == 'FEMALE' and p_gender not in ['FEMALE', '', 'NONE', None]: